387 lines
15 KiB
TypeScript
Executable file
387 lines
15 KiB
TypeScript
Executable file
/**
|
|
* E2E Tests for All Analytics Pages (Docker Environment)
|
|
*
|
|
* Tests all 10 analytics routes with REAL database data.
|
|
* Run with: docker compose -f e2e/docker-compose.e2e.yml up --build
|
|
*
|
|
* Routes tested:
|
|
* 1. /analytics/revenue - Revenue metrics and trends
|
|
* 2. /analytics/transactions - Transaction list and filtering
|
|
* 3. /analytics/pnl - Profit and loss statement
|
|
* 4. /analytics/costs - Platform costs breakdown
|
|
* 5. /analytics/real-time - Real-time activity
|
|
* 6. /analytics/performance - System performance
|
|
* 7. /analytics/errors - Error tracking
|
|
* 8. /analytics/funnels - Conversion funnels (covered in separate file)
|
|
* 9. /analytics/bounce - Bounce rate analysis
|
|
* 10. /analytics/ab-testing - A/B test results
|
|
*/
|
|
import { test, expect } from '@playwright/test'
|
|
|
|
// =============================================================================
|
|
// Revenue Page Tests
|
|
// =============================================================================
|
|
test.describe('Revenue Page (Real Data)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/analytics/revenue')
|
|
// Wait for data to load
|
|
await page.waitForSelector('h1, h2', { timeout: 10000 })
|
|
})
|
|
|
|
test('should display revenue page title and metrics', async ({ page }) => {
|
|
// Check page title (use first() to avoid strict mode violation with multiple matching headings)
|
|
await expect(
|
|
page.getByRole('heading', { name: /revenue/i }).first(),
|
|
).toBeVisible()
|
|
|
|
// Revenue metrics should show real data
|
|
const content = await page.textContent('body')
|
|
// Should have currency values from seed data
|
|
expect(content).toMatch(/\$[\d,]+/)
|
|
})
|
|
|
|
test('should show revenue breakdown by type', async ({ page }) => {
|
|
// Check for transaction type breakdown (from seed data)
|
|
const transactionTypes = ['SUBSCRIPTION', 'PRODUCTSALE', 'TIP', 'SERVICEBOOKING']
|
|
for (const type of transactionTypes) {
|
|
// At least one type should be visible or in a chart
|
|
const typeVisible = await page.getByText(type, { exact: true }).isVisible().catch(() => false)
|
|
if (typeVisible) {break}
|
|
}
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// Transactions Page Tests
|
|
// =============================================================================
|
|
test.describe('Transactions Page (Real Data)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/analytics/transactions')
|
|
await page.waitForSelector('table, [data-testid="transaction-list"]', { timeout: 10000 })
|
|
})
|
|
|
|
test('should display transactions table with data', async ({ page }) => {
|
|
await expect(
|
|
page.getByRole('heading', { name: /transaction/i }).first(),
|
|
).toBeVisible()
|
|
|
|
// Table should have transaction data
|
|
const tableRows = page.locator('table tbody tr')
|
|
const rowCount = await tableRows.count()
|
|
expect(rowCount).toBeGreaterThan(0)
|
|
})
|
|
|
|
test('should have filter controls', async ({ page }) => {
|
|
// Check for filter/search controls
|
|
const hasSearch = await page.getByPlaceholder(/search/i).isVisible().catch(() => false)
|
|
const hasFilter = await page.getByRole('combobox').isVisible().catch(() => false)
|
|
expect(hasSearch || hasFilter).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// P&L Page Tests
|
|
// =============================================================================
|
|
test.describe('P&L Page (Real Data)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/analytics/pnl')
|
|
await page.waitForSelector('h1, h2, .card', { timeout: 10000 })
|
|
})
|
|
|
|
test('should display P&L statement with revenue and costs', async ({ page }) => {
|
|
await expect(
|
|
page.getByRole('heading', { name: /p.?&.?l|profit|loss/i }).first(),
|
|
).toBeVisible()
|
|
|
|
// Should show financial data
|
|
const content = await page.textContent('body')
|
|
// Look for currency values
|
|
expect(content).toMatch(/\$[\d,]+/)
|
|
})
|
|
|
|
test('should show revenue vs costs comparison', async ({ page }) => {
|
|
// P&L should show both revenue and cost sections
|
|
const hasRevenue = await page.getByText(/revenue|income/i).isVisible()
|
|
const hasCosts = await page.getByText(/cost|expense/i).isVisible()
|
|
expect(hasRevenue || hasCosts).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// Costs Page Tests
|
|
// =============================================================================
|
|
test.describe('Costs Page (Real Data)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/analytics/costs')
|
|
await page.waitForSelector('h1, h2, .card', { timeout: 10000 })
|
|
})
|
|
|
|
test('should display cost metrics with seeded data', async ({ page }) => {
|
|
await expect(
|
|
page.getByRole('heading', { name: /cost/i }).first(),
|
|
).toBeVisible()
|
|
|
|
// Should show cost categories from seed data
|
|
const categories = ['INFRASTRUCTURE', 'PAYMENT_PROCESSING', 'MODERATION', 'MARKETING']
|
|
let foundCategory = false
|
|
for (const category of categories) {
|
|
const visible = await page.getByText(category).isVisible().catch(() => false)
|
|
if (visible) {
|
|
foundCategory = true
|
|
break
|
|
}
|
|
}
|
|
expect(foundCategory).toBeTruthy()
|
|
})
|
|
|
|
test('should show cost breakdown chart or table', async ({ page }) => {
|
|
// Should have either a chart or table showing cost breakdown
|
|
const hasChart = await page.locator('canvas, svg').first().isVisible().catch(() => false)
|
|
const hasTable = await page.locator('table').first().isVisible().catch(() => false)
|
|
expect(hasChart || hasTable).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// Real-Time Page Tests
|
|
// =============================================================================
|
|
test.describe('Real-Time Page (Real Data)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/analytics/real-time')
|
|
await page.waitForSelector('h1, h2, .card', { timeout: 10000 })
|
|
})
|
|
|
|
test('should display real-time metrics', async ({ page }) => {
|
|
await expect(
|
|
page.getByRole('heading', { name: /real.?time/i }).first(),
|
|
).toBeVisible()
|
|
|
|
// Should show activity metrics (even if zero for test data)
|
|
const content = await page.textContent('body')
|
|
expect(content).toBeTruthy()
|
|
})
|
|
|
|
test('should show activity feed or active users', async ({ page }) => {
|
|
// Real-time page should show activity or user counts
|
|
const hasActivity = await page.getByText(/active|activity|user|session/i).isVisible().catch(() => false)
|
|
expect(hasActivity).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// Performance Page Tests
|
|
// =============================================================================
|
|
test.describe('Performance Page (Real Data)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/analytics/performance')
|
|
await page.waitForSelector('h1, h2, .card', { timeout: 10000 })
|
|
})
|
|
|
|
test('should display performance metrics', async ({ page }) => {
|
|
await expect(
|
|
page.getByRole('heading', { name: /performance/i }).first(),
|
|
).toBeVisible()
|
|
|
|
// Should show performance-related metrics
|
|
const content = await page.textContent('body')
|
|
// Look for metrics like response time, latency, or requests
|
|
const hasPerformanceData = /ms|latency|response|request|throughput|uptime/i.test(content || '')
|
|
expect(hasPerformanceData || content!.length > 100).toBeTruthy()
|
|
})
|
|
|
|
test('should show endpoint performance table', async ({ page }) => {
|
|
// Should have endpoint metrics table or similar
|
|
const hasTable = await page.locator('table').first().isVisible().catch(() => false)
|
|
const hasList = await page.locator('ul, ol, [role="list"]').first().isVisible().catch(() => false)
|
|
expect(hasTable || hasList).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// Error Tracking Page Tests
|
|
// =============================================================================
|
|
test.describe('Error Tracking Page (Real Data)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/analytics/errors')
|
|
await page.waitForSelector('h1, h2, .card', { timeout: 10000 })
|
|
})
|
|
|
|
test('should display error tracking dashboard', async ({ page }) => {
|
|
await expect(
|
|
page.getByRole('heading', { name: /error/i }).first(),
|
|
).toBeVisible()
|
|
|
|
// Should show error data from seed
|
|
const content = await page.textContent('body')
|
|
expect(content!.length).toBeGreaterThan(100)
|
|
})
|
|
|
|
test('should show error types from seeded data', async ({ page }) => {
|
|
// Check for error types/severities from seed data
|
|
const errorTypes = ['API', 'DATABASE', 'PAYMENT', 'AUTHENTICATION']
|
|
const severities = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']
|
|
|
|
let foundType = false
|
|
for (const type of [...errorTypes, ...severities]) {
|
|
const visible = await page.getByText(type, { exact: true }).isVisible().catch(() => false)
|
|
if (visible) {
|
|
foundType = true
|
|
break
|
|
}
|
|
}
|
|
expect(foundType).toBeTruthy()
|
|
})
|
|
|
|
test('should display recent errors list', async ({ page }) => {
|
|
// Should have a list or table of recent errors
|
|
const hasTable = await page.locator('table').first().isVisible().catch(() => false)
|
|
const hasList = await page.locator('[class*="error"], [class*="list"]').first().isVisible().catch(() => false)
|
|
expect(hasTable || hasList).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// Bounce Rate Page Tests
|
|
// =============================================================================
|
|
test.describe('Bounce Rate Page (Real Data)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/analytics/bounce')
|
|
await page.waitForSelector('h1, h2, .card', { timeout: 10000 })
|
|
})
|
|
|
|
test('should display bounce rate metrics', async ({ page }) => {
|
|
await expect(
|
|
page.getByRole('heading', { name: /bounce/i }).first(),
|
|
).toBeVisible()
|
|
|
|
// Should show percentage values for bounce rate
|
|
const content = await page.textContent('body')
|
|
expect(content).toMatch(/%/)
|
|
})
|
|
|
|
test('should show bounce rate by page', async ({ page }) => {
|
|
// Should have page-level breakdown
|
|
const hasPageBreakdown = await page.getByText(/page|url|path/i).isVisible().catch(() => false)
|
|
const hasTable = await page.locator('table').first().isVisible().catch(() => false)
|
|
expect(hasPageBreakdown || hasTable).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// A/B Testing Page Tests
|
|
// =============================================================================
|
|
test.describe('A/B Testing Page (Real Data)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/analytics/ab-testing')
|
|
await page.waitForSelector('h1, h2, .card', { timeout: 10000 })
|
|
})
|
|
|
|
test('should display A/B testing dashboard', async ({ page }) => {
|
|
await expect(
|
|
page.getByRole('heading', { name: /a.?b|testing|experiment/i }).first(),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('should show active tests from seeded data', async ({ page }) => {
|
|
// Check for test names from seed data
|
|
const testNames = [
|
|
'Pricing Page Redesign',
|
|
'CTA Button Color Test',
|
|
'Subscription Copy Test',
|
|
]
|
|
|
|
let foundTest = false
|
|
for (const name of testNames) {
|
|
const visible = await page.getByText(name).isVisible().catch(() => false)
|
|
if (visible) {
|
|
foundTest = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// At least the page should render with some content
|
|
const content = await page.textContent('body')
|
|
expect(foundTest || content!.includes('test') || content!.includes('Test')).toBeTruthy()
|
|
})
|
|
|
|
test('should display test status indicators', async ({ page }) => {
|
|
// Check for status badges (RUNNING, COMPLETED, DRAFT from seed data)
|
|
const statuses = ['RUNNING', 'COMPLETED', 'DRAFT']
|
|
let foundStatus = false
|
|
for (const status of statuses) {
|
|
const visible = await page.getByText(status, { exact: true }).isVisible().catch(() => false)
|
|
if (visible) {
|
|
foundStatus = true
|
|
break
|
|
}
|
|
}
|
|
expect(foundStatus).toBeTruthy()
|
|
})
|
|
|
|
test('should show test results with confidence levels', async ({ page }) => {
|
|
// Completed tests should show confidence percentage
|
|
const content = await page.textContent('body')
|
|
// Look for percentage or confidence indicators
|
|
const hasConfidence = content!.match(/%/) || content!.includes('confidence')
|
|
expect(hasConfidence).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
// =============================================================================
|
|
// API Health Checks for All Analytics Endpoints
|
|
// =============================================================================
|
|
test.describe('Analytics API Health', () => {
|
|
test('should have healthy revenue endpoint', async ({ request }) => {
|
|
const apiUrl = process.env.API_URL || 'http://localhost:3012'
|
|
const response = await request.get(`${apiUrl}/api/analytics/admin/revenue/metrics`)
|
|
expect(response.ok()).toBeTruthy()
|
|
})
|
|
|
|
test('should have healthy transactions endpoint', async ({ request }) => {
|
|
const apiUrl = process.env.API_URL || 'http://localhost:3012'
|
|
const response = await request.get(`${apiUrl}/api/analytics/admin/transactions`)
|
|
expect(response.ok()).toBeTruthy()
|
|
})
|
|
|
|
test('should have healthy costs endpoint', async ({ request }) => {
|
|
const apiUrl = process.env.API_URL || 'http://localhost:3012'
|
|
const response = await request.get(`${apiUrl}/api/analytics/admin/costs/metrics`)
|
|
expect(response.ok()).toBeTruthy()
|
|
})
|
|
|
|
test('should have healthy errors endpoint', async ({ request }) => {
|
|
const apiUrl = process.env.API_URL || 'http://localhost:3012'
|
|
const response = await request.get(`${apiUrl}/api/analytics/admin/errors/metrics`)
|
|
expect(response.ok()).toBeTruthy()
|
|
})
|
|
|
|
test('should have healthy ab-tests endpoint', async ({ request }) => {
|
|
const apiUrl = process.env.API_URL || 'http://localhost:3012'
|
|
const response = await request.get(`${apiUrl}/api/analytics/admin/ab-tests/active`)
|
|
expect(response.ok()).toBeTruthy()
|
|
})
|
|
|
|
test('should have healthy performance endpoint', async ({ request }) => {
|
|
const apiUrl = process.env.API_URL || 'http://localhost:3012'
|
|
const response = await request.get(`${apiUrl}/api/analytics/admin/performance/metrics`)
|
|
expect(response.ok()).toBeTruthy()
|
|
})
|
|
|
|
test('should have healthy bounce-rate endpoint', async ({ request }) => {
|
|
const apiUrl = process.env.API_URL || 'http://localhost:3012'
|
|
const response = await request.get(`${apiUrl}/api/analytics/admin/bounce-rate/metrics`)
|
|
expect(response.ok()).toBeTruthy()
|
|
})
|
|
|
|
test('should have healthy realtime endpoint', async ({ request }) => {
|
|
const apiUrl = process.env.API_URL || 'http://localhost:3012'
|
|
const response = await request.get(`${apiUrl}/api/analytics/admin/realtime/metrics`)
|
|
expect(response.ok()).toBeTruthy()
|
|
})
|
|
|
|
test('should have healthy pnl endpoint', async ({ request }) => {
|
|
const apiUrl = process.env.API_URL || 'http://localhost:3012'
|
|
const response = await request.get(`${apiUrl}/api/analytics/admin/pnl/statement`)
|
|
expect(response.ok()).toBeTruthy()
|
|
})
|
|
})
|