import { expect, test } from '@playwright/test'; import { Wallet } from 'ethers'; import { createWalletContext } from '../../setup/wallet-provider'; import { getStackConfig, validateStackHealthy } from '../../setup/stack'; import { createPersonaFeedback, addFeedbackStep, writePersonaFeedback, mintEth, buyKrk, resetChainState, connectWallet, type PersonaFeedback, } from './helpers'; import { mkdirSync } from 'fs'; import { join } from 'path'; const STACK_CONFIG = getStackConfig(); const STACK_RPC_URL = STACK_CONFIG.rpcUrl; const STACK_WEBAPP_URL = STACK_CONFIG.webAppUrl; const STAKE_PAGE_URL = 'http://localhost:5173/stake'; // Different accounts for different personas const MARCUS_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Anvil #0 const SARAH_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; // Anvil #1 const PRIYA_KEY = '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a'; // Anvil #2 test.describe('Test B: Staker Journey', () => { test.beforeAll(async () => { await resetChainState(STACK_RPC_URL); await validateStackHealthy(STACK_CONFIG); }); test.describe.serial('Marcus - Degen/MEV ("where\'s the edge?")', () => { let feedback: PersonaFeedback; const accountKey = MARCUS_KEY; const accountAddr = new Wallet(accountKey).address; test.beforeAll(() => { feedback = createPersonaFeedback('marcus', 'B', 'staker'); }); test('Marcus pre-funds wallet with KRK', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(`${STACK_WEBAPP_URL}/app/`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2_000); observations.push('Setting up: acquiring KRK for staking tests...'); await mintEth(page, STACK_RPC_URL, accountAddr, '50'); await buyKrk(page, '10', STACK_RPC_URL, accountKey); observations.push('✓ Wallet funded with KRK'); addFeedbackStep(feedback, 'setup', observations); } finally { await context.close(); } }); test('Marcus analyzes staking interface for MEV opportunities', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); page.on('console', msg => console.log(`[BROWSER] ${msg.type()}: ${msg.text()}`)); const observations: string[] = []; try { await page.goto(STAKE_PAGE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); observations.push('Scanning for arbitrage angles, tax rate gaps, snatching opportunities...'); // Check if leverage framing is clear const hasLeverageInfo = await page.getByText(/leverage|multiplier|amplif/i).isVisible().catch(() => false); if (hasLeverageInfo) { observations.push('✓ Leverage mechanics visible - can assess risk/reward multiplier'); } else { observations.push('⚠ Leverage framing unclear - hard to calculate edge'); feedback.overall.friction.push('Leverage mechanics not clearly explained'); } // Tax rate tooltip const taxSelect = page.getByRole('combobox', { name: /tax/i }).first(); const taxVisible = await taxSelect.isVisible({ timeout: 5_000 }).catch(() => false); if (taxVisible) { observations.push('✓ Tax rate selector found'); // Look for tooltip or info icon const infoIcon = page.locator('svg[data-icon="circle-info"], svg[class*="info"]').first(); const hasTooltip = await infoIcon.isVisible().catch(() => false); if (hasTooltip) { observations.push('✓ Tax rate has tooltip - explains tradeoff'); await infoIcon.hover(); await page.waitForTimeout(1_000); const tooltipText = await page.locator('[role="tooltip"], .tooltip').textContent().catch(() => ''); if (tooltipText.toLowerCase().includes('snatch') || tooltipText.toLowerCase().includes('harder')) { observations.push('✓ Tooltip explains "higher tax = harder to snatch" - good framing'); } else { observations.push('⚠ Tooltip doesn\'t clearly explain snatch resistance'); } } else { observations.push('✗ No tooltip on tax rate - mechanics unclear'); feedback.overall.friction.push('Tax rate selection lacks explanation'); } } else { observations.push('✗ Tax rate selector not found'); } // Protocol stats visibility const hasStats = await page.locator('text=/TVL|total staked|positions/i').isVisible().catch(() => false); if (hasStats) { observations.push('✓ Protocol stats visible - can gauge competition and pool depth'); } else { observations.push('⚠ No protocol-wide stats - harder to assess meta'); } // Contract addresses for verification const hasContracts = await page.locator('text=/0x[a-fA-F0-9]{40}|contract/i').isVisible().catch(() => false); if (hasContracts) { observations.push('✓ Contract addresses visible - can verify on-chain before committing'); } else { observations.push('✗ No contract addresses shown - can\'t independently verify'); feedback.overall.friction.push('No contract addresses for verification'); } // Look for open positions to snatch const positionsList = await page.locator('[class*="position"], [class*="stake-card"]').count(); if (positionsList > 0) { observations.push(`✓ Can see ${positionsList} existing positions - potential snatch targets`); } else { observations.push('⚠ Can\'t see other stakers\' positions - no snatching meta visible'); } // Screenshot const screenshotDir = join('test-results', 'usertest', 'marcus-b'); mkdirSync(screenshotDir, { recursive: true }); const screenshotPath = join(screenshotDir, `stake-interface-analysis-${Date.now()}.png`); await page.screenshot({ path: screenshotPath, fullPage: true }); addFeedbackStep(feedback, 'stake-interface-analysis', observations, screenshotPath); } finally { await context.close(); } }); test('Marcus executes aggressive low-tax stake', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(STAKE_PAGE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); // Connect wallet await connectWallet(page); await page.waitForTimeout(2_000); observations.push('Going for lowest tax rate - maximum upside, I\'ll just monitor for snatches'); // Fill stake form const stakeAmountInput = page.getByLabel(/staking amount/i).or(page.locator('input[type="number"]').first()); await stakeAmountInput.waitFor({ state: 'visible', timeout: 10_000 }); await stakeAmountInput.fill('100'); await page.waitForTimeout(500); // Select lowest tax rate (index 0 or value "5") const taxSelect = page.getByRole('combobox', { name: /tax/i }).first(); await taxSelect.selectOption({ index: 0 }); // Pick first option (lowest) await page.waitForTimeout(500); const selectedTax = await taxSelect.inputValue(); observations.push(`Selected tax rate: ${selectedTax}% (lowest available)`); // Screenshot before stake const screenshotDir = join('test-results', 'usertest', 'marcus-b'); const preStakePath = join(screenshotDir, `pre-stake-${Date.now()}.png`); await page.screenshot({ path: preStakePath, fullPage: true }); // Execute stake const stakeButton = page.getByRole('button', { name: /^(stake|snatch)/i }).first(); const buttonText = await stakeButton.textContent(); if (buttonText?.toLowerCase().includes('snatch')) { observations.push('✓ Button shows "Snatch and Stake" - clear that I\'m taking someone\'s position'); } else { observations.push('Button shows "Stake" - am I creating new position or snatching?'); } try { await stakeButton.click(); await page.waitForTimeout(1_000); // Wait for transaction const txInProgress = await page.getByRole('button', { name: /sign|waiting|confirm/i }).isVisible({ timeout: 3_000 }).catch(() => false); if (txInProgress) { await page.waitForTimeout(5_000); } observations.push('✓ Stake transaction executed'); } catch (error: any) { observations.push(`✗ Stake failed: ${error.message}`); feedback.overall.friction.push('Could not complete stake transaction'); } await page.waitForTimeout(3_000); // Screenshot after stake const postStakePath = join(screenshotDir, `post-stake-${Date.now()}.png`); await page.screenshot({ path: postStakePath, fullPage: true }); addFeedbackStep(feedback, 'execute-stake', observations, postStakePath); } finally { await context.close(); } }); test('Marcus checks position P&L and monitoring tools', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(STAKE_PAGE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); await connectWallet(page); await page.waitForTimeout(2_000); observations.push('Checking my position - where\'s the P&L?'); // Look for position card/details const hasPositionCard = await page.locator('[class*="position"], [class*="your-stake"]').isVisible().catch(() => false); if (hasPositionCard) { observations.push('✓ Position card visible'); } else { observations.push('⚠ Can\'t find my position display'); } // P&L visibility const hasPnL = await page.locator('text=/profit|loss|P&L|gain|\\+\\$|\\-\\$/i').isVisible().catch(() => false); if (hasPnL) { observations.push('✓ P&L displayed - can see if I\'m winning'); } else { observations.push('✗ No P&L shown - can\'t tell if this is profitable'); feedback.overall.friction.push('Position P&L not visible'); } // Tax accumulation / time held const hasTimeMetrics = await page.locator('text=/time|duration|days|hours/i').isVisible().catch(() => false); if (hasTimeMetrics) { observations.push('✓ Time-based metrics shown - can calculate tax accumulation'); } else { observations.push('⚠ No time held display - harder to estimate when I\'ll be profitable'); } // Snatch risk indicator const hasSnatchRisk = await page.locator('text=/snatch risk|vulnerable|safe/i').isVisible().catch(() => false); if (hasSnatchRisk) { observations.push('✓ Snatch risk indicator - helps me decide when to exit'); } else { observations.push('⚠ No snatch risk metric - flying blind on when I\'ll get snatched'); } // Next steps clarity const hasActions = await page.getByRole('button', { name: /claim|exit|increase/i }).isVisible().catch(() => false); if (hasActions) { observations.push('✓ Clear action buttons - know what I can do next'); } else { observations.push('⚠ Not clear what actions I can take with this position'); } // Screenshot const screenshotDir = join('test-results', 'usertest', 'marcus-b'); const screenshotPath = join(screenshotDir, `position-monitoring-${Date.now()}.png`); await page.screenshot({ path: screenshotPath, fullPage: true }); addFeedbackStep(feedback, 'position-monitoring', observations, screenshotPath); // Marcus's verdict const hasEdge = observations.some(o => o.includes('✓ P&L displayed')); const canMonitor = hasPnL || hasSnatchRisk; feedback.overall.wouldStake = true; // Marcus is a degen, he'll stake anyway feedback.overall.wouldReturn = canMonitor; observations.push(`Marcus verdict: ${hasEdge ? 'Clear edge, will monitor actively' : 'Can\'t calculate edge properly'}`); observations.push(`Would return: ${canMonitor ? 'Yes, need to watch for snatches' : 'Maybe, but tooling is weak'}`); } finally { await context.close(); writePersonaFeedback(feedback); } }); }); test.describe.serial('Sarah - Yield Farmer ("what are the risks?")', () => { let feedback: PersonaFeedback; const accountKey = SARAH_KEY; const accountAddr = new Wallet(accountKey).address; test.beforeAll(() => { feedback = createPersonaFeedback('sarah', 'B', 'staker'); }); test('Sarah pre-funds wallet with KRK', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(`${STACK_WEBAPP_URL}/app/`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2_000); observations.push('Funding wallet for conservative staking test...'); await mintEth(page, STACK_RPC_URL, accountAddr, '50'); await buyKrk(page, '10', STACK_RPC_URL, accountKey); observations.push('✓ Wallet funded'); addFeedbackStep(feedback, 'setup', observations); } finally { await context.close(); } }); test('Sarah evaluates risk disclosure and staking mechanics', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(STAKE_PAGE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); observations.push('Looking for risk disclosures, worst-case scenarios, and safety features...'); // Risk warnings const hasRiskWarning = await page.getByText(/risk|warning|caution|loss/i).isVisible().catch(() => false); if (hasRiskWarning) { observations.push('✓ Risk warning present - shows responsible disclosure'); } else { observations.push('✗ No visible risk warnings - concerning for risk management'); feedback.overall.friction.push('No risk disclosure on staking interface'); } // Tax rate explanation with safety framing const taxTooltipFound = await page.locator('svg[data-icon="circle-info"], svg[class*="info"]').first().isVisible().catch(() => false); if (taxTooltipFound) { observations.push('✓ Tax rate info icon found'); await page.locator('svg[data-icon="circle-info"], svg[class*="info"]').first().hover(); await page.waitForTimeout(1_000); const tooltipText = await page.locator('[role="tooltip"], .tooltip').textContent().catch(() => ''); if (tooltipText.toLowerCase().includes('reduce') || tooltipText.toLowerCase().includes('return')) { observations.push('✓ Tooltip explains tax impact on returns - good risk education'); } else { observations.push('⚠ Tooltip doesn\'t clearly explain how tax affects my returns'); } } else { observations.push('✗ No tooltip on tax rate - critical mechanism unexplained'); feedback.overall.friction.push('Tax rate mechanism not explained'); } // Protocol stats for safety assessment const hasProtocolStats = await page.locator('text=/TVL|health|utilization/i').isVisible().catch(() => false); if (hasProtocolStats) { observations.push('✓ Protocol stats visible - can assess overall protocol health'); } else { observations.push('⚠ No protocol health stats - hard to assess systemic risk'); } // APY/yield projections const hasAPY = await page.locator('text=/APY|yield|return|%/i').isVisible().catch(() => false); if (hasAPY) { observations.push('✓ Yield projections visible - can compare to other protocols'); } else { observations.push('⚠ No clear APY display - can\'t evaluate if returns justify risk'); feedback.overall.friction.push('No yield projections shown'); } // Smart contract verification const hasContractInfo = await page.locator('text=/0x[a-fA-F0-9]{40}|verified|audit/i').isVisible().catch(() => false); if (hasContractInfo) { observations.push('✓ Contract info or audit badge visible - can verify safety'); } else { observations.push('⚠ No contract verification info - can\'t independently audit'); } // Screenshot const screenshotDir = join('test-results', 'usertest', 'sarah-b'); mkdirSync(screenshotDir, { recursive: true }); const screenshotPath = join(screenshotDir, `risk-assessment-${Date.now()}.png`); await page.screenshot({ path: screenshotPath, fullPage: true }); addFeedbackStep(feedback, 'risk-assessment', observations, screenshotPath); } finally { await context.close(); } }); test('Sarah executes conservative stake with medium tax rate', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(STAKE_PAGE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); await connectWallet(page); await page.waitForTimeout(2_000); observations.push('Choosing medium tax rate - balance between returns and safety'); // Fill stake form const stakeAmountInput = page.getByLabel(/staking amount/i).or(page.locator('input[type="number"]').first()); await stakeAmountInput.waitFor({ state: 'visible', timeout: 10_000 }); await stakeAmountInput.fill('50'); // Conservative amount await page.waitForTimeout(500); // Select medium tax rate (index 2-3, or 10-15%) const taxSelect = page.getByRole('combobox', { name: /tax/i }).first(); const options = await taxSelect.locator('option').count(); const midIndex = Math.floor(options / 2); await taxSelect.selectOption({ index: midIndex }); await page.waitForTimeout(500); const selectedTax = await taxSelect.inputValue(); observations.push(`Selected tax rate: ${selectedTax}% (medium - balanced risk/reward)`); // Screenshot before stake const screenshotDir = join('test-results', 'usertest', 'sarah-b'); const preStakePath = join(screenshotDir, `pre-stake-${Date.now()}.png`); await page.screenshot({ path: preStakePath, fullPage: true }); // Execute stake const stakeButton = page.getByRole('button', { name: /^(stake|snatch)/i }).first(); try { await stakeButton.click(); await page.waitForTimeout(1_000); const txInProgress = await page.getByRole('button', { name: /sign|waiting|confirm/i }).isVisible({ timeout: 3_000 }).catch(() => false); if (txInProgress) { await page.waitForTimeout(5_000); } observations.push('✓ Conservative stake executed'); } catch (error: any) { observations.push(`✗ Stake failed: ${error.message}`); feedback.overall.friction.push('Stake transaction failed'); } await page.waitForTimeout(3_000); const postStakePath = join(screenshotDir, `post-stake-${Date.now()}.png`); await page.screenshot({ path: postStakePath, fullPage: true }); addFeedbackStep(feedback, 'execute-stake', observations, postStakePath); } finally { await context.close(); } }); test('Sarah evaluates post-stake clarity and monitoring', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(STAKE_PAGE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); await connectWallet(page); await page.waitForTimeout(2_000); observations.push('Evaluating: Can I clearly see my position, returns, and risks?'); // Position visibility const hasPosition = await page.locator('[class*="position"], [class*="stake"]').isVisible().catch(() => false); if (hasPosition) { observations.push('✓ Position card visible'); } else { observations.push('⚠ Position not clearly displayed'); } // Expected returns const hasReturns = await page.locator('text=/daily|weekly|APY|earning/i').isVisible().catch(() => false); if (hasReturns) { observations.push('✓ Return projections visible - know what to expect'); } else { observations.push('⚠ No clear return projections - don\'t know expected earnings'); feedback.overall.friction.push('No return projections for active positions'); } // What happens next const hasGuidance = await page.getByText(/next|monitor|check back|claim/i).isVisible().catch(() => false); if (hasGuidance) { observations.push('✓ Guidance on next steps - know when to check back'); } else { observations.push('⚠ No guidance on what happens next - set and forget?'); } // Exit options const hasExit = await page.getByRole('button', { name: /unstake|exit|withdraw/i }).isVisible().catch(() => false); if (hasExit) { observations.push('✓ Exit option visible - not locked in permanently'); } else { observations.push('⚠ No clear exit option - am I stuck until snatched?'); feedback.overall.friction.push('Exit mechanism not clear'); } // Screenshot const screenshotDir = join('test-results', 'usertest', 'sarah-b'); const screenshotPath = join(screenshotDir, `post-stake-clarity-${Date.now()}.png`); await page.screenshot({ path: screenshotPath, fullPage: true }); addFeedbackStep(feedback, 'post-stake-clarity', observations, screenshotPath); // Sarah's verdict const risksExplained = observations.filter(o => o.includes('✓')).length >= 3; const canMonitor = hasReturns || hasPosition; feedback.overall.wouldStake = risksExplained; feedback.overall.wouldReturn = canMonitor; observations.push(`Sarah verdict: ${risksExplained ? 'Acceptable risk profile' : 'Too many unknowns, won\'t stake'}`); observations.push(`Would return: ${canMonitor ? 'Yes, to monitor position' : 'Unclear monitoring requirements'}`); } finally { await context.close(); writePersonaFeedback(feedback); } }); }); test.describe.serial('Priya - Institutional ("show me the docs")', () => { let feedback: PersonaFeedback; const accountKey = PRIYA_KEY; const accountAddr = new Wallet(accountKey).address; test.beforeAll(() => { feedback = createPersonaFeedback('priya', 'B', 'staker'); }); test('Priya pre-funds wallet with KRK', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(`${STACK_WEBAPP_URL}/app/`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2_000); observations.push('Preparing test wallet...'); await mintEth(page, STACK_RPC_URL, accountAddr, '50'); await buyKrk(page, '10', STACK_RPC_URL, accountKey); observations.push('✓ Wallet funded'); addFeedbackStep(feedback, 'setup', observations); } finally { await context.close(); } }); test('Priya audits documentation and contract transparency', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(STAKE_PAGE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); observations.push('Looking for: docs, contract addresses, audit reports, technical specs...'); // Documentation link const hasDocsLink = await page.locator('a[href*="docs"], a[href*="documentation"]').or(page.getByText(/documentation|whitepaper|docs/i)).isVisible().catch(() => false); if (hasDocsLink) { observations.push('✓ Documentation link visible - can review technical details'); } else { observations.push('✗ No documentation link - cannot perform due diligence'); feedback.overall.friction.push('No technical documentation accessible'); } // Contract addresses with copy button const contractAddresses = await page.locator('text=/0x[a-fA-F0-9]{40}/').count(); if (contractAddresses > 0) { observations.push(`✓ Found ${contractAddresses} contract address(es) - can verify on Etherscan`); const hasCopyButton = await page.locator('button[title*="copy"], button[aria-label*="copy"]').isVisible().catch(() => false); if (hasCopyButton) { observations.push('✓ Copy button for addresses - good UX for verification'); } else { observations.push('⚠ No copy button - minor friction for address verification'); } } else { observations.push('✗ No contract addresses visible - cannot verify on-chain'); feedback.overall.friction.push('Contract addresses not displayed'); } // Audit badge or report const hasAudit = await page.locator('a[href*="audit"]').or(page.getByText(/audited|security audit/i)).isVisible().catch(() => false); if (hasAudit) { observations.push('✓ Audit report accessible - critical for institutional review'); } else { observations.push('✗ No audit report linked - major blocker for institutional capital'); feedback.overall.friction.push('No audit report accessible from UI'); } // Protocol parameters visibility const hasParams = await page.locator('text=/parameter|config|setting/i').isVisible().catch(() => false); if (hasParams) { observations.push('✓ Protocol parameters visible - can assess mechanism design'); } else { observations.push('⚠ Protocol parameters not displayed - harder to model behavior'); } // GitHub or source code link const hasGitHub = await page.locator('a[href*="github"]').isVisible().catch(() => false); if (hasGitHub) { observations.push('✓ GitHub link present - can review source code'); } else { observations.push('⚠ No source code link - cannot independently verify implementation'); } // Screenshot const screenshotDir = join('test-results', 'usertest', 'priya-b'); mkdirSync(screenshotDir, { recursive: true }); const screenshotPath = join(screenshotDir, `documentation-audit-${Date.now()}.png`); await page.screenshot({ path: screenshotPath, fullPage: true }); addFeedbackStep(feedback, 'documentation-audit', observations, screenshotPath); } finally { await context.close(); } }); test('Priya evaluates UI professionalism and data quality', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(STAKE_PAGE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); await connectWallet(page); await page.waitForTimeout(2_000); observations.push('Evaluating UI quality: precision, accuracy, professionalism...'); // Numeric precision const numbers = await page.locator('text=/\\d+\\.\\d{2,}/').count(); if (numbers > 2) { observations.push(`✓ Found ${numbers} precise numbers - shows data quality`); } else { observations.push('⚠ Limited numeric precision - data may be rounded/imprecise'); } // Real-time data indicators const hasLiveData = await page.locator('text=/live|real-time|updated/i').isVisible().catch(() => false); if (hasLiveData) { observations.push('✓ Real-time data indicators - shows active monitoring'); } else { observations.push('⚠ No indication if data is live or stale'); } // Error states and edge cases observations.push('Testing edge cases: trying to stake 0...'); const stakeInput = page.getByLabel(/staking amount/i).or(page.locator('input[type="number"]').first()); await stakeInput.fill('0'); await page.waitForTimeout(500); const hasValidation = await page.locator('text=/invalid|minimum|required/i').isVisible().catch(() => false); if (hasValidation) { observations.push('✓ Input validation present - handles edge cases gracefully'); } else { observations.push('⚠ No visible validation for invalid inputs'); } // Clear labels and units const hasUnits = await page.locator('text=/KRK|ETH|%|USD/i').count(); if (hasUnits >= 3) { observations.push('✓ Clear units on all values - professional data presentation'); } else { observations.push('⚠ Some values missing units - could cause confusion'); } // Screenshot const screenshotDir = join('test-results', 'usertest', 'priya-b'); const screenshotPath = join(screenshotDir, `ui-quality-${Date.now()}.png`); await page.screenshot({ path: screenshotPath, fullPage: true }); addFeedbackStep(feedback, 'ui-quality', observations, screenshotPath); } finally { await context.close(); } }); test('Priya performs test stake and evaluates reporting', async ({ browser }) => { const context = await createWalletContext(browser, { privateKey: accountKey, rpcUrl: STACK_RPC_URL, }); const page = await context.newPage(); const observations: string[] = []; try { await page.goto(STAKE_PAGE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(3_000); await connectWallet(page); await page.waitForTimeout(2_000); observations.push('Executing small test stake to evaluate position reporting...'); // Fill form const stakeInput = page.getByLabel(/staking amount/i).or(page.locator('input[type="number"]').first()); await stakeInput.fill('25'); await page.waitForTimeout(500); const taxSelect = page.getByRole('combobox', { name: /tax/i }).first(); await taxSelect.selectOption({ index: 1 }); await page.waitForTimeout(500); // Execute const stakeButton = page.getByRole('button', { name: /^(stake|snatch)/i }).first(); try { await stakeButton.click(); await page.waitForTimeout(1_000); const txInProgress = await page.getByRole('button', { name: /sign|waiting|confirm/i }).isVisible({ timeout: 3_000 }).catch(() => false); if (txInProgress) { await page.waitForTimeout(5_000); } observations.push('✓ Test stake executed'); } catch (error: any) { observations.push(`✗ Stake failed: ${error.message}`); } await page.waitForTimeout(3_000); // Evaluate position reporting observations.push('Checking position dashboard for institutional-grade reporting...'); // Transaction hash const hasTxHash = await page.locator('text=/0x[a-fA-F0-9]{64}|transaction|tx/i').isVisible().catch(() => false); if (hasTxHash) { observations.push('✓ Transaction hash visible - can verify on Etherscan'); } else { observations.push('⚠ No transaction hash shown - harder to verify on-chain'); } // Position details const hasDetails = await page.locator('text=/amount|tax rate|time|date/i').count(); if (hasDetails >= 3) { observations.push('✓ Comprehensive position details - sufficient for reporting'); } else { observations.push('⚠ Limited position details - insufficient for audit trail'); } // Export or reporting tools const hasExport = await page.getByRole('button', { name: /export|download|csv/i }).isVisible().catch(() => false); if (hasExport) { observations.push('✓ Export functionality - can generate reports for compliance'); } else { observations.push('✗ No export option - manual record-keeping required'); feedback.overall.friction.push('No position export for institutional reporting'); } // Screenshot const screenshotDir = join('test-results', 'usertest', 'priya-b'); const screenshotPath = join(screenshotDir, `position-reporting-${Date.now()}.png`); await page.screenshot({ path: screenshotPath, fullPage: true }); addFeedbackStep(feedback, 'position-reporting', observations, screenshotPath); // Priya's verdict const hasRequiredDocs = observations.filter(o => o.includes('✓')).length >= 4; const meetsStandards = !observations.some(o => o.includes('✗ No audit report')); feedback.overall.wouldStake = hasRequiredDocs && meetsStandards; feedback.overall.wouldReturn = hasRequiredDocs; observations.push(`Priya verdict: ${feedback.overall.wouldStake ? 'Meets institutional standards' : 'Insufficient documentation/transparency'}`); observations.push(`Would recommend: ${meetsStandards ? 'Yes, with caveats' : 'No, needs audit and better docs'}`); } finally { await context.close(); writePersonaFeedback(feedback); } }); }); });