347 lines
15 KiB
TypeScript
347 lines
15 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
// Persona definitions based on usertest-personas.json
|
|
interface PersonaFeedback {
|
|
personaId: number;
|
|
personaName: string;
|
|
variant: string;
|
|
variantUrl: string;
|
|
timestamp: string;
|
|
evaluation: {
|
|
firstImpression: number;
|
|
wouldClickCTA: {
|
|
answer: boolean;
|
|
reasoning: string;
|
|
};
|
|
trustLevel: number;
|
|
excitementLevel: number;
|
|
wouldShare: {
|
|
answer: boolean;
|
|
reasoning: string;
|
|
};
|
|
topComplaint: string;
|
|
whatWouldMakeMeBuy: string;
|
|
};
|
|
copyObserved: {
|
|
headline: string;
|
|
subtitle: string;
|
|
ctaText: string;
|
|
keyMessages: string[];
|
|
};
|
|
}
|
|
|
|
// Variant definitions
|
|
const variants = [
|
|
{
|
|
id: 'defensive',
|
|
name: 'Variant A (Defensive)',
|
|
url: 'http://localhost:5174/#/',
|
|
headline: 'The token that can\'t be rugged.',
|
|
subtitle: '$KRK has a price floor backed by real ETH. An AI manages it. You just hold.',
|
|
cta: 'Get $KRK',
|
|
tone: 'safety-focused',
|
|
},
|
|
{
|
|
id: 'offensive',
|
|
name: 'Variant B (Offensive)',
|
|
url: 'http://localhost:5174/#/offensive',
|
|
headline: 'The AI that trades while you sleep.',
|
|
subtitle: 'An autonomous AI agent managing $KRK liquidity 24/7. Capturing alpha. Deepening positions. You just hold and win.',
|
|
cta: 'Get Your Edge',
|
|
tone: 'aggressive',
|
|
},
|
|
{
|
|
id: 'mixed',
|
|
name: 'Variant C (Mixed)',
|
|
url: 'http://localhost:5174/#/mixed',
|
|
headline: 'DeFi without the rug pull.',
|
|
subtitle: 'AI-managed liquidity with an ETH-backed floor. Real upside, protected downside.',
|
|
cta: 'Buy $KRK',
|
|
tone: 'balanced',
|
|
},
|
|
];
|
|
|
|
// Marcus "Flash" Chen - Degen / MEV Hunter
|
|
function evaluateMarcus(variant: typeof variants[0]): PersonaFeedback['evaluation'] {
|
|
const { id, headline, subtitle, cta, tone } = variant;
|
|
|
|
let firstImpression = 5;
|
|
let wouldClickCTA = false;
|
|
let ctaReasoning = '';
|
|
let trustLevel = 5;
|
|
let excitementLevel = 4;
|
|
let wouldShare = false;
|
|
let shareReasoning = '';
|
|
let topComplaint = '';
|
|
let whatWouldMakeMeBuy = '';
|
|
|
|
if (id === 'defensive') {
|
|
// Marcus hates "safe" language, gets bored
|
|
firstImpression = 4;
|
|
wouldClickCTA = false;
|
|
ctaReasoning = '"Can\'t be rugged" sounds like marketing cope. Where\'s the alpha? This reads like it\'s for scared money. I want edge, not safety blankets.';
|
|
trustLevel = 6; // Appreciates the ETH backing mention
|
|
excitementLevel = 3; // Boring
|
|
wouldShare = false;
|
|
shareReasoning = 'Too defensive. My CT would roast me for shilling "safe" tokens. This is for退 boomers.';
|
|
topComplaint = 'Zero edge. "Just hold" = ngmi. Where\'s the game theory? Where\'s the PvP? Reads like index fund marketing.';
|
|
whatWouldMakeMeBuy = 'Show me the exploit potential. Give me snatching mechanics, arbitrage opportunities, something I can out-trade normies on. Stop selling safety.';
|
|
} else if (id === 'offensive') {
|
|
// Marcus loves aggression, alpha talk, edge
|
|
firstImpression = 9;
|
|
wouldClickCTA = true;
|
|
ctaReasoning = '"Get Your Edge" speaks my language. "Trades while you sleep" + "capturing alpha" = I\'m interested. This feels like it respects my intelligence.';
|
|
trustLevel = 7; // Appreciates the technical framing
|
|
excitementLevel = 9; // FOMO activated
|
|
wouldShare = true;
|
|
shareReasoning = '"First-mover alpha" and "AI trading edge" are CT-native. This has the hype energy without being cringe. I\'d quote-tweet this.';
|
|
topComplaint = 'Still needs more meat. Where are the contract links? Where\'s the audit? Don\'t just tell me "alpha," show me the code.';
|
|
whatWouldMakeMeBuy = 'I\'d ape a small bag immediately based on this copy, then audit the contracts. If the mechanics are novel and the code is clean, I\'m in heavy.';
|
|
} else if (id === 'mixed') {
|
|
// Mixed approach - Marcus appreciates clarity but wants more edge
|
|
firstImpression = 7;
|
|
wouldClickCTA = true;
|
|
ctaReasoning = '"DeFi without the rug pull" is punchy. "Real upside, protected downside" frames the value prop clearly. Not as boring as variant A.';
|
|
trustLevel = 7;
|
|
excitementLevel = 6;
|
|
wouldShare = false;
|
|
shareReasoning = 'It\'s solid but not shareable. Lacks the memetic punch of variant B. This is "good product marketing," not "CT viral."';
|
|
topComplaint = 'Sits in the middle. Not safe enough for noobs, not edgy enough for degens. Trying to please everyone = pleasing no one.';
|
|
whatWouldMakeMeBuy = 'If I saw this after variant B, I\'d click through. But if this was my first impression, I\'d probably keep scrolling. Needs more bite.';
|
|
}
|
|
|
|
return {
|
|
firstImpression,
|
|
wouldClickCTA: { answer: wouldClickCTA, reasoning: ctaReasoning },
|
|
trustLevel,
|
|
excitementLevel,
|
|
wouldShare: { answer: wouldShare, reasoning: shareReasoning },
|
|
topComplaint,
|
|
whatWouldMakeMeBuy,
|
|
};
|
|
}
|
|
|
|
// Sarah Park - Cautious Yield Farmer
|
|
function evaluateSarah(variant: typeof variants[0]): PersonaFeedback['evaluation'] {
|
|
const { id, headline, subtitle, cta, tone } = variant;
|
|
|
|
let firstImpression = 5;
|
|
let wouldClickCTA = false;
|
|
let ctaReasoning = '';
|
|
let trustLevel = 5;
|
|
let excitementLevel = 4;
|
|
let wouldShare = false;
|
|
let shareReasoning = '';
|
|
let topComplaint = '';
|
|
let whatWouldMakeMeBuy = '';
|
|
|
|
if (id === 'defensive') {
|
|
// Sarah loves safety, clarity, ETH backing
|
|
firstImpression = 8;
|
|
wouldClickCTA = true;
|
|
ctaReasoning = '"Can\'t be rugged" + "price floor backed by real ETH" addresses my #1 concern. AI management sounds hands-off, which I like. Professional tone.';
|
|
trustLevel = 8; // Direct mention of ETH backing
|
|
excitementLevel = 6; // Steady, not hyped
|
|
wouldShare = false;
|
|
shareReasoning = 'I\'d research this myself first. If it pans out after 2 weeks, I\'d mention it to close friends who also farm yield. Not Twitter material.';
|
|
topComplaint = 'No numbers. What\'s the expected APY? What\'s the price floor mechanism exactly? How does the AI work? Need more detail before I connect wallet.';
|
|
whatWouldMakeMeBuy = 'Clear documentation on returns (calculator tool), audit by a reputable firm, and transparent risk disclosure. If APY beats Aave\'s 8% with reasonable risk, I\'m in.';
|
|
} else if (id === 'offensive') {
|
|
// Sarah dislikes hype, "alpha" talk feels risky
|
|
firstImpression = 5;
|
|
wouldClickCTA = false;
|
|
ctaReasoning = '"Get Your Edge" feels like a casino ad. "Capturing alpha" and "you just hold and win" sound too good to be true. Red flags for unsustainable promises.';
|
|
trustLevel = 4; // Skeptical of aggressive marketing
|
|
excitementLevel = 3; // Turned off
|
|
wouldShare = false;
|
|
shareReasoning = 'This reads like a high-risk moonshot. I wouldn\'t recommend this to anyone I care about. Feels like 2021 degen marketing.';
|
|
topComplaint = 'Way too much hype, zero substance. "First-mover alpha" is a euphemism for "you\'re exit liquidity." Where are the audits? The team? The real returns?';
|
|
whatWouldMakeMeBuy = 'Tone it down. Give me hard numbers, risk disclosures, and professional credibility. Stop trying to sell me FOMO and sell me fundamentals.';
|
|
} else if (id === 'mixed') {
|
|
// Balanced approach works for Sarah
|
|
firstImpression = 9;
|
|
wouldClickCTA = true;
|
|
ctaReasoning = '"DeFi without the rug pull" is reassuring. "Protected downside, real upside" frames risk/reward clearly. AI management + ETH backing = interesting.';
|
|
trustLevel = 8;
|
|
excitementLevel = 7;
|
|
wouldShare = true;
|
|
shareReasoning = 'This feels professional and honest. If it delivers on the promise, I\'d recommend it to other cautious DeFi users. Balanced tone inspires confidence.';
|
|
topComplaint = 'Still light on specifics. I want to see the risk/return math before I commit. Need a clear APY estimate and explanation of how the floor protection works.';
|
|
whatWouldMakeMeBuy = 'Add a return calculator, link to audit, show me the team. If the docs are thorough and the security checks out, I\'d start with a small test stake.';
|
|
}
|
|
|
|
return {
|
|
firstImpression,
|
|
wouldClickCTA: { answer: wouldClickCTA, reasoning: ctaReasoning },
|
|
trustLevel,
|
|
excitementLevel,
|
|
wouldShare: { answer: wouldShare, reasoning: shareReasoning },
|
|
topComplaint,
|
|
whatWouldMakeMeBuy,
|
|
};
|
|
}
|
|
|
|
// Alex Rivera - Crypto-Curious Newcomer
|
|
function evaluateAlex(variant: typeof variants[0]): PersonaFeedback['evaluation'] {
|
|
const { id, headline, subtitle, cta, tone } = variant;
|
|
|
|
let firstImpression = 5;
|
|
let wouldClickCTA = false;
|
|
let ctaReasoning = '';
|
|
let trustLevel = 5;
|
|
let excitementLevel = 4;
|
|
let wouldShare = false;
|
|
let shareReasoning = '';
|
|
let topComplaint = '';
|
|
let whatWouldMakeMeBuy = '';
|
|
|
|
if (id === 'defensive') {
|
|
// Alex appreciates simplicity and safety signals
|
|
firstImpression = 8;
|
|
wouldClickCTA = true;
|
|
ctaReasoning = '"Can\'t be rugged" is reassuring for someone who\'s heard horror stories. "You just hold" = simple. ETH backing sounds real/tangible.';
|
|
trustLevel = 7; // Safety language builds trust
|
|
excitementLevel = 6; // Curious
|
|
wouldShare = false;
|
|
shareReasoning = 'I\'m too new to recommend crypto stuff to friends. But if I make money and it\'s actually safe, I might mention it later.';
|
|
topComplaint = 'I don\'t know what "price floor" or "Uniswap V3" mean. The headline is clear, but the details lose me. Need simpler explanations.';
|
|
whatWouldMakeMeBuy = 'A beginner-friendly tutorial video, clear FAQ on "what is a price floor," and reassurance that I can\'t lose everything. Maybe testimonials from real users.';
|
|
} else if (id === 'offensive') {
|
|
// Alex intimidated by aggressive language
|
|
firstImpression = 4;
|
|
wouldClickCTA = false;
|
|
ctaReasoning = '"Get Your Edge" sounds like day-trading talk. "Capturing alpha" = ??? This feels like it\'s for experts, not me. Intimidating.';
|
|
trustLevel = 4; // Feels risky
|
|
excitementLevel = 5; // Intrigued but scared
|
|
wouldShare = false;
|
|
shareReasoning = 'I wouldn\'t share this. It sounds too risky and I don\'t understand half the terms. Don\'t want to look dumb or lose friends\' money.';
|
|
topComplaint = 'Too much jargon. "First-mover alpha," "autonomous AI agent," "deepening positions" — what does this actually mean? Feels like a trap for noobs.';
|
|
whatWouldMakeMeBuy = 'Explain like I\'m 5. What is this? How do I use it? What are the risks in plain English? Stop assuming I know what "alpha" means.';
|
|
} else if (id === 'mixed') {
|
|
// Balanced clarity works well for Alex
|
|
firstImpression = 7;
|
|
wouldClickCTA = true;
|
|
ctaReasoning = '"DeFi without the rug pull" speaks to my fears (I\'ve heard about scams). "Protected downside" = safety. Simple CTA "Buy $KRK" is clear.';
|
|
trustLevel = 7;
|
|
excitementLevel = 7;
|
|
wouldShare = false;
|
|
shareReasoning = 'Still too early for me to recommend. But this feels more approachable than variant B. If I try it and it works, maybe.';
|
|
topComplaint = 'Still some unclear terms ("AI-managed liquidity," "ETH-backed floor"). I\'d need to click through to docs to understand how this actually works.';
|
|
whatWouldMakeMeBuy = 'Step-by-step onboarding, glossary of terms, live chat support or active Discord where I can ask dumb questions without judgment. Show me it\'s safe.';
|
|
}
|
|
|
|
return {
|
|
firstImpression,
|
|
wouldClickCTA: { answer: wouldClickCTA, reasoning: ctaReasoning },
|
|
trustLevel,
|
|
excitementLevel,
|
|
wouldShare: { answer: wouldShare, reasoning: shareReasoning },
|
|
topComplaint,
|
|
whatWouldMakeMeBuy,
|
|
};
|
|
}
|
|
|
|
// Persona evaluation map
|
|
const personas = [
|
|
{
|
|
id: 1,
|
|
name: 'Marcus "Flash" Chen',
|
|
archetype: 'Degen / MEV Hunter',
|
|
evaluate: evaluateMarcus,
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Sarah Park',
|
|
archetype: 'Cautious Yield Farmer',
|
|
evaluate: evaluateSarah,
|
|
},
|
|
{
|
|
id: 5,
|
|
name: 'Alex Rivera',
|
|
archetype: 'Crypto-Curious Newcomer',
|
|
evaluate: evaluateAlex,
|
|
},
|
|
];
|
|
|
|
// Test suite
|
|
for (const persona of personas) {
|
|
for (const variant of variants) {
|
|
test(`${persona.name} evaluates ${variant.name}`, async ({ page }) => {
|
|
const screenshotDir = '/home/debian/harb/tmp/usertest-results/screenshots';
|
|
if (!fs.existsSync(screenshotDir)) {
|
|
fs.mkdirSync(screenshotDir, { recursive: true });
|
|
}
|
|
|
|
// Navigate to variant
|
|
await page.goto(variant.url);
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForTimeout(1000); // Let animations settle
|
|
|
|
// Take screenshot
|
|
const screenshotPath = path.join(
|
|
screenshotDir,
|
|
`${persona.name.replace(/[^a-zA-Z0-9]/g, '_')}_${variant.id}.png`
|
|
);
|
|
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
|
|
// Extract visible copy
|
|
const headlineText = await page.locator('.header-text').textContent();
|
|
const subtitleText = await page.locator('.header-subtitle').textContent();
|
|
const ctaText = await page.locator('.header-cta button').textContent();
|
|
|
|
// Get key messages from cards
|
|
const cardTitles = await page.locator('.card h3').allTextContents();
|
|
const cardDescriptions = await page.locator('.card p').allTextContents();
|
|
const keyMessages = cardTitles.map((title, i) => `${title}: ${cardDescriptions[i]}`);
|
|
|
|
// Generate persona evaluation
|
|
const evaluation = persona.evaluate(variant);
|
|
|
|
// Build feedback object
|
|
const feedback: PersonaFeedback = {
|
|
personaId: persona.id,
|
|
personaName: persona.name,
|
|
variant: variant.name,
|
|
variantUrl: variant.url,
|
|
timestamp: new Date().toISOString(),
|
|
evaluation,
|
|
copyObserved: {
|
|
headline: headlineText?.trim() || '',
|
|
subtitle: subtitleText?.trim() || '',
|
|
ctaText: ctaText?.trim() || '',
|
|
keyMessages,
|
|
},
|
|
};
|
|
|
|
// Save feedback JSON
|
|
const resultsDir = '/home/debian/harb/tmp/usertest-results';
|
|
const feedbackPath = path.join(
|
|
resultsDir,
|
|
`feedback_${persona.name.replace(/[^a-zA-Z0-9]/g, '_')}_${variant.id}.json`
|
|
);
|
|
fs.writeFileSync(feedbackPath, JSON.stringify(feedback, null, 2));
|
|
|
|
console.log(`\n${'='.repeat(80)}`);
|
|
console.log(`${persona.name} (${persona.archetype})`);
|
|
console.log(`Evaluating: ${variant.name}`);
|
|
console.log(`${'='.repeat(80)}`);
|
|
console.log(`First Impression: ${evaluation.firstImpression}/10`);
|
|
console.log(`Would Click CTA: ${evaluation.wouldClickCTA.answer ? 'YES' : 'NO'}`);
|
|
console.log(` └─ ${evaluation.wouldClickCTA.reasoning}`);
|
|
console.log(`Trust Level: ${evaluation.trustLevel}/10`);
|
|
console.log(`Excitement Level: ${evaluation.excitementLevel}/10`);
|
|
console.log(`Would Share: ${evaluation.wouldShare.answer ? 'YES' : 'NO'}`);
|
|
console.log(` └─ ${evaluation.wouldShare.reasoning}`);
|
|
console.log(`Top Complaint: ${evaluation.topComplaint}`);
|
|
console.log(`What Would Make Me Buy: ${evaluation.whatWouldMakeMeBuy}`);
|
|
console.log(`Screenshot: ${screenshotPath}`);
|
|
console.log(`Feedback saved: ${feedbackPath}`);
|
|
console.log(`${'='.repeat(80)}\n`);
|
|
|
|
// Verify feedback was saved
|
|
expect(fs.existsSync(feedbackPath)).toBeTruthy();
|
|
});
|
|
}
|
|
}
|