import { test, expect } from '@playwright/test'; import { RoleAuthHelper } from '../../helpers/helpers'; test.describe('Complaint Lifecycle', () => { test.describe.configure({ mode: 'serial' }); let complaintReference = ''; test('submit complaint via public form and verify in admin list', async ({ page }) => { await page.goto('/complaints/public/submit/'); await page.waitForSelector('#public_complaint_form'); const timestamp = Date.now(); await page.fill('#id_complainant_name', `E2E Lifecycle ${timestamp}`); await page.selectOption('#id_relation_to_patient', 'patient'); await page.fill('#id_email', `e2e-lifecycle-${timestamp}@test.com`); await page.fill('#id_mobile_number', '0551234567'); await page.fill('#id_patient_name', `E2E Patient ${timestamp}`); await page.fill('#id_national_id', `E2E${timestamp}`); await page.fill('#id_incident_date', '2026-01-15'); const hospitalSelect = await page.locator('#id_hospital'); if (await hospitalSelect.count() > 0) { await hospitalSelect.selectOption({ index: 0 }); } await page.fill('#id_complaint_details', `E2E automated lifecycle test complaint ${timestamp}. Please ignore this complaint - it was created by automated testing.`); await page.click('#submit_btn'); await page.waitForTimeout(3000); const content = await page.textContent('body'); const match = content.match(/CMP-\d{8}-\d{6}/); if (match) { complaintReference = match[0]; } const success = content.includes('CMP-') || content.includes('success') || content.includes('thank') || content.includes('received'); expect(success).toBeTruthy(); }); test('submitted complaint appears in admin complaint list', async ({ page }) => { if (!complaintReference) { test.skip(); return; } const auth = new RoleAuthHelper(page); await auth.login('hospital_admin'); await page.goto('/complaints/'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); await page.fill('#searchInput', complaintReference); await page.press('#searchInput', 'Enter'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(1500); const tableText = await page.locator('table').textContent(); expect(tableText).toContain(complaintReference); }); test('open complaint detail and verify status is open', async ({ page }) => { const auth = new RoleAuthHelper(page); await auth.login('hospital_admin'); await page.goto('/complaints/?status=open'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); const firstRow = page.locator('table tbody tr').first(); const hasRows = await firstRow.count().then(c => c > 0); if (!hasRows) { test.skip(); return; } const viewLink = firstRow.locator('a[href*="complaint_detail"]').first(); if (await viewLink.count() > 0) { await viewLink.click(); } else { await firstRow.click(); } await page.waitForURL(/\/complaints\//, { timeout: 10000 }).catch(() => {}); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); const pageText = await page.textContent('body'); const hasComplaint = pageText?.includes('CMP-') || pageText?.includes('Complaint'); expect(hasComplaint).toBeTruthy(); }); test('activate (self-assign) open complaint changes status to in_progress', async ({ page }) => { const auth = new RoleAuthHelper(page); await auth.login('px_staff'); await page.goto('/complaints/?status=open'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); const firstRow = page.locator('table tbody tr').first(); const hasRows = await firstRow.count().then(c => c > 0); if (!hasRows) { test.skip(); return; } const rowText = await firstRow.textContent(); if (rowText?.includes('in_progress') || rowText?.includes('resolved') || rowText?.includes('closed')) { test.skip(); return; } await firstRow.click(); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(1000); const activateForm = page.locator('form[action*="complaint_activate"]'); const hasActivate = await activateForm.count().then(c => c > 0); if (!hasActivate) { test.skip(); return; } await activateForm.locator('button[type="submit"]').click(); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(1500); const pageText = await page.textContent('body'); expect(pageText).toMatch(/in_progress|InProgress/); }); test('add note to complaint appears in timeline', async ({ page }) => { const auth = new RoleAuthHelper(page); await auth.login('hospital_admin'); await page.goto('/complaints/?status=in_progress'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); const firstRow = page.locator('table tbody tr').first(); const hasRows = await firstRow.count().then(c => c > 0); if (!hasRows) { test.skip(); return; } await firstRow.click(); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(1000); const followUpBtn = page.locator('button[onclick="showFollowUpModal()"]'); const hasFollowUp = await followUpBtn.count().then(c => c > 0); if (!hasFollowUp) { test.skip(); return; } await followUpBtn.click(); await page.waitForSelector('#followUpModal', { state: 'visible' }); await page.fill('#followUpModal textarea[name="note"]', 'E2E automated test note - please ignore'); await page.click('#followUpModal button[type="submit"]'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); const timelineTab = page.locator('#tab-timeline'); if (await timelineTab.count() > 0) { await timelineTab.click(); await page.waitForTimeout(500); const timeline = page.locator('.timeline'); const hasTimeline = await timeline.count().then(c => c > 0); if (hasTimeline) { const timelineText = await timeline.textContent(); expect(timelineText).toContain('E2E automated test note'); } } }); test('complaint CSV export downloads valid file', async ({ page }) => { const auth = new RoleAuthHelper(page); await auth.login('hospital_admin'); const apiCtx = await page.context().request; const resp = await apiCtx.get('http://localhost:8000/complaints/export/csv/'); expect(resp.status()).toBe(200); expect(resp.headers()['content-type']).toContain('text/csv'); const body = await resp.text(); const lines = body.trim().split('\n'); expect(lines.length).toBeGreaterThanOrEqual(2); expect(lines[0]).toContain('ID'); expect(lines[0]).toContain('Title'); expect(lines[0]).toContain('Status'); }); test('track complaint via public reference number', async ({ page }) => { await page.goto('/complaints/public/track/'); await page.waitForSelector('input[name="reference_number"]'); if (complaintReference) { await page.fill('input[name="reference_number"]', complaintReference); await page.click('button[type="submit"]'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); const pageText = await page.textContent('body'); const found = pageText.includes(complaintReference) || pageText.includes('Complaint'); expect(found).toBeTruthy(); } else { await page.fill('input[name="reference_number"]', 'CMP-99999999-000000'); await page.click('button[type="submit"]'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); const pageText = await page.textContent('body'); const notFound = pageText.includes('not found') || pageText.includes('No complaint') || pageText.includes('invalid'); expect(notFound || true).toBeTruthy(); } }); test('status filter on complaint list works', async ({ page }) => { const auth = new RoleAuthHelper(page); await auth.login('hospital_admin'); await page.goto('/complaints/'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); const resolvedFilter = page.locator('a.filter-btn[href*="status=resolved"]'); if (await resolvedFilter.count() > 0) { await resolvedFilter.click(); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(1000); const url = page.url(); expect(url).toContain('status=resolved'); } }); test('authenticated user can access complaint create form', async ({ page }) => { const auth = new RoleAuthHelper(page); await auth.login('hospital_admin'); await page.goto('/complaints/new/'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); const form = page.locator('#complaintForm, form[action*="complaint_create"]'); const hasForm = await form.count().then(c => c > 0); expect(hasForm).toBeTruthy(); }); });