import { expect, 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, logCopyFeedback, logTokenomicsQuestion, recordPageVisit, recordAction, writeReport, attemptStake, resetChainState, } from './helpers'; // Sarah uses Anvil account #2 const ACCOUNT_PRIVATE_KEY = '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a'; const ACCOUNT_ADDRESS = new Wallet(ACCOUNT_PRIVATE_KEY).address.toLowerCase(); const STACK_CONFIG = getStackConfig(); const STACK_RPC_URL = STACK_CONFIG.rpcUrl; const STACK_WEBAPP_URL = STACK_CONFIG.webAppUrl; test.describe('Sarah Park - Cautious Yield Farmer', () => { test.beforeAll(async () => { await resetChainState(STACK_RPC_URL); await validateStackHealthy(STACK_CONFIG); }); test('Sarah researches thoroughly before committing capital', async ({ browser }) => { const report = createReport('Sarah Park'); const personaName = 'Sarah'; console.log(`[${personaName}] Starting test - Cautious yield farmer seeking sustainable returns...`); const context = await createWalletContext(browser, { privateKey: ACCOUNT_PRIVATE_KEY, 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 { // --- Landing Page (Reads Everything) --- let pageStart = Date.now(); await page.goto(`${STACK_WEBAPP_URL}/app/`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); await takeScreenshot(page, personaName, 'landing-page', report); logObservation(personaName, 'Reading landing page carefully before connecting wallet', report); logCopyFeedback(personaName, 'Landing page should explain "What is Harberger tax?" in simple terms', report); recordPageVisit('Landing', page.url(), pageStart, report); // --- Look for About/Docs FIRST --- logObservation(personaName, 'Looking for About, Docs, or Team page before doing anything else...', report); const hasAbout = await page.getByText(/about/i).first().isVisible().catch(() => false); const hasDocs = await page.getByText(/docs|documentation/i).first().isVisible().catch(() => false); const hasTeam = await page.getByText(/team/i).first().isVisible().catch(() => false); if (!hasAbout && !hasDocs && !hasTeam) { logCopyFeedback(personaName, 'MAJOR ISSUE: No About, Docs, or Team link visible. I need background info before trusting this.', report); logObservation(personaName, 'Feeling uncertain - no clear educational resources or team transparency', report); } await takeScreenshot(page, personaName, 'looking-for-info', report); // --- Check for Audit Badge --- const auditVisible = await page.getByText(/audit/i).isVisible().catch(() => false); if (!auditVisible) { logCopyFeedback(personaName, 'No audit badge visible - this is a dealbreaker for me normally, but will test anyway', report); logTokenomicsQuestion(personaName, 'Has this been audited by Certik, Trail of Bits, or similar?', report); } // --- Connect Wallet (Hesitantly) --- logObservation(personaName, 'Deciding to connect wallet after reading available info...', report); pageStart = Date.now(); await connectWallet(page); await takeScreenshot(page, personaName, 'wallet-connected', report); recordAction('Connect wallet', true, undefined, report); logObservation(personaName, 'Wallet connected. Now checking the staking interface details.', report); // --- Navigate to Stake Page to Learn --- pageStart = Date.now(); await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`); await page.waitForTimeout(3_000); recordPageVisit('Stake (research)', page.url(), pageStart, report); await takeScreenshot(page, personaName, 'stake-page-reading', report); logObservation(personaName, 'Reading staking dashboard carefully - what are these tax rates about?', report); logCopyFeedback(personaName, 'The info icon next to "Staking Dashboard" helps, but needs more detail on risks', report); logTokenomicsQuestion(personaName, 'If I stake at 10% tax, what\'s my expected APY after taxes?', report); logTokenomicsQuestion(personaName, 'What happens if I get snatched? Do I lose my principal or just my position?', report); // --- Check Statistics Section --- const statsSection = page.locator('.statistics-wrapper'); const statsVisible = await statsSection.isVisible().catch(() => false); if (statsVisible) { await takeScreenshot(page, personaName, 'statistics-analysis', report); logObservation(personaName, 'Examining statistics - average tax rate, claimed slots, inflation rate', report); logTokenomicsQuestion(personaName, 'How does the 7-day inflation compare to my expected staking returns?', report); } else { logCopyFeedback(personaName, 'Would be helpful to see protocol statistics and historical data', report); } // --- Mint ETH --- pageStart = Date.now(); await page.goto(`${STACK_WEBAPP_URL}/app/#/cheats`); await page.waitForTimeout(1_000); recordPageVisit('Cheats', page.url(), pageStart, report); logObservation(personaName, 'Using test environment to simulate before committing real funds', report); await mintEth(page, STACK_RPC_URL, ACCOUNT_ADDRESS, '20'); recordAction('Mint 20 ETH', true, undefined, report); // --- Small Test Purchase --- logObservation(personaName, 'Starting with a small test purchase to understand the process', report); await buyKrk(page, '0.05'); recordAction('Buy KRK with 0.05 ETH (test)', true, undefined, report); await takeScreenshot(page, personaName, 'test-purchase-complete', report); logObservation(personaName, 'Test purchase successful. Now buying more for actual staking.', report); await page.waitForTimeout(2_000); // --- Buy enough for staking (split to reduce slippage) --- await buyKrk(page, '3.0'); recordAction('Buy KRK with 3.0 ETH total', true, undefined, report); logObservation(personaName, 'Bought more KRK. Now ready to stake.', report); await page.waitForTimeout(2_000); // --- Navigate Back to Stake --- pageStart = Date.now(); await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`); await page.waitForTimeout(2_000); recordPageVisit('Stake (attempt)', page.url(), pageStart, report); await takeScreenshot(page, personaName, 'stake-form-before-fill', report); logObservation(personaName, 'Examining the stake form - trying to understand tax rate implications', report); logCopyFeedback(personaName, 'Tax rate dropdown needs explanation: "What tax rate should I choose?"', report); logCopyFeedback(personaName, 'Would love a calculator: "Stake X at Y% tax = Z estimated APY"', report); // --- Conservative Test Stake (High Tax for Safety) --- logObservation(personaName, 'Choosing 15% tax rate to minimize snatch risk - prioritizing safety over yield', report); logTokenomicsQuestion(personaName, 'Is 15% tax high enough to prevent snatching? What\'s the meta?', report); try { await attemptStake(page, '50', '15', personaName, report); await takeScreenshot(page, personaName, 'conservative-stake-success', report); logObservation(personaName, 'Stake successful! Now monitoring to see if position stays secure.', report); recordAction('Stake 50 KRK at 15% tax (conservative)', true, undefined, report); } catch (error: any) { logObservation(personaName, `Stake failed: ${error.message}. This is confusing and frustrating.`, report); logCopyFeedback(personaName, 'Error messages need to be clearer and suggest solutions', report); await takeScreenshot(page, personaName, 'stake-error', report); } await page.waitForTimeout(3_000); // --- Check Active Positions --- await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`); await page.waitForTimeout(2_000); await takeScreenshot(page, personaName, 'checking-my-position', report); const activePositions = page.locator('.active-positions-wrapper'); const myPositionVisible = await activePositions.isVisible().catch(() => false); if (myPositionVisible) { logObservation(personaName, 'Can see my active position. Would want notifications when something changes.', report); logCopyFeedback(personaName, 'Need mobile notifications or email alerts for position activity (snatch attempts, tax due)', report); } else { logObservation(personaName, 'Can\'t see my position clearly - where is it? Confusing UX.', report); logCopyFeedback(personaName, '"My Positions" section should be more prominent', report); } // --- Compare to Mental Model (Aave) --- logObservation(personaName, 'Comparing this to Aave in my head - Aave is simpler but boring...', report); logTokenomicsQuestion(personaName, 'Aave gives me 8% on USDC with zero snatch risk. Why should I use this instead?', report); logCopyFeedback(personaName, 'Needs a "Why Kraiken?" section comparing to traditional staking/lending', report); // --- Final Thoughts --- await page.waitForTimeout(2_000); await takeScreenshot(page, personaName, 'final-review', report); report.overallSentiment = 'Interested but need more information before committing real funds. The Harberger tax mechanism is intriguing but confusing - I don\'t fully understand how to optimize my tax rate or what happens if I get snatched. UI is clean but lacks educational content for newcomers. Missing: audit badge, return calculator, risk disclosures, comparison to alternatives, mobile notifications. Would need to monitor my test stake for 1-2 weeks before scaling up. Compared to Aave (8% risk-free), this needs to offer 10-15% to justify the complexity and snatch risk. Verdict: Promising but not ready for my main capital yet.'; logObservation(personaName, report.overallSentiment, report); } finally { writeReport('sarah-park', report); await context.close(); } }); });