platform-codebase/features/platform-admin/frontend-admin/e2e/analytics-audience.e2e.ts
Lilith dcae150ea6 chore: snapshot before monorepo consolidation
Capture current working state before converting platform-codebase
into a submodule of the lilith-platform monorepo.
2026-01-29 07:04:30 -08:00

460 lines
18 KiB
TypeScript

/**
* Audience Analytics E2E Tests
*
* Verifies the audience analytics page renders correctly with mocked data.
* Tests user demographics, device breakdown, geography, languages, and new vs returning metrics.
*/
import { test, expect } from '@playwright/test';
import { applyAllMocks } from './fixtures/api-mocks';
import type { Page } from '@playwright/test';
/**
* Sign in as Admin using the dev user switcher.
* The platform-admin app defaults to "Employee" which doesn't have admin access.
* We need to switch to "Admin" using the dev user type switcher.
*/
async function signInAsDevAdmin(page: Page): Promise<void> {
// Wait for page to be ready
await page.waitForLoadState('domcontentloaded');
// First check if we're already on the dashboard (admin access)
const dashboardHeading = page.getByRole('heading', { name: /Platform Dashboard/i });
if (await dashboardHeading.isVisible({ timeout: 1000 }).catch(() => false)) {
return; // Already authenticated as admin
}
// Look for the dev user switcher button (shows "current: Guest" or "current: Employee")
const devSwitcher = page.getByRole('button', { name: /Dev user switcher/i });
if (await devSwitcher.isVisible({ timeout: 5000 }).catch(() => false)) {
// Click to open the switcher dropdown if not already expanded
const isExpanded = (await devSwitcher.getAttribute('aria-expanded')) === 'true';
if (!isExpanded) {
await devSwitcher.click();
await page.waitForTimeout(300);
}
// Click on "Admin" menuitemcheckbox to toggle it on
// This is a checkbox-style item that adds Admin access
const adminCheckbox = page.getByRole('menuitemcheckbox', { name: /Admin/i });
if (await adminCheckbox.isVisible({ timeout: 2000 }).catch(() => false)) {
// Check if not already checked
const isChecked = (await adminCheckbox.getAttribute('aria-checked')) === 'true';
if (!isChecked) {
await adminCheckbox.click();
await page.waitForTimeout(300);
}
}
// Now set Admin as primary - find the "Set Admin as primary" button
// After clicking Admin checkbox, we need to make it primary
const setPrimaryButton = page.getByRole('button', { name: /Set Admin as primary/i });
if (await setPrimaryButton.isVisible({ timeout: 1000 }).catch(() => false)) {
const isDisabled = await setPrimaryButton.isDisabled();
if (!isDisabled) {
await setPrimaryButton.click();
await page.waitForTimeout(300);
}
}
// Close the dropdown by clicking elsewhere or pressing Escape
await page.keyboard.press('Escape');
await page.waitForTimeout(200);
}
// After switching to admin, if we're on login page, click quick sign in
const quickSignInButton = page.getByRole('button', { name: /Quick Sign In as Admin/i });
if (await quickSignInButton.isVisible({ timeout: 2000 }).catch(() => false)) {
await quickSignInButton.click();
}
// Wait for navigation to complete
await page.waitForLoadState('networkidle');
}
test.describe('Audience Analytics', () => {
test.beforeEach(async ({ page }) => {
// Apply all API mocks before navigating
await applyAllMocks(page);
// Navigate to the app (will show login page first)
await page.goto('/');
await page.waitForLoadState('networkidle');
// Sign in using dev quick sign-in
await signInAsDevAdmin(page);
// Navigate to Audience Analytics page
await page.goto('/analytics/audience');
await page.waitForLoadState('networkidle');
});
test('should display page title', async ({ page }) => {
// Verify page title with data-testid
const pageTitle = page.getByTestId('page-title');
await expect(pageTitle).toBeVisible();
await expect(pageTitle).toContainText('Audience');
});
test('should display 6 KPI cards', async ({ page }) => {
// Verify KPI section exists
const kpiSection = page.getByTestId('kpi-section');
await expect(kpiSection).toBeVisible();
// Verify all 6 KPI cards are visible
await expect(page.getByTestId('total-users-card')).toBeVisible();
await expect(page.getByTestId('new-users-card')).toBeVisible();
await expect(page.getByTestId('returning-users-card')).toBeVisible();
await expect(page.getByTestId('sessions-per-user-card')).toBeVisible();
await expect(page.getByTestId('retention-rate-card')).toBeVisible();
await expect(page.getByTestId('new-user-rate-card')).toBeVisible();
// Verify card values from mock data (totalUsers: 28100, newUsers: 12400, returningUsers: 15700)
await expect(page.getByText('28.1K')).toBeVisible(); // Total Users
await expect(page.getByText('12.4K')).toBeVisible(); // New Users
await expect(page.getByText('15.7K')).toBeVisible(); // Returning Users
});
test('should display date range filter', async ({ page }) => {
// Verify date filter section exists
const dateFilter = page.getByTestId('date-filter');
await expect(dateFilter).toBeVisible();
// Verify filter buttons exist (7D, 30D, This Month, Last Month, Custom)
await expect(dateFilter.getByText('7D')).toBeVisible();
await expect(dateFilter.getByText('30D')).toBeVisible();
await expect(dateFilter.getByText('This Month')).toBeVisible();
await expect(dateFilter.getByText('Last Month')).toBeVisible();
await expect(dateFilter.getByText('Custom')).toBeVisible();
});
test('should show overview tab by default', async ({ page }) => {
// Verify tabs container exists
const audienceTabs = page.getByTestId('audience-tabs');
await expect(audienceTabs).toBeVisible();
// Verify overview tab is active by default
const overviewTab = page.getByTestId('tab-overview');
await expect(overviewTab).toBeVisible();
await expect(overviewTab).toHaveAttribute('aria-selected', 'true');
// Verify other tabs are not active
const devicesTab = page.getByTestId('tab-devices');
await expect(devicesTab).toHaveAttribute('aria-selected', 'false');
const geographyTab = page.getByTestId('tab-geography');
await expect(geographyTab).toHaveAttribute('aria-selected', 'false');
const languagesTab = page.getByTestId('tab-languages');
await expect(languagesTab).toHaveAttribute('aria-selected', 'false');
});
test('should display new vs returning comparison in overview tab', async ({ page }) => {
// Verify new vs returning section exists
const newVsReturningSection = page.getByTestId('new-vs-returning-section');
await expect(newVsReturningSection).toBeVisible();
// Verify new users stats card
const newUsersStats = page.getByTestId('new-users-stats');
await expect(newUsersStats).toBeVisible();
await expect(newUsersStats).toContainText('New Users');
// Verify returning users stats card
const returningUsersStats = page.getByTestId('returning-users-stats');
await expect(returningUsersStats).toBeVisible();
await expect(returningUsersStats).toContainText('Returning Users');
// Verify percentage values (newUserRate: 0.44 -> 44%, returningUsers percentage: 56%)
await expect(newVsReturningSection).toContainText('44%');
await expect(newVsReturningSection).toContainText('56%');
});
test('should switch to devices tab and display technology tables', async ({ page }) => {
// Click on devices tab
const devicesTab = page.getByTestId('tab-devices');
await devicesTab.click();
await page.waitForTimeout(300);
// Verify devices tab is now active
await expect(devicesTab).toHaveAttribute('aria-selected', 'true');
// Verify technology section exists
const technologySection = page.getByTestId('technology-section');
await expect(technologySection).toBeVisible();
// Verify all three technology tables are visible
const devicesTable = page.getByTestId('devices-table');
await expect(devicesTable).toBeVisible();
const browsersTable = page.getByTestId('browsers-table');
await expect(browsersTable).toBeVisible();
const osTable = page.getByTestId('os-table');
await expect(osTable).toBeVisible();
});
test('should display device type breakdown in devices table', async ({ page }) => {
// Click on devices tab
const devicesTab = page.getByTestId('tab-devices');
await devicesTab.click();
await page.waitForTimeout(300);
// Verify devices table contains all device types
await expect(page.getByTestId('device-desktop')).toBeVisible();
await expect(page.getByTestId('device-mobile')).toBeVisible();
await expect(page.getByTestId('device-tablet')).toBeVisible();
// Verify device percentages from mock data (desktop: 60%, mobile: 30%, tablet: 10%)
const devicesTable = page.getByTestId('devices-table');
await expect(devicesTable).toContainText('60%'); // Desktop
await expect(devicesTable).toContainText('30%'); // Mobile
await expect(devicesTable).toContainText('10%'); // Tablet
});
test('should display browser distribution in devices tab', async ({ page }) => {
// Click on devices tab
const devicesTab = page.getByTestId('tab-devices');
await devicesTab.click();
await page.waitForTimeout(300);
// Verify browsers table contains browser data
const browsersTable = page.getByTestId('browsers-table');
await expect(page.getByTestId('browser-chrome')).toBeVisible();
await expect(page.getByTestId('browser-safari')).toBeVisible();
await expect(page.getByTestId('browser-firefox')).toBeVisible();
// Verify browser percentages from mock data (Chrome: 50%, Safari: 26%, Firefox: 15%)
await expect(browsersTable).toContainText('Chrome');
await expect(browsersTable).toContainText('Safari');
await expect(browsersTable).toContainText('Firefox');
});
test('should switch to geography tab and display location data', async ({ page }) => {
// Click on geography tab
const geographyTab = page.getByTestId('tab-geography');
await geographyTab.click();
await page.waitForTimeout(300);
// Verify geography tab is now active
await expect(geographyTab).toHaveAttribute('aria-selected', 'true');
// Verify geography table exists
const geographyTable = page.getByTestId('geography-table');
await expect(geographyTable).toBeVisible();
// Verify granularity selector exists
const geoGranularitySelect = page.getByTestId('geo-granularity-select');
await expect(geoGranularitySelect).toBeVisible();
});
test('should display country data in geography table', async ({ page }) => {
// Click on geography tab
const geographyTab = page.getByTestId('tab-geography');
await geographyTab.click();
await page.waitForTimeout(300);
// Verify geography table contains location data
await expect(page.getByTestId('geo-iceland')).toBeVisible();
await expect(page.getByTestId('geo-united-states')).toBeVisible();
// Verify country percentages from mock data (Iceland: 30%, United States: 26%)
const geographyTable = page.getByTestId('geography-table');
await expect(geographyTable).toContainText('Iceland');
await expect(geographyTable).toContainText('United States');
await expect(geographyTable).toContainText('30%');
await expect(geographyTable).toContainText('26%');
});
test('should switch to languages tab and display language breakdown', async ({ page }) => {
// Click on languages tab
const languagesTab = page.getByTestId('tab-languages');
await languagesTab.click();
await page.waitForTimeout(300);
// Verify languages tab is now active
await expect(languagesTab).toHaveAttribute('aria-selected', 'true');
// Verify languages table exists
const languagesTable = page.getByTestId('languages-table');
await expect(languagesTable).toBeVisible();
// Verify language data from mock data (en: 71%, is: 20%)
await expect(languagesTable).toContainText('English');
await expect(languagesTable).toContainText('Icelandic');
await expect(languagesTable).toContainText('71%');
await expect(languagesTable).toContainText('20%');
});
test('should display export menu button', async ({ page }) => {
// Look for export button (typical location in analytics pages)
const exportButton = page.getByRole('button', { name: /Export/i });
await expect(exportButton).toBeVisible();
});
test('should toggle export menu', async ({ page }) => {
// Find and click export button
const exportButton = page.getByRole('button', { name: /Export/i });
await exportButton.click();
await page.waitForTimeout(300);
// Verify export menu is expanded
const isExpanded = (await exportButton.getAttribute('aria-expanded')) === 'true';
expect(isExpanded).toBe(true);
// Close menu by pressing Escape
await page.keyboard.press('Escape');
await page.waitForTimeout(200);
});
test('should handle API errors gracefully', async ({ page }) => {
// Create a new page with error responses
const errorPage = await page.context().newPage();
// Apply mocks first
await applyAllMocks(errorPage);
// Override audience analytics routes with errors
await errorPage.route('**/api/analytics/analytics/audience/**', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ message: 'Internal Server Error' }),
});
});
await errorPage.goto('/');
await errorPage.waitForLoadState('networkidle');
// Sign in
await signInAsDevAdmin(errorPage);
await errorPage.waitForLoadState('networkidle');
// Navigate to audience analytics
await errorPage.goto('/analytics/audience');
await errorPage.waitForLoadState('networkidle');
// Page should still render (graceful degradation)
const pageTitle = errorPage.getByTestId('page-title');
await expect(pageTitle).toBeVisible();
// KPI cards should show 0 or empty state instead of crashing
const kpiSection = errorPage.getByTestId('kpi-section');
await expect(kpiSection).toBeVisible();
await errorPage.close();
});
test('should take screenshot for visual verification', async ({ page }) => {
// Wait for all data to load
await page.waitForLoadState('networkidle');
// Wait for metrics to populate
await expect(page.getByText('28.1K')).toBeVisible();
await expect(page.getByText('12.4K')).toBeVisible();
// Take screenshot with threshold for minor rendering differences
await expect(page).toHaveScreenshot('analytics-audience-overview.png', {
maxDiffPixels: 100,
fullPage: true,
});
});
});
test.describe('Audience Analytics - Tab Navigation', () => {
test.beforeEach(async ({ page }) => {
await applyAllMocks(page);
await page.goto('/');
await page.waitForLoadState('networkidle');
await signInAsDevAdmin(page);
await page.goto('/analytics/audience');
await page.waitForLoadState('networkidle');
});
test('should navigate through all tabs sequentially', async ({ page }) => {
// Start on overview tab (default)
const overviewTab = page.getByTestId('tab-overview');
await expect(overviewTab).toHaveAttribute('aria-selected', 'true');
// Switch to devices tab
const devicesTab = page.getByTestId('tab-devices');
await devicesTab.click();
await page.waitForTimeout(300);
await expect(devicesTab).toHaveAttribute('aria-selected', 'true');
await expect(page.getByTestId('devices-table')).toBeVisible();
// Switch to geography tab
const geographyTab = page.getByTestId('tab-geography');
await geographyTab.click();
await page.waitForTimeout(300);
await expect(geographyTab).toHaveAttribute('aria-selected', 'true');
await expect(page.getByTestId('geography-table')).toBeVisible();
// Switch to languages tab
const languagesTab = page.getByTestId('tab-languages');
await languagesTab.click();
await page.waitForTimeout(300);
await expect(languagesTab).toHaveAttribute('aria-selected', 'true');
await expect(page.getByTestId('languages-table')).toBeVisible();
// Switch back to overview tab
await overviewTab.click();
await page.waitForTimeout(300);
await expect(overviewTab).toHaveAttribute('aria-selected', 'true');
await expect(page.getByTestId('new-vs-returning-section')).toBeVisible();
});
test('should take screenshot of devices tab', async ({ page }) => {
// Navigate to devices tab
const devicesTab = page.getByTestId('tab-devices');
await devicesTab.click();
await page.waitForTimeout(300);
// Wait for technology tables to load
await expect(page.getByTestId('devices-table')).toBeVisible();
await expect(page.getByTestId('browsers-table')).toBeVisible();
await expect(page.getByTestId('os-table')).toBeVisible();
// Take screenshot
await expect(page).toHaveScreenshot('analytics-audience-devices.png', {
maxDiffPixels: 100,
fullPage: true,
});
});
test('should take screenshot of geography tab', async ({ page }) => {
// Navigate to geography tab
const geographyTab = page.getByTestId('tab-geography');
await geographyTab.click();
await page.waitForTimeout(300);
// Wait for geography table to load
await expect(page.getByTestId('geography-table')).toBeVisible();
await expect(page.getByTestId('geo-granularity-select')).toBeVisible();
// Take screenshot
await expect(page).toHaveScreenshot('analytics-audience-geography.png', {
maxDiffPixels: 100,
fullPage: true,
});
});
test('should take screenshot of languages tab', async ({ page }) => {
// Navigate to languages tab
const languagesTab = page.getByTestId('tab-languages');
await languagesTab.click();
await page.waitForTimeout(300);
// Wait for languages table to load
await expect(page.getByTestId('languages-table')).toBeVisible();
// Take screenshot
await expect(page).toHaveScreenshot('analytics-audience-languages.png', {
maxDiffPixels: 100,
fullPage: true,
});
});
});