Refactored frontend directory
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s
This commit is contained in:
163
frontend/e2e/mode_parent/create-child/happy-path.spec.ts
Normal file
163
frontend/e2e/mode_parent/create-child/happy-path.spec.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
// 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', () => {
|
||||
// Serial mode: the 'empty state' test calls deleteAllChildren() which would
|
||||
// race against sibling tests creating children if they ran in parallel.
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
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 }) => {
|
||||
// 1. Navigate to empty state and click inline Create.
|
||||
// Retry-loop handles SSE-triggered re-renders: a parallel test may add a child
|
||||
// between deleteAllChildren() and the click, detaching the empty-state button.
|
||||
await expect(async () => {
|
||||
await deleteAllChildren(request)
|
||||
await page.goto('/')
|
||||
await expect(page.getByText('No children')).toBeVisible({ timeout: 5000 })
|
||||
await page.getByRole('button', { name: 'Create' }).click({ timeout: 5000 })
|
||||
await expect(page.getByRole('heading', { name: 'Create Child' })).toBeVisible({
|
||||
timeout: 5000,
|
||||
})
|
||||
}).toPass({ timeout: 30000 })
|
||||
|
||||
// 2. 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()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user