feat/ponder-lm-indexing (#142)

This commit is contained in:
johba 2026-02-18 00:19:05 +01:00
parent de3c8eef94
commit 31063379a8
107 changed files with 12517 additions and 367 deletions

View file

@ -0,0 +1,857 @@
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);
}
});
});
});