feat: Add test helper for E2E staking flow (#66)

Implements window.__testHelpers.fillStakeForm() to enable stable E2E testing
of the staking form without fragile UI selectors.

## Changes

- Add window.__testHelpers interface (dev mode only)
- Implement fillStakeForm() in StakeHolder.vue with input validation
- Add TypeScript declarations in env.d.ts
- Update E2E test to use helper and verify full user journey
- Create INTEGRATION_TEST_STATUS.md documenting test coverage
- Document helper in web-app/README.md

## Test Coverage

Playwright E2E now validates complete flow:
- Mint ETH via cheats page UI
- Swap KRK via cheats page UI
- Stake KRK via stake page UI (helper + click)
- Verify position via GraphQL

Both Playwright and verify-swap.sh tests now work independently.

resolves #62

Co-authored-by: johba <johba@harb.eth>
Reviewed-on: https://codeberg.org/johba/harb/pulls/66
This commit is contained in:
johba 2025-10-07 15:06:38 +02:00
parent b1f40374cd
commit 8cd64e808f
5 changed files with 272 additions and 2 deletions

135
INTEGRATION_TEST_STATUS.md Normal file
View file

@ -0,0 +1,135 @@
# Integration Test Status
## Overview
This document tracks the integration test coverage for the Harb/Kraiken stack, including both Playwright E2E tests and shell-based verification scripts.
## Test Coverage Matrix
| Test | Mint ETH | Swap | Stake | Method | Speed | Status |
|------|----------|------|-------|--------|-------|--------|
| **Playwright E2E** | ✅ UI | ✅ UI | ✅ UI (helper + click) | Browser automation | ~4 min | ✅ Implemented |
| **verify-swap.sh** | ❌ | ✅ RPC | ✅ RPC | Direct contract calls | ~5 sec | ✅ Working |
## Playwright E2E Tests
### Location
`tests/e2e/01-acquire-and-stake.spec.ts`
### Full User Journey
The E2E test validates the complete user flow from start to finish:
1. **Mint ETH** - Uses the cheats page UI to mint test ETH via Anvil
2. **Swap KRK** - Uses the cheats page UI to swap ETH for KRK tokens
3. **Stake KRK** - Uses the stake page UI with test helper to create a staking position
### Test Helper Implementation
To avoid fragile UI selectors for complex form interactions, we use a test helper pattern:
**Helper Location:** `web-app/src/components/StakeHolder.vue`
```typescript
// Exposed on window.__testHelpers (dev mode only)
window.__testHelpers = {
fillStakeForm: async (params: { amount: number; taxRate: number }) => {
// Validates amount against min/max constraints
// Sets reactive form values
// Waits for Vue reactivity
}
}
```
**TypeScript Declarations:** `web-app/env.d.ts`
**Usage in Tests:**
```typescript
await page.evaluate(async () => {
await window.__testHelpers.fillStakeForm({
amount: 100,
taxRate: 5.0,
});
});
const stakeButton = page.getByRole('button', { name: /Stake|Snatch and Stake/i });
await stakeButton.click();
```
### Verification Strategy
- **Swap Verification:** Direct RPC call to check KRK token balance
- **Stake Verification:** GraphQL query to Ponder indexer for position data
### Running E2E Tests
```bash
# From repository root
npm run test:e2e
```
The tests automatically:
- Start the full stack (Anvil, contracts, Ponder, web app, txnBot)
- Wait for all services to be ready
- Execute test scenarios
- Stop the stack on completion
## verify-swap.sh Script
### Location
`tests/verify-swap.sh`
### Purpose
Fast contract-level verification that doesn't require browser automation.
### Coverage
- ✅ Swap ETH for KRK via Uniswap router
- ✅ Create staking position via contract call
- ✅ Verify position created in contract storage
### Running the Script
```bash
./tests/verify-swap.sh
```
## Benefits of Dual Approach
### Why Both Tests?
1. **E2E Tests (Playwright)**
- Validates full UI flow
- Catches frontend regressions
- Tests user experience
- Slower but comprehensive
2. **Contract Tests (verify-swap.sh)**
- Fast feedback loop
- Contract-level validation
- Independent of UI changes
- Useful for debugging
### Test Helper Pattern
The `window.__testHelpers` pattern provides:
- **Stability:** Immune to CSS/selector changes
- **Maintainability:** Test intent is clear (`fillStakeForm`)
- **Safety:** Only available in dev mode
- **Flexibility:** Can validate inputs before setting form values
## Future Enhancements
Potential additions to test coverage:
- [ ] Unstaking flow
- [ ] Position snatching scenarios
- [ ] Tax payment via UI
- [ ] Multiple position management
- [ ] Edge cases (insufficient balance, invalid tax rates)
## Notes
- Test helpers should validate inputs to catch test bugs early
- Always verify test actions via independent data sources (RPC, GraphQL)
- Keep verify-swap.sh in sync with E2E test expectations
- Document any new helpers in this file

View file

@ -145,8 +145,56 @@ test.describe('Acquire & Stake', () => {
expect(balance).toBeGreaterThan(0n);
console.log('[TEST] ✅ Swap successful! KRK balance > 0');
console.log('[TEST] ✅ E2E test complete: Swap verified through UI');
console.log('[TEST] Note: Staking is verified separately via verify-swap.sh script');
// Now test staking via UI
console.log('[TEST] Navigating to stake page...');
await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`);
await page.waitForTimeout(2_000);
// Wait for the stake form to be initialized
console.log('[TEST] Waiting for stake form to load...');
await page.waitForSelector('text=Token Amount', { timeout: 15_000 });
// Use the test helper to fill the stake form
console.log('[TEST] Filling stake form via test helper...');
await page.evaluate(async () => {
if (!window.__testHelpers) {
throw new Error('Test helpers not available');
}
await window.__testHelpers.fillStakeForm({
amount: 100, // Stake 100 KRK
taxRate: 5.0, // 5% tax rate
});
});
console.log('[TEST] Clicking stake button...');
const stakeButton = page.getByRole('button', { name: /Stake|Snatch and Stake/i });
await expect(stakeButton).toBeVisible({ timeout: 5_000 });
await stakeButton.click();
// Wait for transaction to process
console.log('[TEST] Waiting for stake transaction...');
try {
await page.getByRole('button', { name: /Sign Transaction|Waiting/i }).waitFor({ state: 'visible', timeout: 5_000 });
console.log('[TEST] Transaction initiated, waiting for completion...');
await page.getByRole('button', { name: /Stake|Snatch and Stake/i }).waitFor({ state: 'visible', timeout: 60_000 });
console.log('[TEST] Stake transaction completed!');
} catch (e) {
console.log('[TEST] Transaction may have completed instantly');
}
await page.waitForTimeout(3_000);
// Verify staking position via GraphQL
console.log('[TEST] Verifying staking position via GraphQL...');
const positions = await fetchPositions(request, ACCOUNT_ADDRESS);
console.log(`[TEST] Found ${positions.length} position(s)`);
expect(positions.length).toBeGreaterThan(0);
const activePositions = positions.filter(p => p.status === 'OPEN');
expect(activePositions.length).toBeGreaterThan(0);
console.log(`[TEST] ✅ Staking successful! Created ${activePositions.length} active position(s)`);
console.log('[TEST] ✅ E2E test complete: Full journey verified (Mint → Swap → Stake)');
} finally {
await context.close();
}

View file

@ -31,3 +31,56 @@ npm run dev
```sh
npm run build
```
## Testing
### Test Helpers
The application exposes test helpers on `window.__testHelpers` in development mode to facilitate E2E testing.
#### Available Helpers
##### `fillStakeForm(params)`
Programmatically fills the staking form without requiring fragile UI selectors.
**Parameters:**
- `amount` (number): Amount of KRK tokens to stake (must be >= minimum stake)
- `taxRate` (number): Tax rate percentage (must be between 0 and 100)
**Example:**
```typescript
// In Playwright test
await page.evaluate(async () => {
await window.__testHelpers.fillStakeForm({
amount: 100,
taxRate: 5.0,
});
});
// Then click the stake button
const stakeButton = page.getByRole('button', { name: /Stake|Snatch and Stake/i });
await stakeButton.click();
```
**Validation:**
- Throws if amount is below minimum stake
- Throws if amount exceeds wallet balance
- Throws if tax rate is outside valid range (0-100)
**TypeScript Support:**
Type declarations are available in `env.d.ts`:
```typescript
interface Window {
__testHelpers?: {
fillStakeForm: (params: { amount: number; taxRate: number }) => Promise<void>;
};
}
```
**Security:**
Test helpers are only available when `import.meta.env.DEV === true` and are automatically stripped from production builds.
### E2E Tests
See `INTEGRATION_TEST_STATUS.md` in the repository root for complete testing documentation.

3
web-app/env.d.ts vendored
View file

@ -5,6 +5,9 @@ import type { EIP1193Provider } from 'viem';
declare global {
interface Window {
ethereum?: EIP1193Provider;
__testHelpers?: {
fillStakeForm: (params: { amount: number; taxRate: number }) => Promise<void>;
};
}
}

View file

@ -208,6 +208,37 @@ function setMaxAmount() {
}
const snatchSelection = useSnatchSelection(demo, taxRate);
// Test helper - only available in dev mode
if (import.meta.env.DEV) {
if (typeof window !== 'undefined') {
window.__testHelpers = {
fillStakeForm: async (params: { amount: number; taxRate: number }) => {
// Validate inputs
const minStakeNum = bigInt2Number(minStake.value, 18);
if (params.amount < minStakeNum) {
throw new Error(`Stake amount ${params.amount} is below minimum ${minStakeNum}`);
}
const maxStakeNum = maxStakeAmount.value;
if (params.amount > maxStakeNum) {
throw new Error(`Stake amount ${params.amount} exceeds balance ${maxStakeNum}`);
}
if (params.taxRate <= 0 || params.taxRate > 100) {
throw new Error(`Tax rate ${params.taxRate} must be between 0 and 100`);
}
// Fill the form
stake.stakingAmountNumber = params.amount;
taxRate.value = params.taxRate;
// Wait for reactive updates
await new Promise(resolve => setTimeout(resolve, 100));
},
};
}
}
</script>
<style lang="sass">