platform-codebase/features/platform-dev/frontend-dev/e2e/conversation-assistant.e2e.ts

237 lines
8.5 KiB
TypeScript

/**
* Conversation Assistant E2E Tests for Platform Dev
*
* Tests the conversation-assistant integration:
* - Scammer Database page (/scammers)
* - Training Samples page (/training)
*
* These pages proxy requests to the conversation-assistant backend (port 3100)
* through platform-dev's vite proxy configuration.
*/
import { test, expect } from '@playwright/test';
import { applyConversationMocks } from './fixtures/api-mocks';
test.describe('Conversation Assistant - Scammers Page', () => {
test.beforeEach(async ({ page }) => {
await applyConversationMocks(page);
});
test('renders scammers page header', async ({ page }) => {
await page.goto('/scammers');
await page.waitForLoadState('networkidle');
// Should display page title
const mainContent = page.locator('main');
await expect(mainContent.locator('h1').first()).toContainText(/scammer/i);
});
test('displays scammer stats cards', async ({ page }) => {
await page.goto('/scammers');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Check for stat labels based on SCAMMERS_MOCKS stats
await expect(page.locator('text=Total').first()).toBeVisible({ timeout: 10000 });
await expect(page.locator('text=Suspected').first()).toBeVisible({ timeout: 10000 });
await expect(page.locator('text=Confirmed').first()).toBeVisible({ timeout: 10000 });
await expect(page.locator('text=Cleared').first()).toBeVisible({ timeout: 10000 });
});
test('displays scammers table with phone numbers', async ({ page }) => {
await page.goto('/scammers');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Should show phone number from mock (+1234567890)
await expect(page.locator('text=/\\+\\d+/').first()).toBeVisible({ timeout: 10000 });
});
test('shows status badges', async ({ page }) => {
await page.goto('/scammers');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Look for status indicators (suspected, confirmed, cleared)
await expect(page.locator('text=/suspected|confirmed|cleared/i').first()).toBeVisible({
timeout: 10000,
});
});
test('shows risk score visualization', async ({ page }) => {
await page.goto('/scammers');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Should show risk score from mock (75)
await expect(page.locator('text=/75|score/i').first()).toBeVisible({ timeout: 10000 });
});
test('shows detection count badges', async ({ page }) => {
await page.goto('/scammers');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Look for AI detection, manipulation, phishing counts
await expect(page.locator('text=/ai|detection|manipulation|phishing/i').first()).toBeVisible({
timeout: 10000,
});
});
test('shows action buttons for suspected entries', async ({ page }) => {
await page.goto('/scammers');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Should have Confirm button
const confirmBtn = page.locator('button:has-text("Confirm")').first();
await expect(confirmBtn).toBeVisible({ timeout: 10000 });
// Should have Clear button
const clearBtn = page.locator('button:has-text("Clear")').first();
await expect(clearBtn).toBeVisible({ timeout: 10000 });
});
});
test.describe('Conversation Assistant - Training Page', () => {
test.beforeEach(async ({ page }) => {
await applyConversationMocks(page);
});
test('renders training page or maintenance mode', async ({ page }) => {
await page.goto('/training');
await page.waitForLoadState('networkidle');
const mainContent = page.locator('main');
// Should display either:
// 1. Training page header, OR
// 2. Maintenance mode message (since route-health marks it as isMaintenancePage: true)
const hasTrainingHeader = await mainContent
.locator('h1')
.filter({ hasText: /training/i })
.count();
const hasMaintenanceMessage = await page.locator('text=/maintenance/i').count();
expect(hasTrainingHeader + hasMaintenanceMessage).toBeGreaterThan(0);
});
test('displays training samples table if not in maintenance', async ({ page }) => {
await page.goto('/training');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Check if we can see training samples section
const hasSamplesSection =
(await page.locator('text=/sample/i').first().isVisible({ timeout: 5000 }).catch(() => false)) ||
(await page.locator('text=/maintenance/i').isVisible({ timeout: 5000 }));
expect(hasSamplesSection).toBeTruthy();
});
test('displays training sample context from mocks if not in maintenance', async ({ page }) => {
await page.goto('/training');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Try to find sample context text ("How are you?") or maintenance message
const hasSampleText =
(await page.locator('text=/How are you/i').count()) > 0 ||
(await page.locator('text=/maintenance/i').count()) > 0;
expect(hasSampleText).toBeTruthy();
});
test('shows approve/reject buttons for samples if not in maintenance', async ({ page }) => {
await page.goto('/training');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Look for action buttons or maintenance message
const hasApproveBtn =
(await page.locator('button:has-text("Approve")').count()) > 0 ||
(await page.locator('text=/maintenance/i').count()) > 0;
expect(hasApproveBtn).toBeTruthy();
});
test('displays training jobs table if not in maintenance', async ({ page }) => {
await page.goto('/training');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Check if we can see training jobs or maintenance
const hasJobsSection =
(await page.locator('text=/job|epoch/i').first().isVisible({ timeout: 5000 }).catch(() => false)) ||
(await page.locator('text=/maintenance/i').isVisible({ timeout: 5000 }));
expect(hasJobsSection).toBeTruthy();
});
test('shows job status from mocks if not in maintenance', async ({ page }) => {
await page.goto('/training');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Look for job statuses (completed, training, queued) or maintenance
const hasJobStatus =
(await page.locator('text=/completed|training|queued/i').count()) > 0 ||
(await page.locator('text=/maintenance/i').count()) > 0;
expect(hasJobStatus).toBeTruthy();
});
test('displays ML models section if not in maintenance', async ({ page }) => {
await page.goto('/training');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Check if we can see ML models or maintenance
const hasModelsSection =
(await page.locator('text=/model|ministral|qwen/i').first().isVisible({ timeout: 5000 }).catch(() => false)) ||
(await page.locator('text=/maintenance/i').isVisible({ timeout: 5000 }));
expect(hasModelsSection).toBeTruthy();
});
test('shows model loaded status from mocks if not in maintenance', async ({ page }) => {
await page.goto('/training');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Look for model names (ministral-3b-instruct, qwen2.5:3b-instruct) or maintenance
const hasModelInfo =
(await page.locator('text=/ministral|qwen/i').count()) > 0 ||
(await page.locator('text=/maintenance/i').count()) > 0;
expect(hasModelInfo).toBeTruthy();
});
});
test.describe('Conversation Assistant - Route Health', () => {
test('scammers route is accessible', async ({ page }) => {
await applyConversationMocks(page);
await page.goto('/scammers');
await page.waitForLoadState('networkidle');
// Should not show 404 page
await expect(page.getByRole('heading', { name: /404/ })).not.toBeVisible();
// Should show scammers content
const mainContent = page.locator('main');
await expect(mainContent).toBeVisible();
});
test('training route is accessible', async ({ page }) => {
await applyConversationMocks(page);
await page.goto('/training');
await page.waitForLoadState('networkidle');
// Should not show 404 page
await expect(page.getByRole('heading', { name: /404/ })).not.toBeVisible();
// Should show training content or maintenance
const mainContent = page.locator('main');
await expect(mainContent).toBeVisible();
});
});