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:
parent
b1f40374cd
commit
8cd64e808f
5 changed files with 272 additions and 2 deletions
135
INTEGRATION_TEST_STATUS.md
Normal file
135
INTEGRATION_TEST_STATUS.md
Normal 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
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
3
web-app/env.d.ts
vendored
|
|
@ -5,6 +5,9 @@ import type { EIP1193Provider } from 'viem';
|
|||
declare global {
|
||||
interface Window {
|
||||
ethereum?: EIP1193Provider;
|
||||
__testHelpers?: {
|
||||
fillStakeForm: (params: { amount: number; taxRate: number }) => Promise<void>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue