harb/tests/e2e/usertest/alex-newcomer.spec.ts
openhands 748557bc83 fix: lint: Ban waitForTimeout, setTimeout-as-delay, and fixed sleep patterns (#442)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 20:58:01 +00:00

248 lines
14 KiB
TypeScript

/* 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. */
import { expect, test } from '@playwright/test';
import { Wallet } from 'ethers';
import { createWalletContext } from '../../setup/wallet-provider';
import { getStackConfig, validateStackHealthy } from '../../setup/stack';
import {
createReport,
connectWallet,
mintEth,
buyKrk,
takeScreenshot,
logObservation,
logCopyFeedback,
logTokenomicsQuestion,
recordPageVisit,
recordAction,
writeReport,
attemptStake,
resetChainState,
} from './helpers';
// Alex uses Anvil account #0 (same as original test, different persona)
const ACCOUNT_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
const ACCOUNT_ADDRESS = new Wallet(ACCOUNT_PRIVATE_KEY).address.toLowerCase();
const STACK_CONFIG = getStackConfig();
const STACK_RPC_URL = STACK_CONFIG.rpcUrl;
const STACK_WEBAPP_URL = STACK_CONFIG.webAppUrl;
test.describe('Alex Rivera - Crypto-Curious Newcomer', () => {
test.beforeAll(async () => {
await resetChainState(STACK_RPC_URL);
await validateStackHealthy(STACK_CONFIG);
});
test('Alex learns about DeFi through Kraiken', async ({ browser }) => {
const report = createReport('Alex Rivera');
const personaName = 'Alex';
console.log(`[${personaName}] Starting test - Newcomer trying to understand DeFi...`);
const context = await createWalletContext(browser, {
privateKey: ACCOUNT_PRIVATE_KEY,
rpcUrl: STACK_RPC_URL,
});
const page = await context.newPage();
page.on('console', msg => console.log(`[BROWSER] ${msg.type()}: ${msg.text()}`));
page.on('pageerror', error => console.log(`[BROWSER ERROR] ${error.message}`));
try {
// --- Landing Page (Reads Carefully) ---
let pageStart = Date.now();
await page.goto(`${STACK_WEBAPP_URL}/app/`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3_000);
await takeScreenshot(page, personaName, 'landing-page', report);
logObservation(personaName, 'This looks professional but I have no idea what I\'m looking at...', report);
logCopyFeedback(personaName, 'Landing page should have a "New to DeFi?" section that explains basics', report);
logTokenomicsQuestion(personaName, 'What is staking? How do I make money from this?', report);
recordPageVisit('Landing', page.url(), pageStart, report);
// --- Look for Help/Tutorial ---
logObservation(personaName, 'Looking for a "How it Works" or tutorial before I do anything...', report);
const tutorialVisible = await page.getByText(/how it works|tutorial|getting started|learn/i).isVisible().catch(() => false);
if (!tutorialVisible) {
logCopyFeedback(personaName, 'CRITICAL: No "Getting Started" guide visible. I\'m intimidated and don\'t know where to begin.', report);
logObservation(personaName, 'Feeling overwhelmed - too much jargon without explanation', report);
}
await takeScreenshot(page, personaName, 'looking-for-help', report);
// --- Nervous About Connecting Wallet ---
logObservation(personaName, 'I\'ve heard about wallet scams... is this safe to connect?', report);
logCopyFeedback(personaName, 'Need trust signals: "Audited", "Secure", "Non-custodial" badges to reassure newcomers', report);
const securityInfo = await page.getByText(/secure|safe|audited|trusted/i).isVisible().catch(() => false);
if (!securityInfo) {
logObservation(personaName, 'No security information visible - makes me nervous to connect wallet', report);
}
await page.waitForTimeout(2_000);
// --- Decides to Connect Wallet (Cautiously) ---
logObservation(personaName, 'Okay, deep breath... connecting wallet for the first time on this app', report);
try {
await connectWallet(page);
await takeScreenshot(page, personaName, 'wallet-connected', report);
recordAction('Connect wallet (first time)', true, undefined, report);
logObservation(personaName, 'Wallet connected! That was easier than I thought. Now what?', report);
} catch (error: any) {
logObservation(personaName, `Wallet connection failed: ${error.message}. This is too complicated, giving up.`, report);
logCopyFeedback(personaName, 'Wallet connection errors need beginner-friendly explanations', report);
recordAction('Connect wallet', false, error.message, report);
throw error;
}
// --- Navigate to Stake Page to Learn ---
pageStart = Date.now();
await page.goto(`${STACK_WEBAPP_URL}/app/stake`);
await page.waitForTimeout(3_000);
recordPageVisit('Stake (learning)', page.url(), pageStart, report);
await takeScreenshot(page, personaName, 'stake-page-first-look', report);
logObservation(personaName, 'Lots of numbers and charts... what does it all mean?', report);
logTokenomicsQuestion(personaName, 'What is a "Harberger Tax"? Never heard of this before.', report);
logTokenomicsQuestion(personaName, 'What are "owner slots"? Is that like shares?', report);
// --- Reads Info Icon ---
const infoIcon = page.locator('svg').filter({ hasText: /info/i }).first();
const infoVisible = await infoIcon.isVisible().catch(() => false);
if (infoVisible) {
logObservation(personaName, 'Found info icon - let me read this...', report);
// Try to hover to see tooltip
await infoIcon.hover().catch(() => {});
await page.waitForTimeout(1_000);
await takeScreenshot(page, personaName, 'reading-info-tooltip', report);
logCopyFeedback(personaName, 'Info tooltips help, but still too technical for total beginners', report);
} else {
logCopyFeedback(personaName, 'Need more info icons and tooltips to explain every element', report);
}
// --- Confused by Terminology ---
logObservation(personaName, 'Words I don\'t understand: VWAP, tax rate, snatching, claimed slots...', report);
logCopyFeedback(personaName, 'ESSENTIAL: Need a glossary or hover definitions for all DeFi terms', report);
// --- Tries to Find FAQ ---
logObservation(personaName, 'Looking for FAQ or help section...', report);
const faqVisible = await page.getByText(/faq|frequently asked|help/i).isVisible().catch(() => false);
if (!faqVisible) {
logCopyFeedback(personaName, 'No FAQ visible! Common questions like "Can I lose money?" need answers up front.', report);
logTokenomicsQuestion(personaName, 'Can I lose my money if I stake? What are the risks?', report);
}
// --- Mint ETH (Following Instructions) ---
logObservation(personaName, 'I need to get some tokens first... let me figure out how', report);
pageStart = Date.now();
await page.goto(`${STACK_WEBAPP_URL}/app/cheats`);
await page.waitForTimeout(2_000);
recordPageVisit('Cheats (confused)', page.url(), pageStart, report);
await takeScreenshot(page, personaName, 'cheats-page', report);
logObservation(personaName, '"Cheat Console"? Is this for testing? I\'m confused but will try it...', report);
try {
await mintEth(page, STACK_RPC_URL, ACCOUNT_ADDRESS, '5');
recordAction('Mint 5 ETH (following guide)', true, undefined, report);
logObservation(personaName, 'Got some ETH! Still not sure what I\'m doing though...', report);
} catch (error: any) {
logObservation(personaName, `Mint failed: ${error.message}. These errors are scary!`, report);
logCopyFeedback(personaName, 'Error messages should be encouraging, not scary. Add "Need help?" links.', report);
recordAction('Mint ETH', false, error.message, report);
}
// --- Buy Small Amount (Cautiously) ---
logObservation(personaName, 'Buying the smallest amount possible to test - don\'t want to lose much if this is a scam', report);
try {
await buyKrk(page, '0.8');
recordAction('Buy KRK with 0.05 ETH (minimal test)', true, undefined, report);
await takeScreenshot(page, personaName, 'small-purchase', report);
logObservation(personaName, 'Purchase went through! That was actually pretty smooth.', report);
logCopyFeedback(personaName, 'Good: Transaction was straightforward. Bad: No confirmation message explaining what happened.', report);
} catch (error: any) {
logObservation(personaName, `Buy failed: ${error.message}. Maybe I should just stick to Coinbase...`, report);
recordAction('Buy KRK', false, error.message, report);
}
await page.waitForTimeout(2_000);
// --- Navigate to Stake (Intimidated) ---
pageStart = Date.now();
await page.goto(`${STACK_WEBAPP_URL}/app/stake`);
await page.waitForTimeout(2_000);
recordPageVisit('Stake (attempting)', page.url(), pageStart, report);
await takeScreenshot(page, personaName, 'stake-form-confused', report);
logObservation(personaName, 'Staring at the stake form... what tax rate should I pick???', report);
logTokenomicsQuestion(personaName, 'Higher tax = more money or less money? This is backwards from normal taxes!', report);
logCopyFeedback(personaName, 'CRITICAL: Tax rate needs "Recommended for beginners: 10-15%" guidance', report);
// --- Looks for Recommendation ---
const recommendationVisible = await page.getByText(/recommended|suggested|beginner/i).isVisible().catch(() => false);
if (!recommendationVisible) {
logCopyFeedback(personaName, 'Please add a "What should I choose?" helper or wizard mode for newcomers!', report);
}
// --- Attempt Conservative Stake ---
logObservation(personaName, 'Going with 15% because it sounds safe... I think? Really not sure about this.', report);
try {
await attemptStake(page, '25', '15', personaName, report);
await takeScreenshot(page, personaName, 'stake-success', report);
logObservation(personaName, 'IT WORKED! I just staked my first crypto! But... what happens now?', report);
recordAction('Stake 25 KRK at 15% tax (nervous)', true, undefined, report);
logTokenomicsQuestion(personaName, 'When do I get paid? How much will I earn? Where do I see my rewards?', report);
} catch (error: any) {
logObservation(personaName, `Stake failed: ${error.message}. I give up, this is too hard.`, report);
logCopyFeedback(personaName, 'Failed stakes need recovery guidance: "Here\'s what to try next..."', report);
await takeScreenshot(page, personaName, 'stake-failed', report);
}
await page.waitForTimeout(3_000);
// --- Check for Progress Indicators ---
await page.goto(`${STACK_WEBAPP_URL}/app/stake`);
await page.waitForTimeout(2_000);
await takeScreenshot(page, personaName, 'looking-for-my-position', report);
logObservation(personaName, 'Where is my position? How do I see what I earned?', report);
logCopyFeedback(personaName, 'Need a big "Your Position" dashboard showing: amount staked, daily earnings, time held', report);
// --- Worried About Snatching ---
logObservation(personaName, 'I see something about "snatching"... can someone steal my stake???', report);
logTokenomicsQuestion(personaName, 'What does snatching mean? Will I lose my money? This is scary!', report);
logCopyFeedback(personaName, 'Snatching concept is TERRIFYING for newcomers. Need clear "You don\'t lose principal" message.', report);
await takeScreenshot(page, personaName, 'worried-about-snatching', report);
// --- Compare to Coinbase ---
logObservation(personaName, 'On Coinbase I just click "Stake ETH" and get 4% APY. This is way more complicated...', report);
logTokenomicsQuestion(personaName, 'Why should I use this instead of just staking ETH on Coinbase?', report);
logCopyFeedback(personaName, 'Need comparison: "Coinbase: 4% simple. Kraiken: 8-15% but you choose your own risk level"', report);
// --- Final Feelings ---
await page.waitForTimeout(2_000);
await takeScreenshot(page, personaName, 'final-state', report);
report.overallSentiment = 'Mixed feelings - excited that I did my first DeFi stake, but confused and nervous about many things. GOOD: The actual transaction process was smooth once I figured it out. UI looks professional and trustworthy. CONFUSING: Harberger tax concept is completely foreign to me. Don\'t understand how tax rates affect my earnings. Scared about "snatching" - sounds like I could lose money. No clear guidance on what to do next or how to track earnings. NEEDED: (1) "Getting Started" tutorial with video walkthrough, (2) Glossary of terms in plain English, (3) Tax rate wizard that asks questions and recommends a rate, (4) Big clear "Your Daily Earnings: $X" display, (5) FAQ addressing "Can I lose money?" and "What is snatching?", (6) Comparison to Coinbase/simple staking to show why this is better. VERDICT: Would monitor my tiny stake for a week to see what happens. If I actually earn money and nothing bad happens, I might add more. But if I get "snatched" without understanding why, I\'m selling everything and never coming back. This needs to be MUCH more beginner-friendly to compete with centralized platforms.';
logObservation(personaName, report.overallSentiment, report);
} finally {
writeReport('alex-rivera', report);
await context.close();
}
});
});