From 8cd64e808f6c0f33453b471915892a1096db054d Mon Sep 17 00:00:00 2001 From: johba Date: Tue, 7 Oct 2025 15:06:38 +0200 Subject: [PATCH] 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 Reviewed-on: https://codeberg.org/johba/harb/pulls/66 --- INTEGRATION_TEST_STATUS.md | 135 +++++++++++++++++++++++++ tests/e2e/01-acquire-and-stake.spec.ts | 52 +++++++++- web-app/README.md | 53 ++++++++++ web-app/env.d.ts | 3 + web-app/src/components/StakeHolder.vue | 31 ++++++ 5 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 INTEGRATION_TEST_STATUS.md diff --git a/INTEGRATION_TEST_STATUS.md b/INTEGRATION_TEST_STATUS.md new file mode 100644 index 0000000..5d7e5be --- /dev/null +++ b/INTEGRATION_TEST_STATUS.md @@ -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 diff --git a/tests/e2e/01-acquire-and-stake.spec.ts b/tests/e2e/01-acquire-and-stake.spec.ts index b6ca254..ae0c72a 100644 --- a/tests/e2e/01-acquire-and-stake.spec.ts +++ b/tests/e2e/01-acquire-and-stake.spec.ts @@ -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(); } diff --git a/web-app/README.md b/web-app/README.md index 29c8ed3..62de929 100644 --- a/web-app/README.md +++ b/web-app/README.md @@ -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; + }; +} +``` + +**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. diff --git a/web-app/env.d.ts b/web-app/env.d.ts index 5a8efba..566ae19 100644 --- a/web-app/env.d.ts +++ b/web-app/env.d.ts @@ -5,6 +5,9 @@ import type { EIP1193Provider } from 'viem'; declare global { interface Window { ethereum?: EIP1193Provider; + __testHelpers?: { + fillStakeForm: (params: { amount: number; taxRate: number }) => Promise; + }; } } diff --git a/web-app/src/components/StakeHolder.vue b/web-app/src/components/StakeHolder.vue index 5b9ae41..0ca4da9 100644 --- a/web-app/src/components/StakeHolder.vue +++ b/web-app/src/components/StakeHolder.vue @@ -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)); + }, + }; + } +}