2026-03-03 20:58:01 +00:00
/* 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. */
2026-02-18 00:19:05 +01:00
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 ;
2026-02-20 17:28:59 +01:00
const STAKE_PAGE_URL = 'http://localhost:5173/stake' ;
2026-02-18 00:19:05 +01:00
// 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 ) ;
}
} ) ;
} ) ;
} ) ;