179 lines
7.2 KiB
TypeScript
179 lines
7.2 KiB
TypeScript
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
|
import { test } from '@playwright/test';
|
|
import { Wallet } from 'ethers';
|
|
import { createWalletContext } from '../../setup/wallet-provider';
|
|
import { getStackConfig, validateStackHealthy } from '../../setup/stack';
|
|
import {
|
|
createReport,
|
|
connectWallet,
|
|
mintEth,
|
|
buyKrk,
|
|
takeScreenshot,
|
|
logObservation,
|
|
recordAction,
|
|
writeReport,
|
|
attemptStake,
|
|
resetChainState,
|
|
} from './helpers';
|
|
|
|
const STACK_CONFIG = getStackConfig();
|
|
const STACK_RPC_URL = STACK_CONFIG.rpcUrl;
|
|
const STACK_WEBAPP_URL = STACK_CONFIG.webAppUrl;
|
|
|
|
// Persona accounts (Anvil #1-5)
|
|
// Note: Pool has limited liquidity - 0.05 ETH buy yields ~3.99 KRK
|
|
// Staking 3 KRK leaves enough for upfront tax payment
|
|
const PERSONAS = [
|
|
{
|
|
name: 'Marcus Flash Chen',
|
|
shortName: 'Marcus',
|
|
privateKey: '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d',
|
|
ethToMint: '10',
|
|
ethToSpend: '0.05',
|
|
stakeAmount: '3', // Conservative amount that fits within ~3.99 KRK balance
|
|
taxRate: '2',
|
|
},
|
|
{
|
|
name: 'Sarah Park',
|
|
shortName: 'Sarah',
|
|
privateKey: '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a',
|
|
ethToMint: '10',
|
|
ethToSpend: '0.05',
|
|
stakeAmount: '3',
|
|
taxRate: '2',
|
|
},
|
|
{
|
|
name: 'Tyler Brooks',
|
|
shortName: 'Tyler',
|
|
privateKey: '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6',
|
|
ethToMint: '10',
|
|
ethToSpend: '0.05',
|
|
stakeAmount: '3',
|
|
taxRate: '2',
|
|
},
|
|
{
|
|
name: 'Priya Sharma',
|
|
shortName: 'Priya',
|
|
privateKey: '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a',
|
|
ethToMint: '10',
|
|
ethToSpend: '0.05',
|
|
stakeAmount: '3',
|
|
taxRate: '2',
|
|
},
|
|
{
|
|
name: 'Alex Rivera',
|
|
shortName: 'Alex',
|
|
privateKey: '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba',
|
|
ethToMint: '10',
|
|
ethToSpend: '0.05',
|
|
stakeAmount: '3',
|
|
taxRate: '2',
|
|
},
|
|
];
|
|
|
|
test.describe('All Personas - Fresh Pool State', () => {
|
|
for (const persona of PERSONAS) {
|
|
test(`${persona.name} completes full journey`, async ({ browser }) => {
|
|
// Reset chain state before THIS persona
|
|
// First call takes initial snapshot, subsequent calls revert to it
|
|
console.log(`\n[ORCHESTRATOR] Resetting chain state for ${persona.name}...`);
|
|
await resetChainState(STACK_RPC_URL);
|
|
|
|
// Validate stack health once at start
|
|
if (persona === PERSONAS[0]) {
|
|
console.log('[ORCHESTRATOR] Validating stack health...');
|
|
await validateStackHealthy(STACK_CONFIG);
|
|
}
|
|
|
|
const report = createReport(persona.name);
|
|
const address = new Wallet(persona.privateKey).address.toLowerCase();
|
|
|
|
console.log(`[${persona.shortName}] Starting test - fresh pool state`);
|
|
|
|
const context = await createWalletContext(browser, {
|
|
privateKey: persona.privateKey,
|
|
rpcUrl: STACK_RPC_URL,
|
|
});
|
|
|
|
const page = await context.newPage();
|
|
page.on('console', msg => console.log(`[BROWSER] ${msg.type()}: ${msg.text()}`));
|
|
page.on('pageerror', error => console.log(`[BROWSER ERROR] ${error.message}`));
|
|
|
|
try {
|
|
// 1. Navigate to app
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/`, { waitUntil: 'domcontentloaded' });
|
|
await page.waitForTimeout(2_000);
|
|
await takeScreenshot(page, persona.shortName, '1-landing', report);
|
|
logObservation(persona.shortName, 'Arrived at app', report);
|
|
|
|
// 2. Connect wallet
|
|
await connectWallet(page);
|
|
await takeScreenshot(page, persona.shortName, '2-wallet-connected', report);
|
|
recordAction('Connect wallet', true, undefined, report);
|
|
console.log(`[${persona.shortName}] ✅ Wallet connected`);
|
|
|
|
// 3. Mint ETH
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/cheats`);
|
|
await page.waitForTimeout(1_000);
|
|
await mintEth(page, STACK_RPC_URL, address, persona.ethToMint);
|
|
await takeScreenshot(page, persona.shortName, '3-eth-minted', report);
|
|
recordAction(`Mint ${persona.ethToMint} ETH`, true, undefined, report);
|
|
console.log(`[${persona.shortName}] ✅ Minted ${persona.ethToMint} ETH`);
|
|
|
|
// 4. Buy KRK
|
|
await buyKrk(page, persona.ethToSpend);
|
|
await takeScreenshot(page, persona.shortName, '4-krk-purchased', report);
|
|
recordAction(`Buy KRK with ${persona.ethToSpend} ETH`, true, undefined, report);
|
|
console.log(`[${persona.shortName}] ✅ Bought KRK with ${persona.ethToSpend} ETH`);
|
|
|
|
await page.waitForTimeout(2_000);
|
|
|
|
// 5. Navigate to stake page
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/stake`);
|
|
await page.waitForTimeout(3_000);
|
|
await takeScreenshot(page, persona.shortName, '5-stake-page', report);
|
|
|
|
// 6. Stake KRK with known working amount
|
|
const stakeAmount = persona.stakeAmount;
|
|
console.log(`[${persona.shortName}] Attempting to stake ${stakeAmount} KRK at ${persona.taxRate}% tax...`);
|
|
await attemptStake(page, stakeAmount, persona.taxRate, persona.shortName, report);
|
|
await takeScreenshot(page, persona.shortName, '6-stake-complete', report);
|
|
console.log(`[${persona.shortName}] ✅ Staked ${stakeAmount} KRK at ${persona.taxRate}% tax`);
|
|
|
|
await page.waitForTimeout(2_000);
|
|
|
|
// 7. Verify position exists
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/stake`);
|
|
await page.waitForTimeout(2_000);
|
|
|
|
const myPositionsSection = page.locator('.my-positions-list, [class*="my-position"], [class*="MyPosition"]').first();
|
|
const hasPosition = await myPositionsSection.isVisible({ timeout: 5_000 }).catch(() => false);
|
|
|
|
if (hasPosition) {
|
|
await takeScreenshot(page, persona.shortName, '7-position-verified', report);
|
|
recordAction('Verify staked position exists', true, undefined, report);
|
|
console.log(`[${persona.shortName}] ✅ Position verified in UI`);
|
|
} else {
|
|
await takeScreenshot(page, persona.shortName, '7-position-check-failed', report);
|
|
recordAction('Verify staked position exists', false, 'Position not visible in UI', report);
|
|
console.log(`[${persona.shortName}] ⚠️ Position not visible in UI - may still exist on-chain`);
|
|
}
|
|
|
|
report.overallSentiment = `${persona.name} completed full journey: connected wallet → bought KRK → staked → ${hasPosition ? 'verified position' : 'stake attempted but position not visible'}`;
|
|
logObservation(persona.shortName, report.overallSentiment, report);
|
|
|
|
console.log(`[${persona.shortName}] ✅ FULL JOURNEY COMPLETE`);
|
|
|
|
} catch (error: any) {
|
|
const errorMsg = error.message || String(error);
|
|
console.error(`[${persona.shortName}] ❌ Test failed: ${errorMsg}`);
|
|
await takeScreenshot(page, persona.shortName, 'error-state', report).catch(() => {});
|
|
report.overallSentiment = `Test failed: ${errorMsg}`;
|
|
throw error;
|
|
} finally {
|
|
writeReport(persona.name.toLowerCase().replace(/\s+/g, '-'), report);
|
|
await context.close();
|
|
}
|
|
});
|
|
}
|
|
});
|