// @ts-check const { test, expect } = require('@playwright/test'); /** * E2E tests run against the full Docker stack (frontend on port 52867, * backend on port 3001). To run locally: * docker-compose up -d * npx playwright test */ const BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:52867'; test.describe('UI Flow — create experiment → add animal → log status', () => { test('full happy path', async ({ page }) => { await page.goto(BASE_URL); // ── Create experiment ──────────────────────────────────────── await page.click('button:has-text("New Experiment")'); await expect(page.getByRole('dialog')).toBeVisible(); await page.getByLabel(/experiment title/i).fill('E2E Test Study'); await page.click('button:has-text("Create Experiment")'); // Modal closes and new experiment appears in the list await expect(page.getByRole('dialog')).not.toBeVisible(); await expect(page.getByText('E2E Test Study')).toBeVisible(); // ── Navigate into experiment ────────────────────────────────── await page.click('text=E2E Test Study'); await expect(page.url()).toContain('/experiments/'); // ── Add animal ──────────────────────────────────────────────── await page.click('button:has-text("Add Animal")'); await expect(page.getByRole('dialog')).toBeVisible(); await page.getByLabel(/animal id/i).fill('M-E2E-001'); await page.getByLabel(/animal name/i).fill('TestMouse'); await page.click('button:has-text("Add Animal")'); await expect(page.getByRole('dialog')).not.toBeVisible(); await expect(page.getByText('TestMouse')).toBeVisible(); // ── Navigate into animal ────────────────────────────────────── await page.click('text=TestMouse'); await expect(page.url()).toContain('/animals/'); // ── Log daily status ────────────────────────────────────────── await page.click('button:has-text("Log Daily Status")'); await expect(page.getByRole('dialog')).toBeVisible(); // Date is pre-filled with today; add vitals await page.getByLabel(/vitals/i).fill('HR 70, Temp 37.0'); await page.getByLabel(/treatment/i).fill('Control'); await page.click('button:has-text("Log Status")'); await expect(page.getByRole('dialog')).not.toBeVisible(); await expect(page.getByText('HR 70, Temp 37.0')).toBeVisible(); }); }); test.describe('UX Flow — validation error states', () => { test('empty experiment title shows error', async ({ page }) => { await page.goto(BASE_URL); await page.click('button:has-text("New Experiment")'); await page.click('button:has-text("Create Experiment")'); await expect(page.getByRole('alert')).toContainText(/title is required/i); }); test('empty animal fields show errors', async ({ page }) => { await page.goto(BASE_URL); // Need an experiment to navigate into — create one first await page.click('button:has-text("New Experiment")'); await page.getByLabel(/experiment title/i).fill('UX Test Study'); await page.click('button:has-text("Create Experiment")'); await page.click('text=UX Test Study'); await page.click('button:has-text("Add Animal")'); await page.click('button:has-text("Add Animal")'); // submit without filling const alerts = page.getByRole('alert'); await expect(alerts).toHaveCount(2); // ID + Name errors }); test('missing date on daily status shows error', async ({ page }) => { await page.goto(BASE_URL); // Create experiment + animal to get to daily status form await page.click('button:has-text("New Experiment")'); await page.getByLabel(/experiment title/i).fill('Date Test Study'); await page.click('button:has-text("Create Experiment")'); await page.click('text=Date Test Study'); await page.click('button:has-text("Add Animal")'); await page.getByLabel(/animal id/i).fill('M-DT-001'); await page.getByLabel(/animal name/i).fill('DateTestMouse'); await page.click('button:has-text("Add Animal")'); await page.click('text=DateTestMouse'); await page.click('button:has-text("Log Daily Status")'); // Clear the date field await page.getByLabel(/date/i).fill(''); await page.click('button:has-text("Log Status")'); await expect(page.getByRole('alert')).toContainText(/date is required/i); }); });