205 lines
11 KiB
TypeScript
205 lines
11 KiB
TypeScript
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';
|
|
|
|
// Sarah uses Anvil account #2
|
|
const ACCOUNT_PRIVATE_KEY = '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a';
|
|
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('Sarah Park - Cautious Yield Farmer', () => {
|
|
test.beforeAll(async () => {
|
|
await resetChainState(STACK_RPC_URL);
|
|
await validateStackHealthy(STACK_CONFIG);
|
|
});
|
|
|
|
test('Sarah researches thoroughly before committing capital', async ({ browser }) => {
|
|
const report = createReport('Sarah Park');
|
|
const personaName = 'Sarah';
|
|
|
|
console.log(`[${personaName}] Starting test - Cautious yield farmer seeking sustainable returns...`);
|
|
|
|
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 Everything) ---
|
|
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, 'Reading landing page carefully before connecting wallet', report);
|
|
logCopyFeedback(personaName, 'Landing page should explain "What is Harberger tax?" in simple terms', report);
|
|
|
|
recordPageVisit('Landing', page.url(), pageStart, report);
|
|
|
|
// --- Look for About/Docs FIRST ---
|
|
logObservation(personaName, 'Looking for About, Docs, or Team page before doing anything else...', report);
|
|
|
|
const hasAbout = await page.getByText(/about/i).first().isVisible().catch(() => false);
|
|
const hasDocs = await page.getByText(/docs|documentation/i).first().isVisible().catch(() => false);
|
|
const hasTeam = await page.getByText(/team/i).first().isVisible().catch(() => false);
|
|
|
|
if (!hasAbout && !hasDocs && !hasTeam) {
|
|
logCopyFeedback(personaName, 'MAJOR ISSUE: No About, Docs, or Team link visible. I need background info before trusting this.', report);
|
|
logObservation(personaName, 'Feeling uncertain - no clear educational resources or team transparency', report);
|
|
}
|
|
|
|
await takeScreenshot(page, personaName, 'looking-for-info', report);
|
|
|
|
// --- Check for Audit Badge ---
|
|
const auditVisible = await page.getByText(/audit/i).isVisible().catch(() => false);
|
|
if (!auditVisible) {
|
|
logCopyFeedback(personaName, 'No audit badge visible - this is a dealbreaker for me normally, but will test anyway', report);
|
|
logTokenomicsQuestion(personaName, 'Has this been audited by Certik, Trail of Bits, or similar?', report);
|
|
}
|
|
|
|
// --- Connect Wallet (Hesitantly) ---
|
|
logObservation(personaName, 'Deciding to connect wallet after reading available info...', report);
|
|
|
|
pageStart = Date.now();
|
|
await connectWallet(page);
|
|
await takeScreenshot(page, personaName, 'wallet-connected', report);
|
|
recordAction('Connect wallet', true, undefined, report);
|
|
logObservation(personaName, 'Wallet connected. Now checking the staking interface details.', report);
|
|
|
|
// --- Navigate to Stake Page to Learn ---
|
|
pageStart = Date.now();
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`);
|
|
await page.waitForTimeout(3_000);
|
|
recordPageVisit('Stake (research)', page.url(), pageStart, report);
|
|
|
|
await takeScreenshot(page, personaName, 'stake-page-reading', report);
|
|
logObservation(personaName, 'Reading staking dashboard carefully - what are these tax rates about?', report);
|
|
logCopyFeedback(personaName, 'The info icon next to "Staking Dashboard" helps, but needs more detail on risks', report);
|
|
logTokenomicsQuestion(personaName, 'If I stake at 10% tax, what\'s my expected APY after taxes?', report);
|
|
logTokenomicsQuestion(personaName, 'What happens if I get snatched? Do I lose my principal or just my position?', report);
|
|
|
|
// --- Check Statistics Section ---
|
|
const statsSection = page.locator('.statistics-wrapper');
|
|
const statsVisible = await statsSection.isVisible().catch(() => false);
|
|
|
|
if (statsVisible) {
|
|
await takeScreenshot(page, personaName, 'statistics-analysis', report);
|
|
logObservation(personaName, 'Examining statistics - average tax rate, claimed slots, inflation rate', report);
|
|
logTokenomicsQuestion(personaName, 'How does the 7-day inflation compare to my expected staking returns?', report);
|
|
} else {
|
|
logCopyFeedback(personaName, 'Would be helpful to see protocol statistics and historical data', report);
|
|
}
|
|
|
|
// --- Mint ETH ---
|
|
pageStart = Date.now();
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/#/cheats`);
|
|
await page.waitForTimeout(1_000);
|
|
recordPageVisit('Cheats', page.url(), pageStart, report);
|
|
|
|
logObservation(personaName, 'Using test environment to simulate before committing real funds', report);
|
|
await mintEth(page, STACK_RPC_URL, ACCOUNT_ADDRESS, '20');
|
|
recordAction('Mint 20 ETH', true, undefined, report);
|
|
|
|
// --- Small Test Purchase ---
|
|
logObservation(personaName, 'Starting with a small test purchase to understand the process', report);
|
|
|
|
await buyKrk(page, '0.05');
|
|
recordAction('Buy KRK with 0.05 ETH (test)', true, undefined, report);
|
|
await takeScreenshot(page, personaName, 'test-purchase-complete', report);
|
|
logObservation(personaName, 'Test purchase successful. Now buying more for actual staking.', report);
|
|
|
|
await page.waitForTimeout(2_000);
|
|
|
|
// --- Buy enough for staking (split to reduce slippage) ---
|
|
await buyKrk(page, '3.0');
|
|
recordAction('Buy KRK with 3.0 ETH total', true, undefined, report);
|
|
logObservation(personaName, 'Bought more KRK. Now ready to stake.', report);
|
|
|
|
await page.waitForTimeout(2_000);
|
|
|
|
// --- Navigate Back to Stake ---
|
|
pageStart = Date.now();
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`);
|
|
await page.waitForTimeout(2_000);
|
|
recordPageVisit('Stake (attempt)', page.url(), pageStart, report);
|
|
|
|
await takeScreenshot(page, personaName, 'stake-form-before-fill', report);
|
|
logObservation(personaName, 'Examining the stake form - trying to understand tax rate implications', report);
|
|
logCopyFeedback(personaName, 'Tax rate dropdown needs explanation: "What tax rate should I choose?"', report);
|
|
logCopyFeedback(personaName, 'Would love a calculator: "Stake X at Y% tax = Z estimated APY"', report);
|
|
|
|
// --- Conservative Test Stake (High Tax for Safety) ---
|
|
logObservation(personaName, 'Choosing 15% tax rate to minimize snatch risk - prioritizing safety over yield', report);
|
|
logTokenomicsQuestion(personaName, 'Is 15% tax high enough to prevent snatching? What\'s the meta?', report);
|
|
|
|
try {
|
|
await attemptStake(page, '50', '15', personaName, report);
|
|
await takeScreenshot(page, personaName, 'conservative-stake-success', report);
|
|
logObservation(personaName, 'Stake successful! Now monitoring to see if position stays secure.', report);
|
|
recordAction('Stake 50 KRK at 15% tax (conservative)', true, undefined, report);
|
|
} catch (error: any) {
|
|
logObservation(personaName, `Stake failed: ${error.message}. This is confusing and frustrating.`, report);
|
|
logCopyFeedback(personaName, 'Error messages need to be clearer and suggest solutions', report);
|
|
await takeScreenshot(page, personaName, 'stake-error', report);
|
|
}
|
|
|
|
await page.waitForTimeout(3_000);
|
|
|
|
// --- Check Active Positions ---
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`);
|
|
await page.waitForTimeout(2_000);
|
|
await takeScreenshot(page, personaName, 'checking-my-position', report);
|
|
|
|
const activePositions = page.locator('.active-positions-wrapper');
|
|
const myPositionVisible = await activePositions.isVisible().catch(() => false);
|
|
|
|
if (myPositionVisible) {
|
|
logObservation(personaName, 'Can see my active position. Would want notifications when something changes.', report);
|
|
logCopyFeedback(personaName, 'Need mobile notifications or email alerts for position activity (snatch attempts, tax due)', report);
|
|
} else {
|
|
logObservation(personaName, 'Can\'t see my position clearly - where is it? Confusing UX.', report);
|
|
logCopyFeedback(personaName, '"My Positions" section should be more prominent', report);
|
|
}
|
|
|
|
// --- Compare to Mental Model (Aave) ---
|
|
logObservation(personaName, 'Comparing this to Aave in my head - Aave is simpler but boring...', report);
|
|
logTokenomicsQuestion(personaName, 'Aave gives me 8% on USDC with zero snatch risk. Why should I use this instead?', report);
|
|
logCopyFeedback(personaName, 'Needs a "Why Kraiken?" section comparing to traditional staking/lending', report);
|
|
|
|
// --- Final Thoughts ---
|
|
await page.waitForTimeout(2_000);
|
|
await takeScreenshot(page, personaName, 'final-review', report);
|
|
|
|
report.overallSentiment = 'Interested but need more information before committing real funds. The Harberger tax mechanism is intriguing but confusing - I don\'t fully understand how to optimize my tax rate or what happens if I get snatched. UI is clean but lacks educational content for newcomers. Missing: audit badge, return calculator, risk disclosures, comparison to alternatives, mobile notifications. Would need to monitor my test stake for 1-2 weeks before scaling up. Compared to Aave (8% risk-free), this needs to offer 10-15% to justify the complexity and snatch risk. Verdict: Promising but not ready for my main capital yet.';
|
|
|
|
logObservation(personaName, report.overallSentiment, report);
|
|
|
|
} finally {
|
|
writeReport('sarah-park', report);
|
|
await context.close();
|
|
}
|
|
});
|
|
});
|