// spec: frontend/vue-app/e2e/plans/parent-rewards-management.plan.md // seed: e2e/seed.spec.ts import { test, expect } from '@playwright/test' test.describe('Reward management', () => { test.beforeEach(async ({ page }, testInfo) => { test.skip(testInfo.project.name === 'chromium-no-pin', 'Requires parent-authenticated mode') }) test.describe.configure({ mode: 'serial' }) test('Create a new reward', async ({ page }) => { const suffix = Date.now() const name = `Toy car ${suffix}` // 1. Navigate to /parent/rewards and verify default list await page.goto('/parent/rewards') // expect: some reward name visible (use default item) await expect(page.getByText('Choose meal', { exact: true })).toBeVisible() // 2. Click the 'Create Reward' FAB await page.getByRole('button', { name: 'Create Reward' }).click() // expect: form appears with fields await expect(page.locator('text=Reward Name')).toBeVisible() await expect(page.getByRole('button', { name: 'Create' })).toBeDisabled() // 3. Fill in name and cost via DOM events await page.evaluate((name) => { const setVal = (sel: string, val: string) => { const el = document.querySelector(sel) as HTMLInputElement | null if (el) { el.value = val el.dispatchEvent(new Event('input', { bubbles: true })) el.dispatchEvent(new Event('change', { bubbles: true })) } } setVal('#name', name) setVal('#cost', '20') }, name) await expect(page.getByRole('button', { name: 'Create' })).toBeEnabled() // 4. Submit await page.click('button:has-text("Create")') await expect(page.locator('.error')).toHaveCount(0) await expect(page).toHaveURL(/\/parent\//) await expect(page.locator(`text=${name}`)).toBeVisible() }) test('Edit an existing reward', async ({ page }) => { const suffix = Date.now() const original = `Toy car ${suffix}` const updated = `Toy boat ${suffix}` // ensure the item exists await page.goto('/parent/rewards') if (!(await page.locator(`text=${original}`).count())) { await page.getByRole('button', { name: 'Create Reward' }).click() await page.evaluate((name) => { const setVal = (sel: string, val: string) => { const el = document.querySelector(sel) as HTMLInputElement | null if (el) { el.value = val el.dispatchEvent(new Event('input', { bubbles: true })) el.dispatchEvent(new Event('change', { bubbles: true })) } } setVal('#name', name) setVal('#cost', '20') }, original) await page.getByRole('button', { name: 'Create' }).click() } await expect(page.locator(`text=${original}`)).toBeVisible() // open edit form await page.click(`text=${original}`) await expect(page.locator('text=Reward Name')).toBeVisible() // change values await page.evaluate((name) => { const setVal = (sel: string, val: string | number) => { const el = document.querySelector(sel) as HTMLInputElement | null if (el) { if (typeof val === 'number') { el.valueAsNumber = val } else { el.value = val } el.dispatchEvent(new Event('input', { bubbles: true })) el.dispatchEvent(new Event('change', { bubbles: true })) } } setVal('#name', name) // set numeric cost to ensure backend receives integer setVal('#cost', 25) }, updated) await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled() await page.click('button:has-text("Save")') await expect(page.locator('.error')).toHaveCount(0) await expect(page).toHaveURL(/\/parent\//) // allow extra time for the updated row to appear via SSE await expect(page.locator(`text=${updated}`)).toBeVisible({ timeout: 10000 }) }) test('Delete a reward', async ({ page }) => { const suffix = Date.now() const name = `Toy car ${suffix}` await page.goto('/parent/rewards') await page.getByRole('button', { name: 'Create Reward' }).click() await page.evaluate((name) => { const setVal = (sel: string, val: string) => { const el = document.querySelector(sel) as HTMLInputElement | null if (el) { el.value = val el.dispatchEvent(new Event('input', { bubbles: true })) el.dispatchEvent(new Event('change', { bubbles: true })) } } setVal('#name', name) setVal('#cost', '20') }, name) await page.getByRole('button', { name: 'Create' }).click() await expect(page.locator(`text=${name}`)).toBeVisible() // delete using row-local button await page .locator(`text=${name}`) .first() .locator('..') .getByRole('button', { name: 'Delete' }) .click() await expect(page.locator('text=Are you sure you want to delete')).toBeVisible() await page.locator('button.btn-danger:has-text("Delete")').click() await expect(page.locator(`text=${name}`)).toHaveCount(0) }) })