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(); } }); } });