// spec: e2e/plans/create-child.plan.md import { test, expect } from '@playwright/test' test.describe('Create Child', () => { test.beforeEach(async ({ page }, testInfo) => { test.skip(testInfo.project.name === 'chromium-no-pin', 'Requires parent-authenticated mode') // Navigate to app root - router redirects to /parent (children list) when parent-authenticated await page.goto('/') await page.getByRole('button', { name: 'Add Child' }).click() await expect(page.getByRole('heading', { name: 'Create Child' })).toBeVisible() }) test('Reject submission when Name is empty', async ({ page }) => { // 2. Leave Name empty, enter '7' in Age - Create remains disabled const createButton = page.getByRole('button', { name: 'Create' }) await page.getByLabel('Age').fill('7') await expect(createButton).toBeDisabled() await expect(page).toHaveURL('/parent/children/create') }) test('Reject submission when Name is whitespace only', async ({ page }) => { // 2. Enter only spaces in Name, enter '7' in Age - Create remains disabled const createButton = page.getByRole('button', { name: 'Create' }) await page.getByLabel('Name').fill(' ') await page.getByLabel('Age').fill('7') await expect(createButton).toBeDisabled() await expect(page).toHaveURL('/parent/children/create') }) test('Reject submission when Age is empty', async ({ page }) => { // 2. Enter 'Charlie', clear Age - Create button should be disabled await page.getByLabel('Name').fill('Charlie') await page.getByLabel('Age').clear() await expect(page.getByRole('button', { name: 'Create' })).toBeDisabled() await expect(page).toHaveURL('/parent/children/create') }) test('Reject negative age', async ({ page }) => { // 2. Enter 'Dave', enter '-1' - Create remains disabled const createButton = page.getByRole('button', { name: 'Create' }) await page.getByLabel('Name').fill('Dave') await page.getByLabel('Age').fill('-1') await expect(createButton).toBeDisabled() await expect(page).toHaveURL('/parent/children/create') }) test('Enforce maximum Name length of 64 characters', async ({ page, request }) => { const nameInput = page.getByLabel('Name') const ageInput = page.getByLabel('Age') const createButton = page.getByRole('button', { name: 'Create' }) // Use toPass() to handle SSE-triggered form resets from parallel tests const FULL_NAME = 'A'.repeat(64) await expect(async () => { if (new URL(page.url()).pathname === '/parent') return // Clean up any previously-created 64-A child in case prev attempt created but nav was cancelled const lr = await request.get('/api/child/list') for (const c of (await lr.json()).children ?? []) { if (c.name === FULL_NAME) await request.delete(`/api/child/${c.id}`) } // Fill 65 chars — HTML maxlength=64 truncates to 64 await nameInput.fill('A'.repeat(65)) // Verify truncation via non-retrying read (throws if SSE cleared the field) const truncated = await nameInput.inputValue() if (truncated !== FULL_NAME) throw new Error(`maxlength truncation failed: got "${truncated}"`) await ageInput.fill('5') await expect(createButton).toBeEnabled() await createButton.click({ timeout: 2000 }) await expect(page).toHaveURL('/parent', { timeout: 5000 }) }).toPass({ timeout: 20000 }) }) test('Reject age greater than 120', async ({ page }) => { // 2. Enter 'Eve', enter '121' in Age - Create button should be disabled await page.getByLabel('Name').fill('Eve') await page.getByLabel('Age').fill('121') await expect(page.getByRole('button', { name: 'Create' })).toBeDisabled() await expect(page).toHaveURL('/parent/children/create') }) })