248 lines
14 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
});
|