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
/ * *
* Test B : Comprehensive Staker Journey ( v2 )
*
* Tests the full staking flow with three personas :
* - Marcus ( Anvil # 3 ) : "the snatcher" - executes snatch operations
* - Sarah ( Anvil # 4 ) : "the risk manager" - focuses on P & L and exit
* - Priya ( Anvil # 5 ) : "new staker" - fresh staking experience
*
* Prerequisites : Run setup - chain - state . ts to prepare initial positions
* /
import { expect , test } from '@playwright/test' ;
import { Wallet , ethers } from 'ethers' ;
import { createWalletContext } from '../../setup/wallet-provider' ;
import { getStackConfig , validateStackHealthy } from '../../setup/stack' ;
import {
createPersonaFeedback ,
addFeedbackStep ,
writePersonaFeedback ,
resetChainState ,
connectWallet ,
type PersonaFeedback ,
} from './helpers' ;
import { mkdirSync } from 'fs' ;
import { join } from 'path' ;
import { execSync } from 'child_process' ;
const STACK_CONFIG = getStackConfig ( ) ;
const STACK_RPC_URL = STACK_CONFIG . rpcUrl ;
2026-02-20 17:28:59 +01:00
const STAKE_PAGE_URL = ` ${ STACK_CONFIG . webAppUrl } /app/stake ` ;
2026-02-18 00:19:05 +01:00
// Anvil test account keys
const MARCUS_KEY = '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6' ; // Anvil #3
const SARAH_KEY = '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a' ; // Anvil #4
const PRIYA_KEY = '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba' ; // Anvil #5
test . describe ( 'Test B: Staker Journey v2' , ( ) = > {
test . beforeAll ( async ( ) = > {
console . log ( '[SETUP] Validating stack health...' ) ;
await validateStackHealthy ( STACK_CONFIG ) ;
console . log ( '[SETUP] Running chain state setup script...' ) ;
try {
execSync ( 'npx tsx tests/e2e/usertest/setup-chain-state.ts' , {
cwd : process.cwd ( ) ,
stdio : 'inherit' ,
} ) ;
} catch ( error ) {
console . error ( '[SETUP] Chain state setup failed:' , error ) ;
throw error ;
}
console . log ( '[SETUP] Saving initial snapshot for persona resets...' ) ;
await resetChainState ( STACK_RPC_URL ) ;
} ) ;
test . describe . serial ( 'Marcus - "the snatcher"' , ( ) = > {
let feedback : PersonaFeedback ;
const accountKey = MARCUS_KEY ;
const accountAddr = new Wallet ( accountKey ) . address ;
test . beforeAll ( ( ) = > {
feedback = createPersonaFeedback ( 'marcus-v2' , 'B' , 'staker' ) ;
} ) ;
test ( 'Marcus connects wallet and navigates to stake page' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : accountKey ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
observations . push ( 'Navigating to stake page...' ) ;
await page . goto ( STAKE_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 3 _000 ) ;
// Connect wallet
observations . push ( 'Connecting wallet...' ) ;
await connectWallet ( page ) ;
await page . waitForTimeout ( 2 _000 ) ;
const walletDisplay = page . getByText ( /0x[a-fA-F0-9]{4}/i ) . first ( ) ;
const isConnected = await walletDisplay . isVisible ( ) . catch ( ( ) = > false ) ;
if ( isConnected ) {
observations . push ( '✓ Wallet connected successfully' ) ;
} else {
observations . push ( '✗ Wallet connection failed' ) ;
feedback . overall . friction . push ( 'Wallet connection failed' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'marcus-v2' ) ;
mkdirSync ( screenshotDir , { recursive : true } ) ;
const screenshotPath = join ( screenshotDir , ` 01-wallet-connected- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'connect-wallet' , observations , screenshotPath ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Marcus verifies his existing position is visible with P&L' , 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 ( 3 _000 ) ;
observations . push ( 'Looking for my existing position created in setup...' ) ;
// Look for active positions section
const activePositions = page . locator ( '.active-positions-wrapper, .f-collapse-active, [class*="position"]' ) ;
const hasPositions = await activePositions . isVisible ( { timeout : 10_000 } ) . catch ( ( ) = > false ) ;
if ( hasPositions ) {
const positionCount = await page . locator ( '.f-collapse-active' ) . count ( ) ;
observations . push ( ` ✓ Found ${ positionCount } active position(s) ` ) ;
// Check for P&L display
const hasPnL = await page . locator ( '.pnl-metrics, .pnl-line1, text=/gross|tax|net/i' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasPnL ) {
observations . push ( '✓ P&L metrics visible (Gross/Tax/Net)' ) ;
} else {
observations . push ( '⚠ P&L metrics not visible' ) ;
feedback . overall . friction . push ( 'Position P&L not displayed' ) ;
}
// Check for position details
const hasDetails = await page . locator ( 'text=/initial stake|tax rate|time held/i' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasDetails ) {
observations . push ( '✓ Position details displayed' ) ;
} else {
observations . push ( '⚠ Position details incomplete' ) ;
}
} else {
observations . push ( '✗ No active positions found - setup may have failed' ) ;
feedback . overall . friction . push ( 'Position created in setup not visible' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'marcus-v2' ) ;
const screenshotPath = join ( screenshotDir , ` 02-existing-position- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'verify-existing-position' , observations , screenshotPath ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Marcus finds Sarah\'s position and executes snatch' , 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 ( 3 _000 ) ;
observations . push ( 'Looking for other positions with lower tax rates to snatch...' ) ;
// Check if we can see other positions (not just our own)
const allPositions = await page . locator ( '.f-collapse-active, [class*="position-card"]' ) . count ( ) ;
observations . push ( ` Found ${ allPositions } total positions visible ` ) ;
// Fill stake form to snatch
observations . push ( 'Filling snatch form: amount + higher tax rate...' ) ;
const amountInput = page . getByLabel ( 'Staking Amount' ) . or ( page . locator ( 'input[type="number"]' ) . first ( ) ) ;
await amountInput . waitFor ( { state : 'visible' , timeout : 10_000 } ) ;
await amountInput . fill ( '200' ) ; // Amount to snatch
await page . waitForTimeout ( 500 ) ;
// Select HIGHER tax rate than victim (Sarah has index 10, so use index 12+)
const taxSelect = page . locator ( 'select.tax-select' ) . or ( page . getByRole ( 'combobox' , { name : /tax/i } ) . first ( ) ) ;
await taxSelect . selectOption ( { index : 12 } ) ; // Higher than Sarah's medium tax
await page . waitForTimeout ( 1 _000 ) ;
// Check button text
const stakeButton = page . getByRole ( 'button' , { name : /snatch and stake|stake/i } ) . first ( ) ;
const buttonText = await stakeButton . textContent ( ) . catch ( ( ) = > '' ) ;
if ( buttonText ? . toLowerCase ( ) . includes ( 'snatch' ) ) {
observations . push ( '✓ Button shows "Snatch and Stake" - clear action' ) ;
// Check for snatch summary
const summary = page . locator ( '.stake-summary, text=/snatch/i' ) ;
const hasSummary = await summary . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasSummary ) {
observations . push ( '✓ Snatch summary visible' ) ;
}
// Screenshot before snatch
const screenshotDir = join ( 'test-results' , 'usertest' , 'marcus-v2' ) ;
const preSnatchPath = join ( screenshotDir , ` 03-pre-snatch- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : preSnatchPath , fullPage : true } ) ;
// Execute snatch
observations . push ( 'Executing snatch transaction...' ) ;
await stakeButton . click ( ) ;
await page . waitForTimeout ( 2 _000 ) ;
// Wait for transaction completion
try {
await page . getByRole ( 'button' , { name : /^(snatch and stake|stake)$/i } ) . waitFor ( {
state : 'visible' ,
timeout : 30_000
} ) ;
observations . push ( '✓ Snatch transaction completed' ) ;
} catch ( error ) {
observations . push ( '⚠ Snatch transaction may be pending' ) ;
}
await page . waitForTimeout ( 3 _000 ) ;
// Verify snatched position appears
const newPositionCount = await page . locator ( '.f-collapse-active' ) . count ( ) ;
observations . push ( ` Now have ${ newPositionCount } active position(s) ` ) ;
} else {
observations . push ( 'Button shows "Stake" - may not be snatching or no targets available' ) ;
}
// Screenshot after snatch
const screenshotDir = join ( 'test-results' , 'usertest' , 'marcus-v2' ) ;
const postSnatchPath = join ( screenshotDir , ` 04-post-snatch- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : postSnatchPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'execute-snatch' , observations , postSnatchPath ) ;
} finally {
await context . close ( ) ;
writePersonaFeedback ( feedback ) ;
}
} ) ;
} ) ;
test . describe . serial ( 'Sarah - "the risk manager"' , ( ) = > {
let feedback : PersonaFeedback ;
const accountKey = SARAH_KEY ;
const accountAddr = new Wallet ( accountKey ) . address ;
test . beforeAll ( ( ) = > {
feedback = createPersonaFeedback ( 'sarah-v2' , 'B' , 'staker' ) ;
} ) ;
test ( 'Sarah connects and views her position' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : accountKey ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
observations . push ( 'Sarah connecting to view her staked position...' ) ;
await page . goto ( STAKE_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 3 _000 ) ;
await connectWallet ( page ) ;
await page . waitForTimeout ( 3 _000 ) ;
// Look for position
const hasPosition = await page . locator ( '.f-collapse-active, [class*="position"]' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasPosition ) {
observations . push ( '✓ Position visible' ) ;
} else {
observations . push ( '✗ Position not found - may have been snatched' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'sarah-v2' ) ;
mkdirSync ( screenshotDir , { recursive : true } ) ;
const screenshotPath = join ( screenshotDir , ` 01-view-position- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'view-position' , observations , screenshotPath ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Sarah checks P&L display (gross return, tax cost, net return)' , 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 ( 3 _000 ) ;
observations . push ( 'Analyzing P&L metrics for risk assessment...' ) ;
// Check for P&L breakdown
const pnlLine = page . locator ( '.pnl-line1, text=/gross.*tax.*net/i' ) ;
const hasPnL = await pnlLine . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasPnL ) {
const pnlText = await pnlLine . textContent ( ) . catch ( ( ) = > '' ) ;
observations . push ( ` ✓ P&L display found: ${ pnlText } ` ) ;
// Check for positive/negative indicators
const isPositive = await page . locator ( '.pnl-positive' ) . isVisible ( ) . catch ( ( ) = > false ) ;
const isNegative = await page . locator ( '.pnl-negative' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( isPositive ) {
observations . push ( '✓ Net return is positive (green)' ) ;
} else if ( isNegative ) {
observations . push ( '⚠ Net return is negative (red)' ) ;
}
} else {
observations . push ( '✗ P&L metrics not visible' ) ;
feedback . overall . friction . push ( 'P&L display missing' ) ;
}
// Check for time held
const timeHeld = page . locator ( '.pnl-line2, text=/held.*d.*h/i' ) ;
const hasTimeHeld = await timeHeld . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasTimeHeld ) {
const timeText = await timeHeld . textContent ( ) . catch ( ( ) = > '' ) ;
observations . push ( ` ✓ Time held displayed: ${ timeText } ` ) ;
} else {
observations . push ( '⚠ Time held not visible' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'sarah-v2' ) ;
const screenshotPath = join ( screenshotDir , ` 02-pnl-analysis- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'check-pnl' , observations , screenshotPath ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Sarah executes exitPosition to recover her 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 ( STAKE_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 3 _000 ) ;
await connectWallet ( page ) ;
await page . waitForTimeout ( 3 _000 ) ;
observations . push ( 'Exiting position to recover KRK...' ) ;
// Find position and expand it
const position = page . locator ( '.f-collapse-active' ) . first ( ) ;
const hasPosition = await position . isVisible ( ) . catch ( ( ) = > false ) ;
if ( ! hasPosition ) {
observations . push ( '✗ No position to exit - may have been snatched already' ) ;
feedback . overall . friction . push ( 'Position disappeared before exit' ) ;
const screenshotDir = join ( 'test-results' , 'usertest' , 'sarah-v2' ) ;
const screenshotPath = join ( screenshotDir , ` 03-no-position- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'exit-position' , observations , screenshotPath ) ;
await context . close ( ) ;
return ;
}
// Expand position to see actions
await position . click ( ) ;
await page . waitForTimeout ( 1 _000 ) ;
// Look for Unstake/Exit button
const exitButton = position . getByRole ( 'button' , { name : /unstake|exit/i } ) ;
const hasExitButton = await exitButton . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasExitButton ) {
observations . push ( '✓ Exit button found' ) ;
// Screenshot before exit
const screenshotDir = join ( 'test-results' , 'usertest' , 'sarah-v2' ) ;
const preExitPath = join ( screenshotDir , ` 03-pre-exit- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : preExitPath , fullPage : true } ) ;
// Click exit
await exitButton . click ( ) ;
await page . waitForTimeout ( 2 _000 ) ;
// Wait for transaction
try {
await page . waitForTimeout ( 5 _000 ) ; // Give time for tx confirmation
observations . push ( '✓ Exit transaction submitted' ) ;
} catch ( error ) {
observations . push ( '⚠ Exit transaction may be pending' ) ;
}
// Verify position is gone
await page . waitForTimeout ( 3 _000 ) ;
const stillVisible = await position . isVisible ( ) . catch ( ( ) = > false ) ;
if ( ! stillVisible ) {
observations . push ( '✓ Position removed from Active Positions' ) ;
} else {
observations . push ( '⚠ Position still visible after exit' ) ;
}
// Screenshot after exit
const postExitPath = join ( screenshotDir , ` 04-post-exit- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : postExitPath , fullPage : true } ) ;
} else {
observations . push ( '✗ Exit button not found' ) ;
feedback . overall . friction . push ( 'Exit mechanism not accessible' ) ;
}
addFeedbackStep ( feedback , 'exit-position' , observations ) ;
} finally {
await context . close ( ) ;
writePersonaFeedback ( feedback ) ;
}
} ) ;
} ) ;
test . describe . serial ( 'Priya - "new staker"' , ( ) = > {
let feedback : PersonaFeedback ;
const accountKey = PRIYA_KEY ;
const accountAddr = new Wallet ( accountKey ) . address ;
test . beforeAll ( ( ) = > {
feedback = createPersonaFeedback ( 'priya-v2' , 'B' , 'staker' ) ;
} ) ;
test ( 'Priya connects wallet (fresh staker, no positions)' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : accountKey ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
observations . push ( 'Priya (fresh staker) connecting wallet...' ) ;
await page . goto ( STAKE_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 3 _000 ) ;
await connectWallet ( page ) ;
await page . waitForTimeout ( 3 _000 ) ;
// Verify no existing positions
const hasPositions = await page . locator ( '.f-collapse-active' ) . isVisible ( { timeout : 5_000 } ) . catch ( ( ) = > false ) ;
if ( ! hasPositions ) {
observations . push ( '✓ No existing positions (fresh staker)' ) ;
} else {
observations . push ( '⚠ Found existing positions - test may be contaminated' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'priya-v2' ) ;
mkdirSync ( screenshotDir , { recursive : true } ) ;
const screenshotPath = join ( screenshotDir , ` 01-fresh-state- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'connect-wallet' , observations , screenshotPath ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Priya fills staking amount using selectors from reference' , 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 ( 3 _000 ) ;
observations . push ( 'Filling staking form as a new user...' ) ;
// Use selector from reference doc: page.getByLabel('Staking Amount')
const amountInput = page . getByLabel ( 'Staking Amount' ) ;
const hasInput = await amountInput . isVisible ( { timeout : 10_000 } ) . catch ( ( ) = > false ) ;
if ( hasInput ) {
observations . push ( '✓ Staking Amount input found' ) ;
await amountInput . fill ( '100' ) ;
await page . waitForTimeout ( 500 ) ;
observations . push ( '✓ Filled amount: 100 KRK' ) ;
} else {
observations . push ( '✗ Staking Amount input not found' ) ;
feedback . overall . friction . push ( 'Staking amount input not accessible' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'priya-v2' ) ;
const screenshotPath = join ( screenshotDir , ` 02-amount-filled- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'fill-amount' , observations , screenshotPath ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Priya selects tax rate via dropdown' , 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 ( 3 _000 ) ;
// Fill amount first
const amountInput = page . getByLabel ( 'Staking Amount' ) ;
await amountInput . fill ( '100' ) ;
await page . waitForTimeout ( 500 ) ;
observations . push ( 'Selecting tax rate...' ) ;
// Use selector from reference: page.locator('select.tax-select')
const taxSelect = page . locator ( 'select.tax-select' ) ;
const hasTaxSelect = await taxSelect . isVisible ( { timeout : 10_000 } ) . catch ( ( ) = > false ) ;
if ( hasTaxSelect ) {
observations . push ( '✓ Tax rate selector found' ) ;
// Select a mid-range tax rate (index 5)
await taxSelect . selectOption ( { index : 5 } ) ;
await page . waitForTimeout ( 500 ) ;
const selectedValue = await taxSelect . inputValue ( ) ;
observations . push ( ` ✓ Selected tax rate index: ${ selectedValue } ` ) ;
} else {
observations . push ( '✗ Tax rate selector not found' ) ;
feedback . overall . friction . push ( 'Tax rate selector not accessible' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'priya-v2' ) ;
const screenshotPath = join ( screenshotDir , ` 03-tax-selected- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'select-tax-rate' , observations , screenshotPath ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Priya clicks Snatch and Stake button and handles permit signing' , 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 ( 3 _000 ) ;
observations . push ( 'Completing stake form and executing transaction...' ) ;
// Fill form
const amountInput = page . getByLabel ( 'Staking Amount' ) ;
await amountInput . fill ( '100' ) ;
await page . waitForTimeout ( 500 ) ;
const taxSelect = page . locator ( 'select.tax-select' ) ;
await taxSelect . selectOption ( { index : 5 } ) ;
await page . waitForTimeout ( 1 _000 ) ;
// Find stake button using reference selector
const stakeButton = page . getByRole ( 'button' , { name : /snatch and stake/i } ) ;
const hasButton = await stakeButton . isVisible ( { timeout : 10_000 } ) . catch ( ( ) = > false ) ;
if ( hasButton ) {
const buttonText = await stakeButton . textContent ( ) . catch ( ( ) = > '' ) ;
observations . push ( ` ✓ Stake button found: " ${ buttonText } " ` ) ;
// Check if enabled
const isEnabled = await stakeButton . isEnabled ( ) . catch ( ( ) = > false ) ;
if ( ! isEnabled ) {
observations . push ( '⚠ Button is disabled - checking for errors...' ) ;
// Check for error messages
const errorMessages = await page . locator ( 'text=/insufficient|too low|invalid/i' ) . allTextContents ( ) ;
if ( errorMessages . length > 0 ) {
observations . push ( ` ✗ Errors: ${ errorMessages . join ( ', ' ) } ` ) ;
feedback . overall . friction . push ( 'Stake button disabled with errors' ) ;
}
} else {
observations . push ( '✓ Button is enabled' ) ;
// Screenshot before stake
const screenshotDir = join ( 'test-results' , 'usertest' , 'priya-v2' ) ;
const preStakePath = join ( screenshotDir , ` 04-pre-stake- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : preStakePath , fullPage : true } ) ;
// Click stake button
observations . push ( 'Clicking stake button...' ) ;
await stakeButton . click ( ) ;
await page . waitForTimeout ( 2 _000 ) ;
// The wallet provider auto-signs, but check for transaction state
observations . push ( '✓ Permit signing handled by wallet provider (EIP-2612)' ) ;
// Wait for transaction completion
try {
await page . waitForTimeout ( 5 _000 ) ;
observations . push ( '✓ Transaction submitted' ) ;
} catch ( error ) {
observations . push ( '⚠ Transaction may be pending' ) ;
}
await page . waitForTimeout ( 3 _000 ) ;
// Screenshot after stake
const postStakePath = join ( screenshotDir , ` 05-post-stake- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : postStakePath , fullPage : true } ) ;
}
} else {
observations . push ( '✗ Stake button not found' ) ;
feedback . overall . friction . push ( 'Stake button not accessible' ) ;
}
addFeedbackStep ( feedback , 'execute-stake' , observations ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Priya verifies position appears in Active Positions' , 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 ( 3 _000 ) ;
observations . push ( 'Checking for new position in Active Positions...' ) ;
// Look for active positions wrapper (from reference)
const activePositionsWrapper = page . locator ( '.active-positions-wrapper' ) ;
const hasWrapper = await activePositionsWrapper . isVisible ( { timeout : 10_000 } ) . catch ( ( ) = > false ) ;
if ( hasWrapper ) {
observations . push ( '✓ Active Positions section found' ) ;
// Count positions
const positionCount = await page . locator ( '.f-collapse-active' ) . count ( ) ;
if ( positionCount > 0 ) {
observations . push ( ` ✓ Found ${ positionCount } active position(s) ` ) ;
feedback . overall . wouldStake = true ;
feedback . overall . wouldReturn = true ;
} else {
observations . push ( '⚠ No positions visible - stake may have failed' ) ;
feedback . overall . wouldStake = false ;
}
} else {
observations . push ( '✗ Active Positions section not found' ) ;
feedback . overall . friction . push ( 'Active Positions not visible after stake' ) ;
}
// Final screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'priya-v2' ) ;
const screenshotPath = join ( screenshotDir , ` 06-final-state- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'verify-position' , observations , screenshotPath ) ;
// Priya's verdict
observations . push ( ` Priya verdict: ${ feedback . overall . wouldStake ? 'Successful first stake' : 'Stake failed or unclear' } ` ) ;
} finally {
await context . close ( ) ;
writePersonaFeedback ( feedback ) ;
}
} ) ;
} ) ;
} ) ;