21 KiB
Chore Scheduler E2E Tests
Application Overview
The chore scheduler allows parents to configure when chores appear for their children and set deadline times. Schedules operate in two modes: Specific Days (select weekdays + per-day deadlines with optional exceptions) and Every X Days (interval-based with anchor date). Schedules can be paused (enabled/disabled toggle) — a paused schedule makes the chore appear every day with no deadline, as if unscheduled. All schedule logic runs client-side; the backend only stores and returns raw schedule data.
Key behavioral difference between parent and child mode:
- Parent mode (
/parent/:id): All assigned chores are always visible regardless of schedule. Off-day and expired chores are grayed out (.chore-inactive). A kebab menu provides Schedule, Edit Points, Extend Time (when expired), and Reset (when completed) options. - Child mode (
/child/:id): Chores not scheduled for today are hidden entirely (filtered out client-side). Expired chores show aTOO LATEbadge and are grayed out. No kebab menu or scheduling controls exist in child mode.
Specs covered:
feat-calendar-chore.md— base schedule featurefeat-calendar-schedule-refactor-01.md— Specific Days UI refactor (day chips, default deadline, exceptions, TimePickerPopover)feat-calendar-schedule-refactor-02.md— Every X Days UI refactor (stepper, DateInputField, anchor date, next occurrence preview, Anytime toggle)feat-schedule-enable-disable.md— enable/disable (pause) toggle
Implementation
Tests live in e2e/mode_parent/chore-scheduler/. Each spec creates its own isolated child and chore tasks via the API so files can run in parallel. All created resources are deleted in afterAll.
Playwright Projects
| Project | testMatch | Notes |
|---|---|---|
chromium-chore-scheduler |
/mode_parent/chore-scheduler/.+\.spec\.ts/ |
Depends on setup only |
Config: Add project to playwright.config.ts with dependencies: ['setup']. Exclude from the catch-all project via testIgnore.
Seed Strategy
Each spec file creates an isolated child and one or more chore tasks via beforeAll API calls. Schedule configuration is done via the PUT /api/child/:childId/task/:taskId/schedule API. Cleanup (delete child + tasks) runs in afterAll.
For tests that need to verify child-mode behavior, the test navigates to /child/:id (no parent auth required for child view). For parent-mode behavior, navigate to /parent/:id.
Time-sensitive tests: Tests that depend on "Due by" labels or "TOO LATE" badges must set a schedule deadline far in the future (e.g., 23:59) to avoid the chore expiring mid-test, or set a deadline in the past (e.g., 00:01) to guarantee expiry. Use page.clock or carefully chosen deadline times to control time-dependent state.
Test Scenarios
1. Schedule Modal — Opening & Structure
File: e2e/mode_parent/chore-scheduler/schedule-modal.spec.ts
Seed: Creates child ScheduleModalChild and chore ScheduleModalChore via API. Assigns chore to child. Deletes all in afterAll.
1.1. Schedule option appears in kebab menu
- Navigate to
/parent/:childId - Click the chore card, then click the kebab "Options" button
- expect: "Schedule" button is visible in the menu
1.2. Schedule modal opens with correct title and subtitle
- Open kebab menu → click "Schedule"
- expect: Modal heading shows "Schedule Chore"
- expect: Modal subtitle shows the chore's name
1.3. Default state for new schedule — days mode selected, no days checked
- Open the Schedule modal for a chore with no existing schedule
- expect: "Specific Days" mode button is active
- expect: No day chips are in the active state
- expect: Enable/disable toggle shows "Enabled" label and is ON
1.4. Mode toggle switches between Specific Days and Every X Days
- Open the Schedule modal
- Click "Every X Days" button
- expect: Interval form is visible (stepper, "Starting on" date field, deadline row)
- Click "Specific Days" button
- expect: Day chips row is visible
1.5. Cancel closes the modal without saving
- Open the Schedule modal, select Monday, click Cancel
- Reopen the modal
- expect: No days are selected (change was not persisted)
1.6. Save is disabled when form is not dirty
- Open the Schedule modal (no existing schedule)
- expect: Save button is disabled
2. Schedule Modal — Specific Days Mode
File: e2e/mode_parent/chore-scheduler/schedule-days-mode.spec.ts
Seed: Creates child ScheduleDaysChild and chore ScheduleDaysChore via API. Assigns chore. Deletes all in afterAll.
2.1. Clicking day chips toggles them active/inactive
- Open Schedule modal
- Click "Mo" chip
- expect: "Mo" chip has active styling
- Click "Mo" chip again
- expect: "Mo" chip is no longer active
2.2. Default deadline row appears when at least one day is selected
- Open Schedule modal, no days selected
- expect: No deadline row visible
- Click "Tu" chip
- expect: Deadline row with "Deadline" label and a time picker appears
2.3. Default deadline "Anytime" toggle hides the time picker
- Select a day so the deadline row appears
- Click "Clear (Anytime)" link
- expect: Time picker disappears, "Anytime" label is shown
- Click "Set deadline" link
- expect: Time picker reappears
2.4. Exception time — "Set different time" creates per-day override
- Select Monday and Wednesday
- In the day list, click "Set different time" on Wednesday
- expect: A separate time picker appears for Wednesday
- expect: Monday still shows the default time label
2.5. Exception time — "Reset to default" removes the override
- With Wednesday exception set, click "Reset to default" on Wednesday
- expect: Wednesday row shows the default time label again
2.6. Save with specific days — schedule persists on reopen
- Select Monday and Friday, set default deadline to 03:00 PM, Save
- Reopen the Schedule modal
- expect: Monday and Friday chips are active
- expect: Default deadline shows 03:00 PM
2.7. Save with zero days selected deletes the schedule
- Open a schedule that has days selected, uncheck all days, Save
- Reopen the modal
- expect: No days selected (schedule was removed), form appears as new
2.8. Dirty detection — changing days enables Save
- Open modal with an existing schedule
- Toggle a different day
- expect: Save becomes enabled
3. Schedule Modal — Every X Days Mode
File: e2e/mode_parent/chore-scheduler/schedule-interval-mode.spec.ts
Seed: Creates child ScheduleIntervalChild and chore ScheduleIntervalChore via API. Assigns chore. Deletes all in afterAll.
3.1. Interval stepper increments and decrements within 1–7
- Switch to "Every X Days" mode
- expect: Stepper shows "1" by default
- expect: Decrement (−) button is disabled at 1
- Click increment (+) 6 times
- expect: Value shows "7"
- expect: Increment (+) button is disabled at 7
3.2. Anchor date field shows today's date by default
- Switch to "Every X Days" mode
- expect: "Starting on" date field contains today's ISO date
3.3. Next occurrence preview label updates correctly
- Set interval to 2, set anchor date to today
- expect: "Next occurrence" label shows a date 2 days from today
3.4. Interval deadline "Anytime" toggle works
- Switch to interval mode; deadline time picker is visible by default
- Click "Clear (Anytime)"
- expect: Time picker disappears, "Anytime" label shown
- Click "Set deadline"
- expect: Time picker reappears
3.5. Save interval schedule — persists on reopen
- Set interval to 3, pick a specific anchor date, set deadline to 04:30 PM, Save
- Reopen Schedule modal
- expect: "Every X Days" mode is active
- expect: Interval shows 3, anchor date matches, deadline shows 04:30 PM
3.6. Dirty detection — changing interval enables Save
- Open modal with an existing interval schedule
- Change interval from 3 to 5
- expect: Save becomes enabled
4. Schedule Enable/Disable Toggle
File: e2e/mode_parent/chore-scheduler/schedule-enable-disable.spec.ts
Seed: Creates child ScheduleToggleChild and chore ScheduleToggleChore via API. Assigns chore. Creates a "Specific Days" schedule for a non-today weekday via API. Deletes all in afterAll.
4.1. Toggle row is visible in the schedule modal
- Open Schedule modal
- expect: Toggle row with "Enabled" label and a switch is visible above the mode buttons
4.2. Toggling OFF shows "Paused" label and dims the form body
- Click the toggle switch to OFF
- expect: Label changes to "Paused"
- expect: Mode toggle and form fields have reduced opacity (dimmed)
- expect: Cancel and Save buttons remain fully interactive
4.3. Toggling ON shows "Enabled" label and restores the form body
- Toggle OFF, then toggle back ON
- expect: Label returns to "Enabled"
- expect: Form body opacity is restored
4.4. Save paused state — persists on reopen
- Toggle OFF, Save
- Reopen Schedule modal
- expect: Toggle is OFF, label shows "Paused", form body is dimmed
4.5. Dirty detection — toggling enabled state enables Save
- Open an existing schedule (enabled), toggle OFF
- expect: Save becomes enabled
- Toggle back ON
- expect: Save is disabled again (reverted to original)
4.6. Paused schedule in parent mode — chore is visible (not grayed out)
- Set up a schedule for a non-today weekday, then pause it via API
- Navigate to
/parent/:childId - expect: Chore card is visible and NOT grayed out (paused = show always like unscheduled)
4.7. Paused schedule in child mode — chore is visible (not hidden)
- Same paused schedule as 4.6
- Navigate to
/child/:childId - expect: Chore card IS visible (paused = scheduled for every day)
4.8. Paused schedule shows no "Due by" label
- Pause a schedule that has a deadline set
- Navigate to
/parent/:childId - expect: No "Due by" sub-text on the chore card (paused = no deadline)
5. Parent Mode — Chore Card Schedule States
File: e2e/mode_parent/chore-scheduler/parent-card-states.spec.ts
Seed: Creates child ParentCardChild with multiple chores via API. Assigns chores. Creates various schedules via API. Deletes all in afterAll.
Chores:
AlwaysActiveChore— no schedule (always visible, normal appearance)ScheduledTodayChore— schedule includes today's weekday, deadline at 23:59NotScheduledTodayChore— schedule for a weekday that is NOT todayExpiredChore— schedule for today's weekday, deadline at 00:01 (guaranteed past)AnytimeChore— schedule for today, deadline set to "Anytime" (no expiry)
5.1. Chore with no schedule — normal appearance, no annotations
- Navigate to
/parent/:childId - expect:
AlwaysActiveChorecard is visible with normal styling (no grayout, no badge)
5.2. Chore scheduled for today — shows "Due by" label
- expect:
ScheduledTodayChorecard is visible - expect: Card shows "Due by 11:59 PM" sub-text
- expect: Card is NOT grayed out
5.3. Chore not scheduled for today — grayed out in parent mode
- expect:
NotScheduledTodayChorecard IS visible (parent mode shows all chores) - expect: Card has grayed-out styling (
.chore-inactiveclass — reduced opacity)
5.4. Chore with expired deadline — grayed out with TOO LATE badge
- expect:
ExpiredChorecard is visible - expect: Card has grayed-out styling
- expect: "TOO LATE" badge is visible on the card
5.5. Chore with "Anytime" deadline — no "Due by" label, no TOO LATE
- expect:
AnytimeChorecard is visible with normal styling - expect: No "Due by" sub-text
- expect: No "TOO LATE" badge
5.6. Kebab menu always accessible on grayed-out chores
- Click the
NotScheduledTodayChorecard (grayed out) - expect: Kebab "Options" button is visible
- Click "Options" → "Schedule" is available
- expect: Schedule modal opens normally
5.7. Chore can still be triggered by parent regardless of schedule state
- Click the
ExpiredChore(which has TOO LATE badge) - Click again to activate
- expect: Confirmation dialog appears (parent can still trigger expired chores)
6. Child Mode — Chore Visibility and Schedule States
File: e2e/mode_parent/chore-scheduler/child-card-states.spec.ts
Seed: Creates child ChildCardChild with multiple chores. Assigns chores. Creates schedules via API. Deletes all in afterAll. Uses same chore set as test 5 where applicable.
Chores:
ChildAlwaysChore— no scheduleChildTodayChore— scheduled for today, deadline at 23:59ChildNotTodayChore— scheduled for a non-today weekdayChildExpiredChore— scheduled for today, deadline at 00:01 (past)ChildAnytimeChore— scheduled for today, deadline "Anytime"
6.1. Chore with no schedule — visible normally in child mode
- Navigate to
/child/:childId - expect:
ChildAlwaysChorecard is visible with normal styling
6.2. Chore scheduled for today — visible with "Due by" label
- expect:
ChildTodayChorecard is visible - expect: "Due by 11:59 PM" sub-text shown on card
- expect: Card is NOT grayed out
6.3. Chore not scheduled for today — HIDDEN in child mode
- expect:
ChildNotTodayChorecard is NOT visible (fully hidden, not just grayed out)
6.4. Chore with expired deadline — grayed out with TOO LATE badge
- expect:
ChildExpiredChorecard IS visible (still shown, but expired) - expect: Card has grayed-out styling
- expect: "TOO LATE" badge visible
6.5. Chore with "Anytime" — visible, no badge, no "Due by"
- expect:
ChildAnytimeChorecard visible with normal styling - expect: No "Due by" label, no "TOO LATE" badge
6.6. No kebab menu or schedule controls in child mode
- Click on
ChildTodayChorecard - expect: No "Options" button or kebab menu appears on the card
7. Parent vs Child Mode Comparison
File: e2e/mode_parent/chore-scheduler/parent-vs-child.spec.ts
Seed: Creates child CompareChild and two chores: VisibleBothChore (scheduled for today) and ParentOnlyChore (scheduled for a non-today weekday). Assigns both. Deletes all in afterAll.
7.1. Parent mode shows all chores including off-day ones
- Navigate to
/parent/:childId - expect: Both
VisibleBothChoreandParentOnlyChoreare visible - expect:
ParentOnlyChoreis grayed out (chore-inactive) - expect:
VisibleBothChoreis NOT grayed out
7.2. Child mode hides off-day chores
- Navigate to
/child/:childId - expect:
VisibleBothChoreIS visible - expect:
ParentOnlyChoreis NOT visible (hidden entirely)
7.3. Adding a schedule via parent mode hides chore in child mode on wrong day
- Navigate to
/parent/:childId - Open Schedule for
VisibleBothChore, set it to a non-today weekday only, Save - Navigate to
/child/:childId - expect:
VisibleBothChoreis no longer visible in child mode
7.4. Removing a schedule via parent mode shows chore in child mode again
- Navigate to
/parent/:childId - Open Schedule for the chore, uncheck all days (removes schedule), Save
- Navigate to
/child/:childId - expect: Chore is visible again in child mode (no schedule = always shown)
7.5. Paused schedule — chore visible in both modes
- Set a non-today schedule and pause it via API
- Navigate to
/parent/:childId - expect: Chore visible, NOT grayed out (paused = acts unscheduled)
- Navigate to
/child/:childId - expect: Chore IS visible (paused = shows every day)
8. Extend Time
File: e2e/mode_parent/chore-scheduler/extend-time.spec.ts
Seed: Creates child ExtendTimeChild and chore ExtendTimeChore with a schedule for today and a deadline at 00:01 (guaranteed expired). Assigns chore. Deletes all in afterAll.
8.1. "Extend Time" appears in kebab menu only when chore is expired
- Navigate to
/parent/:childId - expect:
ExtendTimeChoreshows "TOO LATE" badge (deadline is 00:01, already past) - Click chore card, then "Options"
- expect: "Extend Time" menu item is visible
8.2. "Extend Time" is absent when chore is not expired
- Set up a second chore with deadline at 23:59 (not yet passed)
- Click chore card, then "Options"
- expect: "Extend Time" menu item is NOT visible
8.3. Clicking "Extend Time" removes the TOO LATE badge
- Click "Extend Time" on the expired chore
- expect: "TOO LATE" badge disappears from the card
- expect: Card is no longer grayed out
8.4. Extended chore no longer shows "Due by" label
- After extending, the chore card
- expect: No "Due by" sub-text (extended = no deadline for remainder of day)
8.5. Extending the same chore twice returns 409
- After the first extension, try opening kebab again
- expect: "Extend Time" is no longer in the menu (chore is no longer expired)
8.6. Extension effect is visible in child mode
- After extending the chore in parent mode
- Navigate to
/child/:childId - expect: Chore is visible and NOT showing "TOO LATE" badge
9. Schedule with Interval Mode — Anchor Date & Next Occurrence
File: e2e/mode_parent/chore-scheduler/interval-anchor.spec.ts
Seed: Creates child IntervalAnchorChild and chore IntervalAnchorChore. Assigns chore. Deletes all in afterAll.
9.1. Interval schedule set to every 1 day from today — chore active today
- Create interval schedule:
interval_days=1,anchor_date=today, deadline 23:59 - Navigate to
/parent/:childId - expect: Chore is NOT grayed out (hits today)
- Navigate to
/child/:childId - expect: Chore IS visible
9.2. Interval schedule set to every 2 days from yesterday — chore NOT active today
- Create interval schedule:
interval_days=2,anchor_date=yesterday - Navigate to
/parent/:childId - expect: Chore IS grayed out (yesterday + 2 = tomorrow, not today)
- Navigate to
/child/:childId - expect: Chore is NOT visible
9.3. Interval schedule set to every 2 days starting today — active today
- Create interval schedule:
interval_days=2,anchor_date=today - Navigate to
/parent/:childId - expect: Chore is NOT grayed out
- Navigate to
/child/:childId - expect: Chore IS visible
9.4. Interval "Anytime" deadline — no "Due by" label
- Create interval schedule with
interval_has_deadline=false - Navigate to
/parent/:childId - expect: No "Due by" sub-text on the chore card
10. SSE Real-Time Updates
File: e2e/mode_parent/chore-scheduler/schedule-sse.spec.ts
Seed: Creates child SSEScheduleChild and chore SSEScheduleChore. Assigns chore. Deletes all in afterAll.
10.1. Setting a schedule via API reflects in an already-open parent view
- Navigate to
/parent/:childId - expect: Chore is visible, no schedule annotations
- Via API call (
request.put), create a schedule for a non-today weekday - expect: Within a few seconds, the chore card becomes grayed out (SSE
chore_schedule_modifiedfires, list refreshes)
10.2. Deleting a schedule via API un-grays the chore in parent view
- (Continuing from 10.1) Delete the schedule via API
- expect: Within a few seconds, the chore card returns to normal styling
10.3. Extending time via API reflects in an already-open parent view
- Set up a schedule with a past deadline (00:01) via API so chore is expired
- Navigate to
/parent/:childId - expect: "TOO LATE" badge visible
- Extend time via API call
- expect: "TOO LATE" badge disappears (SSE
chore_time_extendedfires, list refreshes)
11. Schedule Loading — Backward Compatibility
File: e2e/mode_parent/chore-scheduler/schedule-load.spec.ts
Seed: Creates child and chore. Uses direct API calls to create schedules with specific field combinations. Deletes all in afterAll.
11.1. Schedule with enabled field missing defaults to enabled
- Create schedule via API without
enabledfield - Open Schedule modal
- expect: Toggle shows "Enabled" (defaults to true)
11.2. Schedule with interval_has_deadline=false shows "Anytime"
- Create interval schedule with
interval_has_deadline=false - Open Schedule modal
- expect: "Anytime" label is shown instead of time picker
11.3. Existing days schedule with mixed times loads correctly
- Create days schedule with 3 days: Mon (08:00), Wed (08:00), Fri (10:30) — Fri is an exception
- Open Schedule modal
- expect: Mo, We, Fr chips are active
- expect: Default deadline shows 08:00 AM
- expect: Friday row shows its own time picker with 10:30 AM
11.4. Interval schedule loads anchor date and interval correctly
- Create interval schedule via API with
anchor_date="2026-03-15",interval_days=3 - Open Schedule modal
- expect: "Every X Days" mode is active
- expect: Stepper shows 3
- expect: Date field shows 2026-03-15
CSS Spec References
.chore-inactive:opacity: 0.45; filter: grayscale(60%)— applied in both parent and child views.chore-stamp: absolute-positioned badge overlay for "TOO LATE", "COMPLETED", "PENDING".due-label: "Due by X:XX PM" sub-text below point value.schedule-body.disabled:opacity: 0.45; pointer-events: none— dim wrapper when schedule is paused.toggle-track.on:background: var(--btn-primary)— toggle ON state