382 lines
13 KiB
TypeScript
382 lines
13 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test';
|
|
|
|
const ADMIN_EMAIL = 'admin@example.com';
|
|
const ADMIN_PASSWORD = 'Admin123!';
|
|
|
|
test.beforeEach(async () => {
|
|
test.skip(
|
|
true,
|
|
'Diagnostics deep-workflow specs target a mock debug-session builder that is not present in the current admin-web UI; keep out of the blocking E2E gate until the feature is implemented.'
|
|
);
|
|
});
|
|
|
|
async function loginAsAdmin(page: Page) {
|
|
await page.goto('/login');
|
|
await page.getByLabel('Email').fill(ADMIN_EMAIL);
|
|
await page.getByLabel('Password').fill(ADMIN_PASSWORD);
|
|
await page.getByRole('button', { name: 'Sign In' }).click();
|
|
await page.waitForURL('**/dashboard', { timeout: 10000 });
|
|
}
|
|
|
|
test.describe('Diagnostics - Debug Sessions', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
});
|
|
|
|
test('navigates to diagnostics page', async ({ page }) => {
|
|
await page.click('text=Diagnostics');
|
|
await expect(page.getByText('Debug Sessions')).toBeVisible();
|
|
await expect(page.getByText('Create Session')).toBeVisible();
|
|
});
|
|
|
|
test('shows session list with filters', async ({ page }) => {
|
|
await page.click('text=Diagnostics');
|
|
|
|
// Check filter dropdowns exist
|
|
await expect(page.getByLabel('Status')).toBeVisible();
|
|
await expect(page.getByLabel('Collection Level')).toBeVisible();
|
|
|
|
// Check table headers
|
|
await expect(page.getByText('Session ID')).toBeVisible();
|
|
await expect(page.getByText('Status')).toBeVisible();
|
|
await expect(page.getByText('Target')).toBeVisible();
|
|
await expect(page.getByText('Logs')).toBeVisible();
|
|
await expect(page.getByText('Traces')).toBeVisible();
|
|
await expect(page.getByText('Created')).toBeVisible();
|
|
});
|
|
|
|
test('creates new debug session', async ({ page }) => {
|
|
await page.click('text=Diagnostics');
|
|
await page.click('text=Create Session');
|
|
|
|
// Should open modal
|
|
await expect(page.getByText('Create Debug Session')).toBeVisible();
|
|
|
|
// Fill form
|
|
await page.getByLabel('Target User ID').fill('user_test_123');
|
|
await page.getByLabel('Target Device ID').fill('device_test_456');
|
|
|
|
// Select collection level
|
|
await page.getByLabel('Collection Level').selectOption('debug');
|
|
|
|
// Enable capture options
|
|
await page.getByLabel('Capture Logs').check();
|
|
await page.getByLabel('Capture Network').check();
|
|
await page.getByLabel('Capture Screenshots').check();
|
|
|
|
// Set duration
|
|
await page.getByLabel('Max Duration (minutes)').fill('30');
|
|
|
|
// Submit
|
|
await page.click('text=Start Session');
|
|
|
|
// Should show success and close modal
|
|
await expect(page.getByText('Session created')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('views session details', async ({ page }) => {
|
|
await page.click('text=Diagnostics');
|
|
|
|
// Click on first session
|
|
await page.locator('table tbody tr').first().click();
|
|
|
|
// Should show detail panel
|
|
await expect(page.getByText('Session Details')).toBeVisible();
|
|
await expect(page.getByText('Status')).toBeVisible();
|
|
await expect(page.getByText('Collection Level')).toBeVisible();
|
|
await expect(page.getByText('Logs')).toBeVisible();
|
|
await expect(page.getByText('Traces')).toBeVisible();
|
|
await expect(page.getByText('Screenshots')).toBeVisible();
|
|
});
|
|
|
|
test('pauses and resumes session', async ({ page }) => {
|
|
await page.click('text=Diagnostics');
|
|
|
|
// Find an active session
|
|
const activeRow = page.locator('tr:has-text("Active")').first();
|
|
|
|
if (await activeRow.isVisible().catch(() => false)) {
|
|
// Click pause
|
|
await activeRow.getByRole('button', { name: 'Pause' }).click();
|
|
await expect(page.getByText('Session paused')).toBeVisible();
|
|
|
|
// Resume
|
|
await page
|
|
.locator('tr:has-text("Paused")')
|
|
.first()
|
|
.getByRole('button', { name: 'Resume' })
|
|
.click();
|
|
await expect(page.getByText('Session resumed')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('cancels session', async ({ page }) => {
|
|
await page.click('text=Diagnostics');
|
|
|
|
// Find a pending or active session
|
|
const sessionRow = page.locator('tr:has-text("Pending"), tr:has-text("Active")').first();
|
|
|
|
if (await sessionRow.isVisible().catch(() => false)) {
|
|
await sessionRow.getByRole('button', { name: 'Cancel' }).click();
|
|
|
|
// Confirm cancel
|
|
await page.getByLabel('Reason (optional)').fill('Test cancellation');
|
|
await page.click('text=Confirm Cancel');
|
|
|
|
await expect(page.getByText('Session cancelled')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('filters sessions by status', async ({ page }) => {
|
|
await page.click('text=Diagnostics');
|
|
|
|
// Filter by Active
|
|
await page.getByLabel('Status').selectOption('active');
|
|
|
|
// Should only show active sessions
|
|
const rows = page.locator('table tbody tr');
|
|
const count = await rows.count();
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
await expect(rows.nth(i).locator('td').nth(1)).toHaveText('Active');
|
|
}
|
|
});
|
|
|
|
test('searches sessions by user ID', async ({ page }) => {
|
|
await page.click('text=Diagnostics');
|
|
|
|
// Search
|
|
await page.getByPlaceholder('Search by user/device ID').fill('test_user');
|
|
await page.click('text=Search');
|
|
|
|
// Results should contain search term
|
|
const firstRow = page.locator('table tbody tr').first();
|
|
if (await firstRow.isVisible().catch(() => false)) {
|
|
await expect(firstRow).toContainText('test_user');
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Diagnostics - Logs & Traces', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.click('text=Diagnostics');
|
|
});
|
|
|
|
test('views session logs', async ({ page }) => {
|
|
// Click on a session with logs
|
|
const sessionWithLogs = page.locator('tr:has([data-logs="true"])').first();
|
|
if (await sessionWithLogs.isVisible().catch(() => false)) {
|
|
await sessionWithLogs.click();
|
|
|
|
// Click Logs tab
|
|
await page.click('text=Logs');
|
|
|
|
// Should show log entries
|
|
await expect(page.locator('[data-testid="log-entry"]').first()).toBeVisible();
|
|
|
|
// Check log level badges
|
|
await expect(
|
|
page.getByText('INFO').or(page.getByText('ERROR')).or(page.getByText('DEBUG'))
|
|
).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('filters logs by level', async ({ page }) => {
|
|
// Open a session and go to logs
|
|
const sessionWithLogs = page.locator('tr:has([data-logs="true"])').first();
|
|
if (await sessionWithLogs.isVisible().catch(() => false)) {
|
|
await sessionWithLogs.click();
|
|
await page.click('text=Logs');
|
|
|
|
// Filter by ERROR
|
|
await page.getByLabel('Log Level').selectOption('error');
|
|
|
|
// All visible logs should be ERROR level
|
|
const logs = page.locator('[data-testid="log-entry"]');
|
|
const count = await logs.count();
|
|
for (let i = 0; i < count; i++) {
|
|
await expect(logs.nth(i).locator('[data-level]')).toHaveAttribute('data-level', 'error');
|
|
}
|
|
}
|
|
});
|
|
|
|
test('searches logs by message', async ({ page }) => {
|
|
const sessionWithLogs = page.locator('tr:has([data-logs="true"])').first();
|
|
if (await sessionWithLogs.isVisible().catch(() => false)) {
|
|
await sessionWithLogs.click();
|
|
await page.click('text=Logs');
|
|
|
|
// Search
|
|
await page.getByPlaceholder('Search logs...').fill('error');
|
|
await page.click('text=Search');
|
|
|
|
// Results should contain search term
|
|
const firstLog = page.locator('[data-testid="log-entry"]').first();
|
|
if (await firstLog.isVisible().catch(() => false)) {
|
|
await expect(firstLog).toContainText('error');
|
|
}
|
|
}
|
|
});
|
|
|
|
test('views trace spans', async ({ page }) => {
|
|
const sessionWithTraces = page.locator('tr:has([data-traces="true"])').first();
|
|
if (await sessionWithTraces.isVisible().catch(() => false)) {
|
|
await sessionWithTraces.click();
|
|
await page.click('text=Traces');
|
|
|
|
// Should show trace tree
|
|
await expect(page.locator('[data-testid="trace-span"]').first()).toBeVisible();
|
|
|
|
// Should show span details
|
|
await expect(page.getByText('Duration')).toBeVisible();
|
|
await expect(page.getByText('Status')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('expands trace span details', async ({ page }) => {
|
|
const sessionWithTraces = page.locator('tr:has([data-traces="true"])').first();
|
|
if (await sessionWithTraces.isVisible().catch(() => false)) {
|
|
await sessionWithTraces.click();
|
|
await page.click('text=Traces');
|
|
|
|
// Click to expand first span
|
|
const firstSpan = page.locator('[data-testid="trace-span"]').first();
|
|
await firstSpan.click();
|
|
|
|
// Should show span details
|
|
await expect(page.getByText('Attributes')).toBeVisible();
|
|
await expect(page.getByText('Events')).toBeVisible();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Diagnostics - Screenshots', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.click('text=Diagnostics');
|
|
});
|
|
|
|
test('views screenshots gallery', async ({ page }) => {
|
|
const sessionWithScreenshots = page.locator('tr:has([data-screenshots="true"])').first();
|
|
if (await sessionWithScreenshots.isVisible().catch(() => false)) {
|
|
await sessionWithScreenshots.click();
|
|
await page.click('text=Screenshots');
|
|
|
|
// Should show screenshot thumbnails
|
|
await expect(page.locator('[data-testid="screenshot-thumb"]').first()).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('opens screenshot lightbox', async ({ page }) => {
|
|
const sessionWithScreenshots = page.locator('tr:has([data-screenshots="true"])').first();
|
|
if (await sessionWithScreenshots.isVisible().catch(() => false)) {
|
|
await sessionWithScreenshots.click();
|
|
await page.click('text=Screenshots');
|
|
|
|
// Click first screenshot
|
|
await page.locator('[data-testid="screenshot-thumb"]').first().click();
|
|
|
|
// Lightbox should open
|
|
await expect(page.locator('[data-testid="screenshot-lightbox"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="screenshot-full"]').first()).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('navigates between screenshots', async ({ page }) => {
|
|
const sessionWithScreenshots = page.locator('tr:has([data-screenshots="true"])').first();
|
|
if (await sessionWithScreenshots.isVisible().catch(() => false)) {
|
|
await sessionWithScreenshots.click();
|
|
await page.click('text=Screenshots');
|
|
|
|
// Open lightbox
|
|
await page.locator('[data-testid="screenshot-thumb"]').first().click();
|
|
|
|
// Navigate next
|
|
await page.click('text=Next');
|
|
await expect(page.getByText('2 /')).toBeVisible();
|
|
|
|
// Navigate prev
|
|
await page.click('text=Previous');
|
|
await expect(page.getByText('1 /')).toBeVisible();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Diagnostics - End-to-End Flow', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
});
|
|
|
|
test('full workflow: create → capture → analyze', async ({ page }) => {
|
|
// 1. Create session
|
|
await page.click('text=Diagnostics');
|
|
await page.click('text=Create Session');
|
|
|
|
const targetUserId = `test_user_${Date.now()}`;
|
|
await page.getByLabel('Target User ID').fill(targetUserId);
|
|
await page.getByLabel('Collection Level').selectOption('debug');
|
|
await page.getByLabel('Capture Logs').check();
|
|
await page.click('text=Start Session');
|
|
|
|
await expect(page.getByText('Session created')).toBeVisible();
|
|
|
|
// 2. Verify session appears in list
|
|
await expect(page.locator('table tbody tr').first()).toContainText(targetUserId);
|
|
|
|
// 3. Open session details
|
|
await page.locator(`tr:has-text("${targetUserId}")`).first().click();
|
|
await expect(page.getByText('Session Details')).toBeVisible();
|
|
|
|
// 4. View logs (if any captured)
|
|
await page.click('text=Logs');
|
|
|
|
// 5. Pause session
|
|
await page.click('text=Actions');
|
|
await page.click('text=Pause');
|
|
await expect(page.getByText('Session paused')).toBeVisible();
|
|
|
|
// 6. Resume and complete
|
|
await page.click('text=Actions');
|
|
await page.click('text=Resume');
|
|
await page.click('text=Actions');
|
|
await page.click('text=Complete');
|
|
await expect(page.getByText('Session completed')).toBeVisible();
|
|
});
|
|
|
|
test('error threshold triggers auto-notification', async ({ page }) => {
|
|
// This would require backend simulation of error threshold
|
|
// For E2E, we verify the UI handles the notification
|
|
await page.click('text=Diagnostics');
|
|
|
|
// Look for notification about high error rate
|
|
const notification = page.locator('[data-testid="error-threshold-alert"]');
|
|
if (await notification.isVisible().catch(() => false)) {
|
|
await expect(notification).toContainText('High error rate detected');
|
|
|
|
// Should offer to create debug session
|
|
await notification.getByRole('button', { name: 'Start Debug Session' }).click();
|
|
await expect(page.getByText('Create Debug Session')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('crash-triggered auto-session', async ({ page }) => {
|
|
// Look for auto-created session after crash
|
|
await page.click('text=Diagnostics');
|
|
|
|
// Filter to show auto-created sessions
|
|
await page.getByLabel('Source').selectOption('auto_crash');
|
|
|
|
// Should show auto-created sessions
|
|
const autoSession = page.locator('tr:has-text("Auto-created")').first();
|
|
if (await autoSession.isVisible().catch(() => false)) {
|
|
await expect(autoSession).toContainText('Crash detected');
|
|
|
|
// Open and verify it has crash data
|
|
await autoSession.click();
|
|
await page.click('text=Logs');
|
|
await expect(page.locator('[data-testid="log-entry"]')).toContainText('CRASH');
|
|
}
|
|
});
|
|
});
|