Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m12s
- Changed "Skip tour" button to "Cancel" in various tutorial overlays. - Updated modal titles from "Confirm Task" to "Confirm Penalty" where applicable. - Modified tutorial step call-to-action labels from "Got it" and "Next" to "Continue" for consistency. - Added tests for new tutorial overlay behavior and button interactions. - Refactored tutorial controller to allow ignoring tutorial enabled state in step visibility checks. - Enhanced user profile tutorial restart functionality with appropriate modal messages. - Updated user profile and task confirmation dialog components to reflect new titles and messages. - Adjusted configuration files for local development environments.
100 lines
4.4 KiB
TypeScript
100 lines
4.4 KiB
TypeScript
// spec: e2e/plans/create-child.plan.md
|
|
|
|
import { test, expect } from '@playwright/test'
|
|
|
|
/** Navigate to the parent children list and wait for the Add Child button to be ready. */
|
|
async function gotoParentList(page: import('@playwright/test').Page): Promise<void> {
|
|
await page.goto('/')
|
|
await expect(page.getByRole('button', { name: 'Add Child' })).toBeVisible({ timeout: 10000 })
|
|
}
|
|
|
|
test.describe('Create Child', () => {
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
test.skip(testInfo.project.name === 'chromium-no-pin', 'Requires parent-authenticated mode')
|
|
|
|
// Navigate to parent list and wait for it to fully load before clicking Add Child
|
|
await gotoParentList(page)
|
|
// Dismiss tutorial overlay if present — it intercepts pointer events
|
|
const skip = page.getByRole('button', { name: 'Cancel' })
|
|
if (await skip.isVisible({ timeout: 1000 }).catch(() => false)) {
|
|
await skip.click()
|
|
}
|
|
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.getByRole('textbox', { name: '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.getByRole('textbox', { name: '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.getByRole('textbox', { name: '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.getByRole('textbox', { name: '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.getByRole('textbox', { name: 'Name' }).fill('Eve')
|
|
await page.getByLabel('Age').fill('121')
|
|
|
|
await expect(page.getByRole('button', { name: 'Create' })).toBeDisabled()
|
|
await expect(page).toHaveURL('/parent/children/create')
|
|
})
|
|
})
|