// spec: e2e/plans/create-child.plan.md import { test, expect } from '@playwright/test' import { STORAGE_STATE } from '../../e2e-constants' test.describe('Create Child', () => { test.describe.configure({ mode: 'serial' }) test('New child appears in list without page reload', async ({ page, context, request }) => { // Clean up 'Hannah' before test const res = await request.get('/api/child/list') const data = await res.json() for (const child of data.children ?? []) { if (child.name === 'Hannah') { await request.delete(`/api/child/${child.id}`) } } // 1. Open two browser tabs both on /parent (children list) await page.goto('/') await expect(page).toHaveURL('/parent') const tab2 = await context.newPage() await tab2.goto('/') await expect(tab2).toHaveURL('/parent') // 2. In Tab 1, create child 'Hannah' age '4' // Use a retry loop: SSE events from parallel tests can reset the form or cancel navigation await page.getByRole('button', { name: 'Add Child' }).click() await expect(async () => { if (new URL(page.url()).pathname === '/parent') return // Clean up any Hannah created in a previous attempt where navigation was cancelled const lr = await request.get('/api/child/list') for (const c of (await lr.json()).children ?? []) { if (c.name === 'Hannah') await request.delete(`/api/child/${c.id}`) } await page.getByLabel('Name').fill('Hannah') await page.getByLabel('Age').fill('4') await expect(page.getByRole('button', { name: 'Create' })).toBeEnabled() await page.getByRole('button', { name: 'Create' }).click({ timeout: 2000 }) await expect(page).toHaveURL('/parent', { timeout: 5000 }) }).toPass({ timeout: 20000 }) await expect(page).toHaveURL('/parent') await expect(page.getByRole('heading', { name: 'Hannah' })).toBeVisible() // 3. Tab 2 should show 'Hannah' via SSE without a manual refresh await expect(tab2.getByRole('heading', { name: 'Hannah' })).toBeVisible() await tab2.close() }) test('New child appears in child mode list without page reload', async ({ page, browser, request, }) => { // Clean up 'Hannah' before test const res = await request.get('/api/child/list') const data = await res.json() for (const child of data.children ?? []) { if (child.name === 'Hannah') { await request.delete(`/api/child/${child.id}`) } } // 1. Tab 1: parent mode await page.goto('/') await expect(page).toHaveURL('/parent') // 2. Tab 2: isolated browser context so removing parentAuth doesn't affect Tab 1 const childContext = await browser.newContext({ storageState: STORAGE_STATE }) const tab2 = await childContext.newPage() // Load the app first (to ensure localStorage is seeded from storageState), // then remove parentAuth so the next navigation boots in child mode. await tab2.goto('/') await tab2.evaluate(() => localStorage.removeItem('parentAuth')) // Full navigation triggers a fresh Vue init — auth store reads no parentAuth // so the router allows /child await tab2.goto('/child') await expect(tab2).toHaveURL('/child') // 3. In Tab 1, create child 'Hannah' age '4' // Use a retry loop: SSE events from parallel tests can reset the form or cancel navigation await page.getByRole('button', { name: 'Add Child' }).click() await expect(async () => { if (new URL(page.url()).pathname === '/parent') return // Clean up any Hannah created in a previous attempt where navigation was cancelled const lr = await request.get('/api/child/list') for (const c of (await lr.json()).children ?? []) { if (c.name === 'Hannah') await request.delete(`/api/child/${c.id}`) } await page.getByLabel('Name').fill('Hannah') await page.getByLabel('Age').fill('4') await expect(page.getByRole('button', { name: 'Create' })).toBeEnabled() await page.getByRole('button', { name: 'Create' }).click({ timeout: 2000 }) await expect(page).toHaveURL('/parent', { timeout: 5000 }) }).toPass({ timeout: 20000 }) await expect(page).toHaveURL('/parent') await expect(page.getByRole('heading', { name: 'Hannah' })).toBeVisible() // 4. Tab 2 (child mode) should show 'Hannah' via SSE without a manual refresh await expect(tab2.getByRole('heading', { name: 'Hannah' })).toBeVisible() await childContext.close() }) })