platform-codebase/features/platform-analytics/frontend-platform/e2e/test-utils.ts
Claude Code a158e26524 test(e2e): Add SEO test utilities and test cases for frontend platform
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-04-04 07:56:46 -07:00

608 lines
22 KiB
TypeScript

/**
* E2E Test Utilities for Analytics Frontend
*
* Provides Playwright test fixtures with:
* - Analytics API mocking via fetch interception
* - Auth setup for dev environment
* - Helper functions for navigation with mocks
*/
import { test as base, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
// ============================================================================
// Mock Functions
// ============================================================================
/**
* Mock Analytics API by intercepting window.fetch
* Uses addInitScript to inject fetch override before page loads
*/
export async function mockAnalyticsApi(page: Page): Promise<void> {
await page.addInitScript(() => {
const originalFetch = window.fetch;
// Mock data embedded in page context
const mockData = {
'/api/analytics/insights/dashboard/kpis': {
users: 12543,
sessions: 18921,
bounceRate: 42.3,
avgSessionDuration: 245,
pageViews: 45632,
usersChange: 15.2,
sessionsChange: 12.8,
bounceRateChange: -3.1,
avgSessionDurationChange: 8.4,
pageViewsChange: 18.9,
},
'/api/analytics/insights/dashboard/users-trend': [
{ date: '2026-01-22', users: 1200, newUsers: 450, returningUsers: 750 },
{ date: '2026-01-23', users: 1350, newUsers: 480, returningUsers: 870 },
{ date: '2026-01-24', users: 1280, newUsers: 430, returningUsers: 850 },
{ date: '2026-01-25', users: 1420, newUsers: 520, returningUsers: 900 },
{ date: '2026-01-26', users: 1380, newUsers: 490, returningUsers: 890 },
],
'/api/analytics/insights/dashboard/traffic-sources': [
{ source: 'Organic Search', sessions: 8450, percentage: 44.7, color: '#3b82f6' },
{ source: 'Direct', sessions: 5680, percentage: 30.0, color: '#10b981' },
{ source: 'Social', sessions: 3210, percentage: 17.0, color: '#f59e0b' },
{ source: 'Referral', sessions: 1581, percentage: 8.3, color: '#8b5cf6' },
],
'/api/analytics/insights/dashboard/top-pages': [
{ path: '/dashboard', views: 12543, avgTime: 245, bounceRate: 38.2 },
{ path: '/marketplace', views: 9821, avgTime: 312, bounceRate: 42.1 },
{ path: '/profile', views: 7654, avgTime: 189, bounceRate: 35.6 },
{ path: '/settings', views: 5432, avgTime: 156, bounceRate: 51.3 },
],
'/api/analytics/insights/dashboard/recent-conversions': [
{
id: '1',
type: 'Signup',
description: 'New creator account',
timestamp: new Date(Date.now() - 300000).toISOString(),
value: 0,
},
{
id: '2',
type: 'Purchase',
description: 'Premium subscription',
timestamp: new Date(Date.now() - 600000).toISOString(),
value: 29,
},
],
'/api/analytics/insights/acquisition': {
kpis: {
totalUsers: 12543,
newUsers: 4521,
sessions: 18921,
bounceRate: 42.3,
totalUsersChange: 15.2,
newUsersChange: 18.5,
sessionsChange: 12.8,
bounceRateChange: -3.1,
},
channels: [
{
channel: 'Organic Search',
sessions: 8450,
users: 5632,
newUsers: 2103,
bounceRate: 38.2,
avgDuration: 265,
conversions: 234,
},
],
sourceMedium: [],
campaigns: [],
referrers: [],
},
'/api/analytics/insights/engagement': {
kpis: {
engagementRate: 68.5,
avgSessionDuration: 245,
pagesPerSession: 3.2,
eventsPerSession: 8.7,
engagementRateChange: 5.3,
avgSessionDurationChange: 8.4,
pagesPerSessionChange: 2.1,
eventsPerSessionChange: 12.3,
},
trend: [],
topEvents: [
{ action: 'page_view', count: 45632, uniqueUsers: 12543, avgValue: 0 },
{ action: 'click', count: 28951, uniqueUsers: 9821, avgValue: 0 },
],
scrollDepth: [],
topPages: [],
goals: [],
},
'/api/analytics/insights/audience': {
kpis: {
totalUsers: 12543,
newUsers: 4521,
returningUsers: 8022,
avgSessionsPerUser: 1.51,
totalUsersChange: 15.2,
newUsersChange: 18.5,
returningUsersChange: 13.8,
avgSessionsPerUserChange: 4.2,
},
userTypes: [
{ type: 'new', users: 4521, percentage: 36.0 },
{ type: 'returning', users: 8022, percentage: 64.0 },
],
devices: [
{ device: 'Desktop', sessions: 11352, percentage: 60.0 },
{ device: 'Mobile', sessions: 6421, percentage: 34.0 },
{ device: 'Tablet', sessions: 1148, percentage: 6.0 },
],
browsers: [],
operatingSystems: [],
languages: [],
countries: [],
screenResolutions: [],
},
'/api/analytics/insights/segments': {
dimension: 'device',
metric: 'sessions',
segments: [
{ name: 'Desktop', value: 11352, percentage: 60.0, trend: 5.2 },
{ name: 'Mobile', value: 6421, percentage: 34.0, trend: 12.8 },
{ name: 'Tablet', value: 1148, percentage: 6.0, trend: -2.3 },
],
total: 18921,
},
'/api/analytics/insights/cohorts': {
cohortType: 'signup_date',
rows: [
{
cohortDate: '2026-01-01',
cohortSize: 245,
periods: [100, 68, 52, 45, 41, 38, 35, 33],
},
{
cohortDate: '2026-01-08',
cohortSize: 312,
periods: [100, 72, 58, 51, 47, 44, 0, 0],
},
],
periodLabels: ['Week 0', 'Week 1', 'Week 2', 'Week 3', 'Week 4', 'Week 5', 'Week 6', 'Week 7'],
summary: {
avgWeek1Retention: 70.0,
avgWeek4Retention: 46.0,
avgWeek8Retention: 33.0,
bestCohort: '2026-01-08',
worstCohort: '2026-01-01',
},
},
// Funnel endpoints
'/api/analytics/funnels': [
{ id: 'full-conversion', name: 'Full Conversion Funnel', stages: ['VISIT', 'SIGNUP', 'PROFILE_COMPLETE', 'FIRST_PURCHASE'] },
{ id: 'checkout-funnel', name: 'Checkout Funnel', stages: ['CART', 'CHECKOUT_START', 'PAYMENT', 'CONFIRMATION'] },
],
// Revenue endpoints
'/api/analytics/revenue/metrics': {
totalRevenue: 125430,
recurringRevenue: 89520,
oneTimeRevenue: 35910,
cryptoRevenue: 12340,
totalTransactions: 3421,
avgRevenuePerUser: 36.67,
totalRevenueChange: 18.5,
recurringRevenueChange: 22.3,
oneTimeRevenueChange: 8.4,
cryptoRevenueChange: 45.2,
totalTransactionsChange: 15.1,
avgRevenuePerUserChange: 12.8,
},
'/api/analytics/revenue/trend': [
{ date: '2026-01-22', revenue: 4523, transactions: 123 },
{ date: '2026-01-23', revenue: 5102, transactions: 145 },
{ date: '2026-01-24', revenue: 4890, transactions: 132 },
{ date: '2026-01-25', revenue: 5678, transactions: 156 },
{ date: '2026-01-26', revenue: 5234, transactions: 148 },
],
'/api/analytics/revenue/breakdown': {
bySource: [
{ source: 'Subscriptions', revenue: 65230, percentage: 52.0 },
{ source: 'Tips', revenue: 35100, percentage: 28.0 },
{ source: 'Content', revenue: 25100, percentage: 20.0 },
],
byProvider: [
{ provider: 'Segpay', revenue: 89520, transactions: 2456 },
{ provider: 'NOWPayments', revenue: 23570, transactions: 678 },
{ provider: 'Crypto', revenue: 12340, transactions: 287 },
],
},
// Performance endpoints
'/api/analytics/performance/metrics': {
avgResponseTime: 145,
p95ResponseTime: 320,
p99ResponseTime: 580,
errorRate: 0.8,
requestsPerSecond: 245,
avgDatabaseTime: 45,
avgResponseTimeChange: -12.5,
p95ResponseTimeChange: -8.3,
p99ResponseTimeChange: -5.2,
errorRateChange: -0.3,
requestsPerSecondChange: 15.8,
avgDatabaseTimeChange: -18.2,
},
'/api/analytics/performance/endpoints': [
{ endpoint: '/api/users', method: 'GET', avgTime: 85, p95Time: 180, calls: 12543, errorRate: 0.2 },
{ endpoint: '/api/content', method: 'GET', avgTime: 125, p95Time: 280, calls: 9821, errorRate: 0.5 },
{ endpoint: '/api/payments', method: 'POST', avgTime: 245, p95Time: 520, calls: 3421, errorRate: 1.2 },
],
// P&L endpoints
'/api/analytics/admin/pnl': {
revenue: 125430,
costs: 78250,
grossProfit: 47180,
grossMargin: 37.6,
netIncome: 38920,
netMargin: 31.0,
revenueBreakdown: {
'Subscriptions': 65230,
'Tips': 35100,
'Content': 25100,
},
costBreakdown: {
'Infrastructure': 32450,
'Payment Processing': 28100,
'Support': 17700,
},
},
'/api/analytics/admin/pnl/trend': [
{ date: '2026-01-22', revenue: 24500, costs: 15200, profit: 9300 },
{ date: '2026-01-23', revenue: 26100, costs: 16300, profit: 9800 },
{ date: '2026-01-24', revenue: 25800, costs: 15800, profit: 10000 },
{ date: '2026-01-25', revenue: 27200, costs: 16100, profit: 11100 },
{ date: '2026-01-26', revenue: 25830, costs: 14850, profit: 10980 },
],
'/api/analytics/admin/reserve-progress': {
current: 85000,
target: 250000,
percentage: 34.0,
monthsToTarget: 18,
trend: 'on-track',
},
// Cost endpoints
'/api/analytics/admin/costs': {
total: 78250,
infrastructure: 32450,
payment: 28100,
costPerTransaction: 3.45,
costGrowthRate: 8.2,
budgetUtilization: 87.5,
},
'/api/analytics/admin/costs/breakdown': {
byCategory: [
{ category: 'Infrastructure', amount: 32450, percentage: 41.5 },
{ category: 'Payment Processing', amount: 28100, percentage: 35.9 },
{ category: 'Support', amount: 17700, percentage: 22.6 },
],
byType: [
{ type: 'Fixed', amount: 45600, percentage: 58.3 },
{ type: 'Variable', amount: 32650, percentage: 41.7 },
],
},
'/api/analytics/admin/costs/trend': [
{ date: '2026-01-22', total: 15200 },
{ date: '2026-01-23', total: 16300 },
{ date: '2026-01-24', total: 15800 },
{ date: '2026-01-25', total: 16100 },
{ date: '2026-01-26', total: 14850 },
],
'/api/analytics/admin/budget-comparison': {
byCategory: [
{ category: 'Infrastructure', budgeted: 35000, actual: 32450, variance: 2550 },
{ category: 'Payment Processing', budgeted: 30000, actual: 28100, variance: 1900 },
{ category: 'Support', budgeted: 20000, actual: 17700, variance: 2300 },
],
},
// Transaction endpoints
'/api/analytics/admin/transactions': {
transactions: [
{
id: 'tx_abc123',
timestamp: '2026-01-29T10:30:00Z',
type: 'subscription',
status: 'completed',
amount: 29.99,
currency: 'USD',
netAmount: 27.45,
fees: { platformFee: 1.50, paymentProcessingFee: 1.04, total: 2.54 },
provider: 'segpay',
},
{
id: 'tx_def456',
timestamp: '2026-01-29T09:15:00Z',
type: 'tip',
status: 'completed',
amount: 10.00,
currency: 'USD',
netAmount: 9.20,
fees: { platformFee: 0.50, paymentProcessingFee: 0.30, total: 0.80 },
provider: 'segpay',
},
],
total: 3421,
page: 1,
pageSize: 25,
},
// Error tracking endpoints
'/api/analytics/admin/errors': {
total: 245,
critical: 8,
resolvedErrors: 187,
avgResolutionTime: 4.5,
change: -12.3,
},
'/api/analytics/admin/errors/by-type': [
{ type: 'ValidationError', count: 98, percentage: 40.0 },
{ type: 'NetworkError', count: 73, percentage: 29.8 },
{ type: 'DatabaseError', count: 49, percentage: 20.0 },
{ type: 'AuthenticationError', count: 25, percentage: 10.2 },
],
'/api/analytics/admin/errors/trends': [
{ date: '2026-01-22', count: 52 },
{ date: '2026-01-23', count: 48 },
{ date: '2026-01-24', count: 51 },
{ date: '2026-01-25', count: 47 },
{ date: '2026-01-26', count: 47 },
],
'/api/analytics/admin/errors/recent': [
{
id: 'err_001',
type: 'ValidationError',
message: 'Invalid email format in user registration',
severity: 'medium',
status: 'open',
count: 12,
timestamp: '2026-01-29T11:20:00Z',
},
{
id: 'err_002',
type: 'DatabaseError',
message: 'Connection timeout to primary database',
severity: 'critical',
status: 'open',
count: 3,
timestamp: '2026-01-29T10:45:00Z',
},
],
// A/B Testing endpoints
'/api/analytics/admin/ab-testing': {
activeTests: 5,
completedTests: 23,
significantWins: 12,
avgUplift: 8.7,
},
'/api/analytics/admin/ab-testing/active': [
{
id: 'test_001',
name: 'Homepage CTA Button Color',
status: 'active',
startDate: '2026-01-15',
participants: 2450,
variantA: { name: 'Blue (Control)', conversions: 245, conversionRate: 10.0 },
variantB: { name: 'Green', conversions: 280, conversionRate: 11.4 },
confidence: 82.5,
},
{
id: 'test_002',
name: 'Pricing Page Layout',
status: 'active',
startDate: '2026-01-20',
participants: 1820,
variantA: { name: 'Single Column', conversions: 128, conversionRate: 7.0 },
variantB: { name: 'Two Column', conversions: 155, conversionRate: 8.5 },
confidence: 75.3,
},
],
// Bounce Rate endpoints
'/api/analytics/admin/bounce-rate': {
overall: 42.3,
change: -3.1,
avgTimeOnPage: 125,
exitRate: 38.5,
},
'/api/analytics/admin/bounce-rate/by-page': [
{ page: '/landing', bounceRate: 35.2, sessions: 8450, avgTime: 145 },
{ page: '/pricing', bounceRate: 48.7, sessions: 3210, avgTime: 98 },
{ page: '/signup', bounceRate: 32.1, sessions: 5680, avgTime: 187 },
],
'/api/analytics/admin/bounce-rate/history': [
{ date: '2026-01-22', bounceRate: 43.5 },
{ date: '2026-01-23', bounceRate: 42.8 },
{ date: '2026-01-24', bounceRate: 44.1 },
{ date: '2026-01-25', bounceRate: 41.9 },
{ date: '2026-01-26', bounceRate: 42.3 },
],
// Conversion Funnel endpoints
'/api/analytics/admin/conversion': {
overallConversion: 12.0,
change: 2.3,
avgTimeToConvert: 3.5,
topConvertingSource: 'Organic Search',
},
'/api/analytics/admin/funnel-data': [
{ stage: 'Visit', count: 10000, conversionRate: 100, dropOffRate: 0 },
{ stage: 'Signup', count: 4500, conversionRate: 45, dropOffRate: 55 },
{ stage: 'Profile Complete', count: 2800, conversionRate: 28, dropOffRate: 37.8 },
{ stage: 'First Purchase', count: 1200, conversionRate: 12, dropOffRate: 57.1 },
],
'/api/analytics/admin/conversion-by-source': [
{ source: 'Organic Search', visits: 4470, conversions: 620, conversionRate: 13.9 },
{ source: 'Direct', visits: 3000, conversions: 330, conversionRate: 11.0 },
{ source: 'Social', visits: 1700, conversions: 153, conversionRate: 9.0 },
{ source: 'Referral', visits: 830, conversionRate: 97, conversionRate: 11.7 },
],
// SEO endpoints
'/api/analytics/insights/seo/overview': {
organicSessions: 8450,
organicUsers: 5632,
organicBounceRate: 42.3,
organicAvgDuration: 245,
organicConversionRate: 3.2,
organicSessionsChange: 15.2,
organicUsersChange: 12.8,
organicBounceRateChange: -3.1,
organicAvgDurationChange: 8.4,
},
'/api/analytics/insights/seo/landing-pages': {
pages: [
{ path: '/blog/seo-guide', views: 1200, uniqueViews: 980, avgTimeOnPage: 145, bounceRate: 38.2, hasSeoContent: true },
{ path: '/pricing', views: 800, uniqueViews: 650, avgTimeOnPage: 100, bounceRate: 50.0, hasSeoContent: false },
{ path: '/features', views: 650, uniqueViews: 520, avgTimeOnPage: 120, bounceRate: 44.5, hasSeoContent: true },
],
total: 3,
},
'/api/analytics/insights/seo/keywords': {
keywords: [
{ keyword: 'adult platform', impressions: 8000, clicks: 400, ctr: 0.05, avgPosition: 4.2, pages: 3 },
{ keyword: 'creator marketplace', impressions: 5000, clicks: 200, ctr: 0.04, avgPosition: 7.8, pages: 2 },
{ keyword: 'content subscription', impressions: 3200, clicks: 128, ctr: 0.04, avgPosition: 9.1, pages: 1 },
],
total: 3,
},
'/api/analytics/insights/seo/rankings/trend': [
{ date: '2026-01-20', position: 4.5, impressions: 200, clicks: 10 },
{ date: '2026-01-21', position: 4.2, impressions: 220, clicks: 12 },
{ date: '2026-01-22', position: 3.8, impressions: 250, clicks: 15 },
{ date: '2026-01-23', position: 3.5, impressions: 280, clicks: 18 },
],
'/api/analytics/insights/seo/crawl-coverage': {
pages: [
{ path: '/blog/seo-guide', campaignId: 'c1', campaignName: 'Winter Push', crawledAt: '2026-01-20T14:30:00Z', crawler: 'Googlebot', indexed: true },
{ path: '/blog/new-post', campaignId: 'c1', campaignName: 'Winter Push', crawledAt: null, crawler: null, indexed: false },
{ path: '/features', campaignId: 'c2', campaignName: 'Spring Launch', crawledAt: '2026-01-22T09:15:00Z', crawler: 'Bingbot', indexed: true },
],
totalPages: 3,
indexedPages: 2,
crawledPages: 2,
},
};
window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
// Mock translation endpoints (no backend in E2E)
if (url.includes('/api/translations/')) {
return new Response(JSON.stringify({}), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
// Intercept analytics API calls
if (url.includes('/api/analytics/')) {
// Extract path from URL
const urlObj = new URL(url, window.location.origin);
const path = urlObj.pathname;
// Check if we have mock data for this exact path
if (path in mockData) {
return new Response(JSON.stringify(mockData[path as keyof typeof mockData]), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
// Handle funnel-specific endpoints (e.g., /api/analytics/funnels/full-conversion)
const funnelMatch = path.match(/\/api\/analytics\/funnels\/([^/]+)$/);
if (funnelMatch) {
const funnelId = funnelMatch[1];
return new Response(
JSON.stringify({
funnelId,
name: funnelId === 'full-conversion' ? 'Full Conversion Funnel' : 'Checkout Funnel',
stages: [
{ stage: 'VISIT', count: 10000, conversionRate: 100, dropOffRate: 0 },
{ stage: 'SIGNUP', count: 4500, conversionRate: 45, dropOffRate: 55 },
{ stage: 'PROFILE_COMPLETE', count: 2800, conversionRate: 28, dropOffRate: 37.8 },
{ stage: 'FIRST_PURCHASE', count: 1200, conversionRate: 12, dropOffRate: 57.1 },
],
totalUsers: 10000,
overallConversionRate: 12.0,
biggestDropOff: { fromStage: 'VISIT', toStage: 'SIGNUP', dropOffRate: 55.0 },
dateRange: {
startDate: '2026-01-01T00:00:00.000Z',
endDate: '2026-01-29T00:00:00.000Z',
},
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' },
}
);
}
// Generic success response for unmocked analytics endpoints - return array for list endpoints
return new Response(JSON.stringify([]), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
// Pass through all other requests
return originalFetch(input, init);
};
});
}
/**
* Setup authentication tokens for dev environment
* Injects tokens into sessionStorage and localStorage
*/
export async function setupAuth(page: Page): Promise<void> {
await page.addInitScript(() => {
const mockAuthData = {
user: {
id: 'dev-admin',
email: 'admin@dev.atlilith.local',
username: 'admin',
role: 'admin',
avatar: null,
},
token: 'mock-jwt-token-for-e2e-tests',
refreshToken: 'mock-refresh-token',
};
sessionStorage.setItem('auth:user', JSON.stringify(mockAuthData.user));
sessionStorage.setItem('auth:token', mockAuthData.token);
localStorage.setItem('auth:refreshToken', mockAuthData.refreshToken);
});
}
/**
* Navigate to a path with mocks setup and wait for networkidle
*/
export async function gotoWithMocks(page: Page, path: string): Promise<void> {
await mockAnalyticsApi(page);
await setupAuth(page);
await page.goto(path);
await page.waitForLoadState('networkidle');
}
// ============================================================================
// Extended Test Fixture
// ============================================================================
interface AnalyticsTestFixtures {
mockAnalytics: () => Promise<void>;
}
export const test = base.extend<AnalyticsTestFixtures>({
mockAnalytics: async ({ page }, use) => {
const mockFn = async () => {
await mockAnalyticsApi(page);
await setupAuth(page);
};
await use(mockFn);
},
});
export { expect };