// spec: e2e/plans/create-child.plan.md import { test, expect } from '@playwright/test' import path from 'path' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const TEST_IMAGE = path.join(__dirname, '../../.resources/crown.png') async function deleteNamedChildren(request: any, names: string[]) { const res = await request.get('/api/child/list') const data = await res.json() for (const child of data.children ?? []) { if (names.includes(child.name)) { await request.delete(`/api/child/${child.id}`) } } } async function deleteAllChildren(request: any) { const res = await request.get('/api/child/list') const data = await res.json() for (const child of data.children ?? []) { await request.delete(`/api/child/${child.id}`) } } test.describe('Create Child', () => { test.beforeEach(async ({ page }, testInfo) => { test.skip(testInfo.project.name === 'chromium-no-pin', 'Requires parent-authenticated mode') }) test('Create a child with name and age only', async ({ page, request }) => { await deleteNamedChildren(request, ['Alice']) // 1. Navigate to app root - router redirects to /parent (children list) when parent-authenticated await page.goto('/') await expect(page).toHaveURL('/parent') // 2. Click the 'Add Child' FAB await page.getByRole('button', { name: 'Add Child' }).click() await expect(page.getByRole('heading', { name: 'Create Child' })).toBeVisible() const nameInput = page.getByLabel('Name') const ageInput = page.getByLabel('Age') const createButton = page.getByRole('button', { name: 'Create' }) await expect(nameInput).toBeVisible() await expect(ageInput).toBeVisible() // Submit should be disabled until all required fields are valid. await expect(createButton).toBeDisabled() // 3. Enter 'Alice' in the Name field await nameInput.fill('Alice') await expect(createButton).toBeDisabled() // 4. Enter '8' in the Age field await ageInput.fill('8') // 5. Leave Image as default and click Create // Use toPass() to handle SSE-triggered form resets from parallel tests await expect(async () => { if (new URL(page.url()).pathname === '/parent') return await deleteNamedChildren(request, ['Alice']) await nameInput.fill('Alice') await ageInput.fill('8') await expect(createButton).toBeEnabled() await createButton.click({ timeout: 2000 }) await expect(page).toHaveURL('/parent', { timeout: 5000 }) }).toPass({ timeout: 20000 }) await expect(page.locator('.error')).not.toBeVisible() await expect(page).toHaveURL('/parent') await expect(page.getByText('Alice')).toBeVisible() }) test('Create a child via the inline Create button in empty state', async ({ page, request }) => { await deleteAllChildren(request) // 1. Navigate to app root - router redirects to /parent (children list) await page.goto('/') await expect(page.getByText('No children')).toBeVisible() await expect(page.getByRole('button', { name: 'Create' })).toBeVisible() // 2. Click the inline 'Create' button await page.getByRole('button', { name: 'Create' }).click() await expect(page.getByRole('heading', { name: 'Create Child' })).toBeVisible() // 3. Enter 'Bob' and '10', then submit const nameInput = page.getByLabel('Name') const ageInput = page.getByLabel('Age') const createButton = page.getByRole('button', { name: 'Create' }) // Submit should be disabled until all required fields are valid await expect(createButton).toBeDisabled() await nameInput.fill('Bob') await expect(createButton).toBeDisabled() await ageInput.fill('10') // Handle async form initialization race and SSE-triggered form resets await expect(async () => { if (new URL(page.url()).pathname === '/parent') return await deleteNamedChildren(request, ['Bob']) await nameInput.fill('Bob') await ageInput.fill('10') await expect(createButton).toBeEnabled() await createButton.click({ timeout: 2000 }) await expect(page).toHaveURL('/parent', { timeout: 5000 }) }).toPass({ timeout: 20000 }) await expect(page.locator('.error')).not.toBeVisible() await expect(page).toHaveURL('/parent') await expect(page.getByText('Bob')).toBeVisible() }) test('Create a child with a custom uploaded image', async ({ page, request }) => { await deleteNamedChildren(request, ['Grace']) // 1. Navigate to app root - router redirects to /parent (children list) await page.goto('/') await page.getByRole('button', { name: 'Add Child' }).click() await expect(page.getByRole('heading', { name: 'Create Child' })).toBeVisible() // 2. Enter 'Grace' and '6' const nameInput = page.getByLabel('Name') const ageInput = page.getByLabel('Age') const createButton = page.getByRole('button', { name: 'Create' }) // Submit should be disabled until all required fields are valid await expect(createButton).toBeDisabled() await nameInput.fill('Grace') await expect(createButton).toBeDisabled() await ageInput.fill('6') // 3. Upload a local image file via 'Add from device' const fileChooserPromise = page.waitForEvent('filechooser') await page.getByRole('button', { name: 'Add from device' }).click() const fileChooser = await fileChooserPromise await fileChooser.setFiles(TEST_IMAGE) // Handle async form initialization race and SSE-triggered form resets await expect(async () => { if (new URL(page.url()).pathname === '/parent') return await deleteNamedChildren(request, ['Grace']) await nameInput.fill('Grace') await ageInput.fill('6') await expect(createButton).toBeEnabled() await createButton.click({ timeout: 2000 }) await expect(page).toHaveURL('/parent', { timeout: 5000 }) }).toPass({ timeout: 20000 }) await expect(page.locator('.error')).not.toBeVisible() await expect(page).toHaveURL('/parent') await expect(page.getByText('Grace')).toBeVisible() }) })