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 ,
type PersonaFeedback ,
} from './helpers' ;
import { mkdirSync , readFileSync } from 'fs' ;
import { join } from 'path' ;
const KRK_ADDRESS = JSON . parse ( readFileSync ( join ( process . cwd ( ) , 'onchain' , 'deployments-local.json' ) , 'utf-8' ) ) . contracts . Kraiken . toLowerCase ( ) ;
const STACK_CONFIG = getStackConfig ( ) ;
const STACK_RPC_URL = STACK_CONFIG . rpcUrl ;
const STACK_WEBAPP_URL = STACK_CONFIG . webAppUrl ;
const LANDING_PAGE_URL = 'http://localhost:8081/' ;
// Account for tests
const ACCOUNT_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' ;
const ACCOUNT_ADDRESS = new Wallet ( ACCOUNT_PRIVATE_KEY ) . address ;
test . describe ( 'Test A: Passive Holder Journey' , ( ) = > {
test . beforeAll ( async ( ) = > {
await resetChainState ( STACK_RPC_URL ) ;
await validateStackHealthy ( STACK_CONFIG ) ;
} ) ;
test . describe . serial ( 'Tyler - Retail Degen ("sell me in 30 seconds")' , ( ) = > {
let feedback : PersonaFeedback ;
test . beforeAll ( ( ) = > {
feedback = createPersonaFeedback ( 'tyler' , 'A' , 'passive-holder' ) ;
} ) ;
test ( 'Tyler evaluates landing page value prop' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : ACCOUNT_PRIVATE_KEY ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
// Step 1: Navigate to landing page
await page . goto ( LANDING_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 2 _000 ) ;
// Tyler's quick evaluation
observations . push ( 'Scanning page... do I see APY numbers? Big buttons? What\'s the hook?' ) ;
// Check for value prop clarity
const hasGetKrkButton = await page . getByRole ( 'button' , { name : /get.*krk/i } ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasGetKrkButton ) {
observations . push ( '✓ "Get KRK" button is visible and prominent - good CTA' ) ;
} else {
observations . push ( '✗ No clear "Get KRK" button visible - where do I start?' ) ;
}
// Check for stats/numbers that catch attention
const hasStats = await page . locator ( 'text=/\\d+%|\\$\\d+|APY/i' ) . first ( ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasStats ) {
observations . push ( '✓ Numbers visible - I see stats, that\'s good for credibility' ) ;
} else {
observations . push ( '✗ No flashy APY or TVL numbers - nothing to grab my attention' ) ;
}
// Crypto jargon check
const pageText = await page . textContent ( 'body' ) || '' ;
const jargonWords = [ 'harberger' , 'vwap' , 'tokenomics' , 'liquidity' , 'leverage' ] ;
const foundJargon = jargonWords . filter ( word = > pageText . toLowerCase ( ) . includes ( word ) ) ;
if ( foundJargon . length > 2 ) {
observations . push ( ` ⚠ Too much jargon: ${ foundJargon . join ( ', ' ) } - might scare normies away ` ) ;
} else {
observations . push ( '✓ Copy is relatively clean, not too technical' ) ;
}
// Protocol Health section
const hasProtocolHealth = await page . getByText ( /protocol health|system status/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasProtocolHealth ) {
observations . push ( '✓ Protocol Health section builds trust - shows transparency' ) ;
} else {
observations . push ( 'Missing: No visible protocol health/stats - how do I know this isn\'t rugpull?' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'tyler-a' ) ;
mkdirSync ( screenshotDir , { recursive : true } ) ;
const screenshotPath = join ( screenshotDir , ` landing-page- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'landing-page' , observations , screenshotPath ) ;
// Tyler's 30-second verdict
const verdict = hasGetKrkButton && hasStats ?
'PASS: Clear CTA, visible stats. I\'d click through to learn more.' :
'FAIL: Not sold in 30 seconds. Needs bigger numbers and clearer value prop.' ;
observations . push ( ` Tyler \ 's verdict: ${ verdict } ` ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Tyler clicks Get KRK and checks Uniswap link' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : ACCOUNT_PRIVATE_KEY ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
await page . goto ( LANDING_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 1 _000 ) ;
// Click Get KRK button
const getKrkButton = page . getByRole ( 'button' , { name : /get.*krk/i } ) . first ( ) ;
const buttonVisible = await getKrkButton . isVisible ( { timeout : 5_000 } ) . catch ( ( ) = > false ) ;
if ( buttonVisible ) {
await getKrkButton . click ( ) ;
await page . waitForTimeout ( 2 _000 ) ;
// Check if navigated to web-app
const currentUrl = page . url ( ) ;
if ( currentUrl . includes ( 'get-krk' ) || currentUrl . includes ( '5173' ) ) {
observations . push ( '✓ Get KRK button navigated to web-app' ) ;
} else {
observations . push ( ` ✗ Get KRK button went to wrong place: ${ currentUrl } ` ) ;
}
// Check for Uniswap link with correct token address
const uniswapLink = await page . locator ( ` a[href*="uniswap"][href*=" ${ KRK_ADDRESS } "] ` ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( uniswapLink ) {
observations . push ( '✓ Uniswap link exists with correct KRK token address' ) ;
} else {
const anyUniswapLink = await page . locator ( 'a[href*="uniswap"]' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( anyUniswapLink ) {
observations . push ( '⚠ Uniswap link exists but may have wrong token address' ) ;
} else {
observations . push ( '✗ No Uniswap link found - how do I actually get KRK?' ) ;
}
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'tyler-a' ) ;
const screenshotPath = join ( screenshotDir , ` get-krk-page- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'get-krk' , observations , screenshotPath ) ;
} else {
observations . push ( '✗ CRITICAL: Get KRK button not found on landing page' ) ;
addFeedbackStep ( feedback , 'get-krk' , observations ) ;
}
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Tyler simulates having KRK and checks return value' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : ACCOUNT_PRIVATE_KEY ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
// Navigate to web-app to connect wallet
await page . goto ( ` ${ STACK_WEBAPP_URL } /app/ ` , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 2 _000 ) ;
// Mint ETH and buy KRK programmatically
observations . push ( 'Buying KRK via on-chain swap...' ) ;
await mintEth ( page , STACK_RPC_URL , ACCOUNT_ADDRESS , '10' ) ;
try {
await buyKrk ( page , '1' , STACK_RPC_URL , ACCOUNT_PRIVATE_KEY ) ;
observations . push ( '✓ Successfully acquired KRK via swap' ) ;
} catch ( error : any ) {
observations . push ( ` ✗ KRK purchase failed: ${ error . message } ` ) ;
feedback . overall . friction . push ( 'Cannot acquire KRK through documented flow' ) ;
}
// Navigate back to landing page
await page . goto ( LANDING_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 2 _000 ) ;
// Check for reasons to return
observations . push ( 'Now I have KRK... why would I come back to landing page?' ) ;
const hasStatsSection = await page . getByText ( /stats|protocol health|dashboard/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
const hasPriceInfo = await page . locator ( 'text=/price|\\$[0-9]/i' ) . isVisible ( ) . catch ( ( ) = > false ) ;
const hasAPY = await page . locator ( 'text=/APY|%/i' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasStatsSection || hasPriceInfo || hasAPY ) {
observations . push ( '✓ Landing page has stats/info - gives me reason to check back' ) ;
} else {
observations . push ( '✗ No compelling reason to return to landing page - just a static ad' ) ;
feedback . overall . friction . push ( 'Landing page offers no ongoing value for holders' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'tyler-a' ) ;
const screenshotPath = join ( screenshotDir , ` return-check- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'return-value' , observations , screenshotPath ) ;
// Tyler's overall assessment
const wouldReturn = hasStatsSection || hasPriceInfo || hasAPY ;
feedback . overall . wouldBuy = observations . some ( o = > o . includes ( '✓ Successfully acquired KRK' ) ) ;
feedback . overall . wouldReturn = wouldReturn ;
if ( ! wouldReturn ) {
feedback . overall . friction . push ( 'Landing page is one-time conversion, no repeat visit value' ) ;
}
} finally {
await context . close ( ) ;
writePersonaFeedback ( feedback ) ;
}
} ) ;
} ) ;
test . describe . serial ( 'Alex - Newcomer ("what even is this?")' , ( ) = > {
let feedback : PersonaFeedback ;
test . beforeAll ( ( ) = > {
feedback = createPersonaFeedback ( 'alex' , 'A' , 'passive-holder' ) ;
} ) ;
test ( 'Alex tries to understand the landing page' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : ACCOUNT_PRIVATE_KEY ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
await page . goto ( LANDING_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 2 _000 ) ;
observations . push ( 'Reading the page... trying to understand what this protocol does' ) ;
// Check for explanatory content
const hasExplainer = await page . getByText ( /how it works|what is|getting started/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasExplainer ) {
observations . push ( '✓ Found "How it works" or explainer section - helpful!' ) ;
} else {
observations . push ( '✗ No clear explainer - I\'m lost and don\'t know what this is' ) ;
feedback . overall . friction . push ( 'No beginner-friendly explanation on landing page' ) ;
}
// Jargon overload check
const pageText = await page . textContent ( 'body' ) || '' ;
const complexTerms = [ 'harberger' , 'vwap' , 'amm' , 'liquidity pool' , 'tokenomics' , 'leverage' , 'tax rate' ] ;
const foundTerms = complexTerms . filter ( term = > pageText . toLowerCase ( ) . includes ( term ) ) ;
if ( foundTerms . length > 3 ) {
observations . push ( ` ⚠ Jargon overload ( ${ foundTerms . length } complex terms): ${ foundTerms . join ( ', ' ) } ` ) ;
observations . push ( 'As a newcomer, this is intimidating and confusing' ) ;
feedback . overall . friction . push ( 'Too much unexplained crypto jargon' ) ;
} else {
observations . push ( '✓ Language is relatively accessible' ) ;
}
// Check for Get KRK button clarity
const getKrkButton = await page . getByRole ( 'button' , { name : /get.*krk/i } ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( getKrkButton ) {
observations . push ( '✓ "Get KRK" button is clear - I understand that\'s the next step' ) ;
} else {
observations . push ( '✗ Not sure how to start or what to do first' ) ;
}
// Trust signals
const hasTrustSignals = await page . getByText ( /audit|secure|safe|verified/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasTrustSignals ) {
observations . push ( '✓ Trust signals present (audit/secure) - makes me feel safer' ) ;
} else {
observations . push ( '⚠ No visible security/audit info - how do I know this is safe?' ) ;
feedback . overall . friction . push ( 'Lack of trust signals for newcomers' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'alex-a' ) ;
mkdirSync ( screenshotDir , { recursive : true } ) ;
const screenshotPath = join ( screenshotDir , ` landing-confusion- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'landing-page' , observations , screenshotPath ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Alex explores Get KRK page and looks for guidance' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : ACCOUNT_PRIVATE_KEY ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
await page . goto ( LANDING_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 1 _000 ) ;
// Try to click Get KRK
const getKrkButton = page . getByRole ( 'button' , { name : /get.*krk/i } ) . first ( ) ;
const buttonVisible = await getKrkButton . isVisible ( { timeout : 5_000 } ) . catch ( ( ) = > false ) ;
if ( buttonVisible ) {
await getKrkButton . click ( ) ;
await page . waitForTimeout ( 2 _000 ) ;
observations . push ( 'Clicked "Get KRK" - now what?' ) ;
// Look for step-by-step instructions
const hasInstructions = await page . getByText ( /step|how to|tutorial|guide/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasInstructions ) {
observations . push ( '✓ Found step-by-step instructions - very helpful for newcomer' ) ;
} else {
observations . push ( '✗ No clear instructions on how to proceed' ) ;
feedback . overall . friction . push ( 'Get KRK page lacks step-by-step guide' ) ;
}
// Check for Uniswap link explanation
const uniswapLink = await page . locator ( 'a[href*="uniswap"]' ) . first ( ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( uniswapLink ) {
// Check if there's explanatory text near the link
const hasContext = await page . getByText ( /swap|exchange|buy on uniswap/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasContext ) {
observations . push ( '✓ Uniswap link has context/explanation' ) ;
} else {
observations . push ( '⚠ Uniswap link present but no explanation - what is Uniswap?' ) ;
feedback . overall . friction . push ( 'No explanation of external links (Uniswap)' ) ;
}
} else {
observations . push ( '✗ No Uniswap link found - how do I get KRK?' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'alex-a' ) ;
const screenshotPath = join ( screenshotDir , ` get-krk-page- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'get-krk' , observations , screenshotPath ) ;
} else {
observations . push ( '✗ Could not find Get KRK button' ) ;
addFeedbackStep ( feedback , 'get-krk' , observations ) ;
}
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Alex simulates getting KRK and evaluates next steps' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : ACCOUNT_PRIVATE_KEY ,
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 ) ;
// Simulate getting KRK
observations . push ( 'Pretending I figured out Uniswap and bought KRK...' ) ;
await mintEth ( page , STACK_RPC_URL , ACCOUNT_ADDRESS , '10' ) ;
try {
await buyKrk ( page , '1' , STACK_RPC_URL , ACCOUNT_PRIVATE_KEY ) ;
observations . push ( '✓ Somehow managed to get KRK' ) ;
} catch ( error : any ) {
observations . push ( ` ✗ Failed to get KRK: ${ error . message } ` ) ;
}
// Navigate back to landing
await page . goto ( LANDING_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 2 _000 ) ;
observations . push ( 'Okay, I have KRK now... what should I do with it?' ) ;
// Look for holder guidance
const hasHolderInfo = await page . getByText ( /hold|stake|earn|now what/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasHolderInfo ) {
observations . push ( '✓ Found guidance for what to do after getting KRK' ) ;
} else {
observations . push ( '✗ No clear next steps - just have tokens sitting in wallet' ) ;
feedback . overall . friction . push ( 'No guidance for new holders on what to do next' ) ;
}
// Check for ongoing value
const hasReasonToReturn = await page . getByText ( /dashboard|stats|price|track/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasReasonToReturn ) {
observations . push ( '✓ Landing page has info worth checking regularly' ) ;
} else {
observations . push ( '✗ No reason to come back to landing page' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'alex-a' ) ;
const screenshotPath = join ( screenshotDir , ` after-purchase- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'post-purchase' , observations , screenshotPath ) ;
// Alex's verdict
const understandsValueProp = observations . some ( o = > o . includes ( '✓ Found "How it works"' ) ) ;
const knowsNextSteps = hasHolderInfo ;
feedback . overall . wouldBuy = understandsValueProp && observations . some ( o = > o . includes ( '✓ Somehow managed to get KRK' ) ) ;
feedback . overall . wouldReturn = hasReasonToReturn ;
if ( ! understandsValueProp ) {
feedback . overall . friction . push ( 'Value proposition unclear to crypto newcomers' ) ;
}
if ( ! knowsNextSteps ) {
feedback . overall . friction . push ( 'Post-purchase journey undefined' ) ;
}
} finally {
await context . close ( ) ;
writePersonaFeedback ( feedback ) ;
}
} ) ;
} ) ;
test . describe . serial ( 'Sarah - Yield Farmer ("is this worth my time?")' , ( ) = > {
let feedback : PersonaFeedback ;
test . beforeAll ( ( ) = > {
feedback = createPersonaFeedback ( 'sarah' , 'A' , 'passive-holder' ) ;
} ) ;
test ( 'Sarah analyzes landing page metrics and credibility' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : ACCOUNT_PRIVATE_KEY ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
await page . goto ( LANDING_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 2 _000 ) ;
observations . push ( 'Scanning for key metrics: APY, TVL, risk factors...' ) ;
// Check for APY/yield info
const hasAPY = await page . locator ( 'text=/\\d+%|APY|yield/i' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasAPY ) {
observations . push ( '✓ APY or yield percentage visible - good, I can compare to other protocols' ) ;
} else {
observations . push ( '✗ No clear APY shown - can\'t evaluate if this is competitive' ) ;
feedback . overall . friction . push ( 'No yield/APY displayed on landing page' ) ;
}
// Check for TVL
const hasTVL = await page . locator ( 'text=/TVL|total value locked|\\$[0-9]+[kmb]/i' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasTVL ) {
observations . push ( '✓ TVL visible - helps me assess protocol size and safety' ) ;
} else {
observations . push ( '⚠ No TVL shown - harder to gauge protocol maturity' ) ;
}
// Protocol Health section
const hasProtocolHealth = await page . getByText ( /protocol health|health|status/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasProtocolHealth ) {
observations . push ( '✓ Protocol Health section present - shows transparency and confidence' ) ;
} else {
observations . push ( '⚠ No protocol health metrics - how do I assess risk?' ) ;
feedback . overall . friction . push ( 'Missing protocol health/risk indicators' ) ;
}
// Audit info
const hasAudit = await page . getByText ( /audit|audited|security/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasAudit ) {
observations . push ( '✓ Audit information visible - critical for serious yield farmers' ) ;
} else {
observations . push ( '✗ No audit badge or security info - major red flag' ) ;
feedback . overall . friction . push ( 'No visible audit/security credentials' ) ;
}
// Smart contract addresses
const hasContracts = await page . locator ( 'text=/0x[a-fA-F0-9]{40}|contract address/i' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasContracts ) {
observations . push ( '✓ Contract addresses visible - I can verify on Etherscan' ) ;
} else {
observations . push ( '⚠ No contract addresses - want to verify before committing capital' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'sarah-a' ) ;
mkdirSync ( screenshotDir , { recursive : true } ) ;
const screenshotPath = join ( screenshotDir , ` landing-metrics- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'landing-page' , observations , screenshotPath ) ;
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Sarah evaluates Get KRK flow efficiency' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : ACCOUNT_PRIVATE_KEY ,
rpcUrl : STACK_RPC_URL ,
} ) ;
const page = await context . newPage ( ) ;
const observations : string [ ] = [ ] ;
try {
await page . goto ( LANDING_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 1 _000 ) ;
const getKrkButton = page . getByRole ( 'button' , { name : /get.*krk/i } ) . first ( ) ;
const buttonVisible = await getKrkButton . isVisible ( { timeout : 5_000 } ) . catch ( ( ) = > false ) ;
if ( buttonVisible ) {
await getKrkButton . click ( ) ;
await page . waitForTimeout ( 2 _000 ) ;
observations . push ( 'Evaluating acquisition flow - time is money' ) ;
// Check for direct swap vs external redirect
const currentUrl = page . url ( ) ;
const hasDirectSwap = await page . locator ( 'input[placeholder*="amount" i]' ) . isVisible ( { timeout : 3_000 } ) . catch ( ( ) = > false ) ;
if ( hasDirectSwap ) {
observations . push ( '✓ Direct swap interface - efficient, no external redirects' ) ;
} else {
observations . push ( '⚠ Redirects to external swap - adds friction and gas costs' ) ;
}
// Uniswap link check
const uniswapLink = await page . locator ( ` a[href*="uniswap"][href*=" ${ KRK_ADDRESS } "] ` ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( uniswapLink ) {
observations . push ( '✓ Uniswap link with correct token address - can verify liquidity' ) ;
} else {
observations . push ( '✗ No Uniswap link or wrong address - can\'t verify DEX liquidity' ) ;
feedback . overall . friction . push ( 'Cannot verify DEX liquidity before buying' ) ;
}
// Price impact warning
const hasPriceImpact = await page . getByText ( /price impact|slippage/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasPriceImpact ) {
observations . push ( '✓ Price impact/slippage shown - good UX for larger trades' ) ;
} else {
observations . push ( '⚠ No price impact warning - could be surprised by slippage' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'sarah-a' ) ;
const screenshotPath = join ( screenshotDir , ` get-krk-flow- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'get-krk' , observations , screenshotPath ) ;
} else {
observations . push ( '✗ Get KRK button not found' ) ;
addFeedbackStep ( feedback , 'get-krk' , observations ) ;
}
} finally {
await context . close ( ) ;
}
} ) ;
test ( 'Sarah checks for holder value and monitoring tools' , async ( { browser } ) = > {
const context = await createWalletContext ( browser , {
privateKey : ACCOUNT_PRIVATE_KEY ,
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 ) ;
// Acquire KRK
observations . push ( 'Acquiring KRK to evaluate holder experience...' ) ;
await mintEth ( page , STACK_RPC_URL , ACCOUNT_ADDRESS , '10' ) ;
try {
await buyKrk ( page , '2' , STACK_RPC_URL , ACCOUNT_PRIVATE_KEY ) ;
observations . push ( '✓ KRK acquired' ) ;
} catch ( error : any ) {
observations . push ( ` ✗ Acquisition failed: ${ error . message } ` ) ;
feedback . overall . friction . push ( 'Programmatic acquisition flow broken' ) ;
}
// Return to landing page
await page . goto ( LANDING_PAGE_URL , { waitUntil : 'domcontentloaded' } ) ;
await page . waitForTimeout ( 2 _000 ) ;
observations . push ( 'Now holding KRK - what ongoing value does landing page provide?' ) ;
// Real-time stats
const hasRealtimeStats = await page . locator ( 'text=/live|24h|volume|price/i' ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasRealtimeStats ) {
observations . push ( '✓ Real-time stats visible - makes landing page a monitoring dashboard' ) ;
} else {
observations . push ( '✗ No real-time data - no reason to return to landing page' ) ;
feedback . overall . friction . push ( 'Landing page provides no ongoing value for holders' ) ;
}
// Protocol health tracking
const hasHealthMetrics = await page . getByText ( /protocol health|system status|health score/i ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasHealthMetrics ) {
observations . push ( '✓ Protocol health tracking - helps me monitor risk' ) ;
} else {
observations . push ( '⚠ No protocol health dashboard - can\'t monitor protocol risk' ) ;
}
// Links to analytics
const hasAnalytics = await page . locator ( 'a[href*="dune"][href*="dexscreener"]' ) . or ( page . getByText ( /analytics|charts/i ) ) . isVisible ( ) . catch ( ( ) = > false ) ;
if ( hasAnalytics ) {
observations . push ( '✓ Links to analytics platforms - good for research' ) ;
} else {
observations . push ( '⚠ No links to Dune/DexScreener - harder to do deep analysis' ) ;
}
// Screenshot
const screenshotDir = join ( 'test-results' , 'usertest' , 'sarah-a' ) ;
const screenshotPath = join ( screenshotDir , ` holder-dashboard- ${ Date . now ( ) } .png ` ) ;
await page . screenshot ( { path : screenshotPath , fullPage : true } ) ;
addFeedbackStep ( feedback , 'holder-experience' , observations , screenshotPath ) ;
// Sarah's ROI assessment
const hasCompetitiveAPY = observations . some ( o = > o . includes ( '✓ APY or yield percentage visible' ) ) ;
const hasMonitoringTools = hasRealtimeStats || hasHealthMetrics ;
const lowFriction = feedback . overall . friction . length < 3 ;
feedback . overall . wouldBuy = hasCompetitiveAPY && lowFriction ;
feedback . overall . wouldReturn = hasMonitoringTools ;
if ( ! hasMonitoringTools ) {
feedback . overall . friction . push ( 'Insufficient monitoring/analytics tools for active yield farmers' ) ;
}
observations . push ( ` Sarah's verdict: ${ feedback . overall . wouldBuy ? 'Worth allocating capital' : 'Not competitive enough' } ` ) ;
observations . push ( ` Would return: ${ feedback . overall . wouldReturn ? 'Yes, for monitoring' : 'No, one-time interaction only' } ` ) ;
} finally {
await context . close ( ) ;
writePersonaFeedback ( feedback ) ;
}
} ) ;
} ) ;
} ) ;