test(meds): playwright e2e for every wizard cadence shape

Six specs walk the seven-step creation wizard end-to-end via the
German label surface, one per cadence kind, with shared step helpers
behind a single API stub. Verifies the medication lands in the list
view with the right next-due chip; one-shot suppresses the chip per
the design. Also closes the missing `wizard.page.backToList` key the
host route reads on /medications/new.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-28 09:42:10 +02:00
1 parent 030e97906c
commit b4b099003f
13 files changed
+823

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
import { expect, test } from "@playwright/test";
import {
STUB_MEDICATION_ID,
clickCreate,
clickNext,
expectMedicationOnList,
expectRedirectToMedication,
expectStep,
fillStep1,
mockMedicationsApi,
stubDashboardAnalytics,
} from "./medications-wizard-helpers";
import { STORAGE_STATE_PATH } from "./setup/global-setup";
/**
* Medication wizard end-to-end — bi-weekly cadence (every N weeks).
*
* The "everyNWeeks" picker carries two sub-controls: an integer-week
* input (defaults to 2) and a weekday chip row (defaults to [MO]).
* Keeping both defaults yields `FREQ=WEEKLY;INTERVAL=2;BYDAY=MO`,
* which the step-7 summary phrase pins as "Alle zwei Wochen".
*
* Bi-weekly is the canonical regression case from the design-synthesis
* (the pre-v1.5 reminder worker ignored intervalWeeks). This spec
* pins the wizard's emit path; the worker fix lives in the unit suite.
*/
test.describe("medication wizard — bi-weekly", () => {
test.use({ storageState: STORAGE_STATE_PATH });
test("creates an every-2-weeks Monday medication end-to-end", async ({
page,
}) => {
await stubDashboardAnalytics(page);
const capture = mockMedicationsApi(page, {
id: STUB_MEDICATION_ID,
name: "Methotrexat",
dose: "10 mg",
rrule: "FREQ=WEEKLY;INTERVAL=2;BYDAY=MO",
rollingIntervalDays: null,
oneShot: false,
});
await page.goto("/medications/new", { waitUntil: "domcontentloaded" });
await fillStep1(page, { name: "Methotrexat", doseAmount: "10" });
await clickNext(page);
await expectStep(page, 2);
await page.getByRole("radio", { name: /Wiederkehrend/i }).click();
await clickNext(page);
await expectStep(page, 3);
await page
.getByRole("radio", { name: /Alle N Wochen an bestimmten Tagen/i })
.click();
// Sub-controls render inline. The integer input defaults to 2; we
// re-fill it explicitly so the spec is robust to default drift.
const weeksInput = page
.locator('[data-slot="cadence-number-input"]')
.first();
await weeksInput.fill("2");
await clickNext(page);
await expectStep(page, 4);
await clickNext(page);
await expectStep(page, 5);
await clickNext(page);
await expectStep(page, 6);
await clickNext(page);
await expectStep(page, 7);
const summary = page.locator('[data-slot="wizard-step7-summary"]');
// n=2 collapses to the "biweekly" summary leaf (intervalWeeks ===
// 2 short-circuits in `summaryKeyForCadence`).
await expect(summary).toContainText(/Alle zwei Wochen/i);
await clickCreate(page);
await expectRedirectToMedication(page, STUB_MEDICATION_ID);
expect(capture.postBody).toMatchObject({
name: "Methotrexat",
oneShot: false,
schedules: [{ rrule: "FREQ=WEEKLY;INTERVAL=2;BYDAY=MO" }],
});
await expectMedicationOnList(page, {
name: "Methotrexat",
withNextDueChip: true,
});
});
});
+100
View File
@@ -0,0 +1,100 @@
import { expect, test } from "@playwright/test";
import {
STUB_MEDICATION_ID,
clickCreate,
clickNext,
expectMedicationOnList,
expectRedirectToMedication,
expectStep,
fillStep1,
mockMedicationsApi,
stubDashboardAnalytics,
} from "./medications-wizard-helpers";
import { STORAGE_STATE_PATH } from "./setup/global-setup";
/**
* Medication wizard end-to-end — daily cadence.
*
* Walks the seven-step `CreationWizard` from `/medications/new` using
* the DE label surface from `messages/de.json`, picks "Jeden Tag" on
* step 3, accepts the default 08:00 chip on step 5, accepts today as
* startsOn on step 6, then submits. After the POST the spec asserts
* the URL settled on `/medications/<id>` AND the medication appears on
* `/medications` with the "Nächste Einnahme:" chip.
*/
test.describe("medication wizard — daily", () => {
test.use({ storageState: STORAGE_STATE_PATH });
test("creates a daily medication end-to-end", async ({ page }) => {
await stubDashboardAnalytics(page);
const capture = mockMedicationsApi(page, {
id: STUB_MEDICATION_ID,
name: "Vitamin D",
dose: "1000 iu",
rrule: "FREQ=DAILY",
rollingIntervalDays: null,
oneShot: false,
});
await page.goto("/medications/new", { waitUntil: "domcontentloaded" });
// Step 1 — name + dose.
await fillStep1(page, { name: "Vitamin D", doseAmount: "1000" });
await page.locator("#wizard-dose-unit").click();
await page.getByRole("option", { name: /I\.E\./i }).click();
await clickNext(page);
// Step 2 — Recurring.
await expectStep(page, 2);
await page.getByRole("radio", { name: /Wiederkehrend/i }).click();
await clickNext(page);
// Step 3 — cadence: "Jeden Tag" (daily is the default but we click
// it explicitly so the spec is robust to defaults drifting).
await expectStep(page, 3);
await page.getByRole("radio", { name: /^Jeden Tag$/i }).click();
await clickNext(page);
// Step 4 — recap.
await expectStep(page, 4);
await expect(
page.locator('[data-slot="wizard-step4-recap"]'),
).toContainText(/Jeden Tag/i);
await clickNext(page);
// Step 5 — times of day (08:00 default chip).
await expectStep(page, 5);
await expect(
page.locator('[data-slot="times-of-day-chip"][data-time="08:00"]'),
).toBeVisible();
await clickNext(page);
// Step 6 — course window (defaults to today, no end date).
await expectStep(page, 6);
await expect(
page.locator('[data-slot="course-window-starts"]'),
).toHaveValue(/^\d{4}-\d{2}-\d{2}$/);
await clickNext(page);
// Step 7 — summary + create.
await expectStep(page, 7);
const summary = page.locator('[data-slot="wizard-step7-summary"]');
await expect(summary).toContainText(/Jeden Tag/i);
await expect(summary).toContainText(/08:00/);
await clickCreate(page);
await expectRedirectToMedication(page, STUB_MEDICATION_ID);
expect(capture.postBody).toMatchObject({
name: "Vitamin D",
oneShot: false,
schedules: [{ rrule: "FREQ=DAILY" }],
});
await expectMedicationOnList(page, {
name: "Vitamin D",
withNextDueChip: true,
});
});
});
+221
View File
@@ -0,0 +1,221 @@
import { expect, type Page, type Route } from "@playwright/test";
/**
* Shared helpers for the medication-wizard cadence specs.
*
* The six specs (daily / weekdays / biweekly / monthly / rolling /
* oneshot) walk the same seven-step skeleton with different sub-step
* inputs. The shared pieces — POST + GET stubs, list-page entry check,
* step-indicator assertions, dashboard analytics stub — live here so
* the per-cadence specs only carry the cadence-specific selectors.
*
* No abstraction over the seven steps themselves: the cadence-specific
* differences (which radio to pick, which sub-control to fill) are the
* point of the spec, so the per-spec code expresses them directly.
*/
/** The medication id the POST stub echoes back to the wizard. */
export const STUB_MEDICATION_ID = "med_e2e_wizard";
/**
* The minimum medication shape `/medications` page reads through
* `MedicationCard`. The list reads `data: Medication[]` and the card
* itself is forgiving about missing optional fields.
*/
interface StubbedMedication {
id: string;
name: string;
dose: string;
rrule: string | null;
rollingIntervalDays: number | null;
oneShot: boolean;
}
/**
* Mock `/api/medications` for both the POST (creation) and the GET
* (list refresh after the wizard redirects). Returns the captured
* request body so each spec can assert against it independently.
*
* The list GET echoes the just-posted medication so the `/medications`
* page paints a card the spec can pin against.
*/
export function mockMedicationsApi(page: Page, stubbed: StubbedMedication) {
const capture: { postBody: unknown } = { postBody: null };
// The list page reads the schedule shape too — surface the new
// v1.5 fields the card consumes (rrule, rollingIntervalDays, oneShot,
// timesOfDay). windowStart/windowEnd stay required by the card's
// legacy fallback even when timesOfDay is populated.
const listEntry = {
id: stubbed.id,
name: stubbed.name,
dose: stubbed.dose,
category: "MEDICATION",
active: true,
notificationsEnabled: true,
pausedAt: null,
lastTakenAt: null,
startsOn: null,
endsOn: null,
oneShot: stubbed.oneShot,
schedules: [
{
id: "sch_e2e_1",
windowStart: "08:00",
windowEnd: "09:00",
label: null,
dose: null,
daysOfWeek: null,
timesOfDay: ["08:00"],
rrule: stubbed.rrule,
rollingIntervalDays: stubbed.rollingIntervalDays,
reminderGraceMinutes: null,
},
],
};
// The `**/api/medications*` glob matches the bare collection URL the
// wizard posts to AND the list query the medications page issues.
// Routes registered earlier win, so the list page never falls through
// to a real server response.
void page.route("**/api/medications**", async (route: Route) => {
const req = route.request();
const url = new URL(req.url());
// Sub-paths (e.g. /api/medications/<id>/...) — let them through
// unhandled so the spec only stubs the surfaces it owns.
if (url.pathname !== "/api/medications") {
await route.fallback();
return;
}
if (req.method() === "POST") {
capture.postBody = JSON.parse(req.postData() ?? "null");
await route.fulfill({
status: 201,
contentType: "application/json",
body: JSON.stringify({
data: { ...listEntry },
error: null,
}),
});
return;
}
if (req.method() === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
data: [listEntry],
error: null,
}),
});
return;
}
await route.fallback();
});
return capture;
}
/**
* Stub the analytics fetches that fire on `/medications` so the page
* paints without depending on the seed user's measurement state. The
* `/api/analytics(?slice=…)` regex matches both the slim and thick
* dashboard variants — same pattern the other specs use.
*/
export async function stubDashboardAnalytics(page: Page): Promise<void> {
await page.route(/\/api\/analytics(\?|$)/, (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
data: { summaries: {}, bpInTargetPct: null, glucoseByContext: {} },
error: null,
}),
}),
);
await page.route("**/api/mood/analytics", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
data: { entries: [], summary: { count: 0 } },
error: null,
}),
}),
);
}
/**
* Fill the Step 1 name + dose-amount inputs and confirm the step
* indicator reads "Schritt 1 von 7" before we leave it. The dose unit
* already defaults to "mg" via `emptyWizardPayload()` so we leave it
* alone unless the caller wants something else.
*/
export async function fillStep1(
page: Page,
{ name, doseAmount }: { name: string; doseAmount: string },
): Promise<void> {
await expect(page.getByText(/Schritt 1 von 7/i)).toBeVisible();
await page.locator("#wizard-name").fill(name);
await page.locator("#wizard-dose-amount").fill(doseAmount);
}
/**
* Click the wizard's primary Next button. Pinned by data-slot so we do
* not collide with any other "Weiter" / "Next" labelled control the
* page chrome may render.
*/
export async function clickNext(page: Page): Promise<void> {
await page.locator('[data-slot="wizard-next"]').click();
}
/**
* Click the wizard's primary Create button at step 7.
*/
export async function clickCreate(page: Page): Promise<void> {
await page.locator('[data-slot="wizard-create"]').click();
}
/** Wait for the wizard to land on a given step (1-7). */
export async function expectStep(page: Page, step: number): Promise<void> {
await expect(
page.getByText(new RegExp(`Schritt ${step} von 7`, "i")),
).toBeVisible();
}
/**
* Verify the post-submit URL settles on `/medications/<id>`. Next.js
* 404s for an unknown id which renders the `not-found.tsx` boundary
* cleanly; the URL is the contract we care about, not the page body.
*/
export async function expectRedirectToMedication(
page: Page,
id: string,
): Promise<void> {
await expect
.poll(() => new URL(page.url()).pathname, { timeout: 10_000 })
.toBe(`/medications/${id}`);
}
/**
* Navigate to the list and assert the just-created medication appears
* with the "Nächste Einnahme:" next-due chip (`medications.nextIntake`
* leaf in `messages/de.json`). The one-shot variant uses a different
* surface — the list still shows the medication name; we only assert
* on the chip for recurring cadences.
*/
export async function expectMedicationOnList(
page: Page,
{ name, withNextDueChip }: { name: string; withNextDueChip: boolean },
): Promise<void> {
await page.goto("/medications", { waitUntil: "domcontentloaded" });
const main = page.locator("main");
await expect(main.getByText(name).first()).toBeVisible({ timeout: 10_000 });
if (withNextDueChip) {
// "Nächste Einnahme:" with the trailing colon is the literal label
// surfaced by `medications.nextIntake`. The card renders the chip
// when the medication is active AND has a next schedule slot, both
// true for the stubbed entry.
await expect(main.getByText(/Nächste Einnahme:/i).first()).toBeVisible();
}
}
+89
View File
@@ -0,0 +1,89 @@
import { expect, test } from "@playwright/test";
import {
STUB_MEDICATION_ID,
clickCreate,
clickNext,
expectMedicationOnList,
expectRedirectToMedication,
expectStep,
fillStep1,
mockMedicationsApi,
stubDashboardAnalytics,
} from "./medications-wizard-helpers";
import { STORAGE_STATE_PATH } from "./setup/global-setup";
/**
* Medication wizard end-to-end — monthly cadence.
*
* Picks "Monatlich an einem bestimmten Tag" on step 3 and pins
* dayOfMonth = 15. The emitted rrule is `FREQ=MONTHLY;BYMONTHDAY=15`
* and the step-7 summary surfaces the "Monatlich" phrase.
*/
test.describe("medication wizard — monthly", () => {
test.use({ storageState: STORAGE_STATE_PATH });
test("creates a monthly-on-day-15 medication end-to-end", async ({
page,
}) => {
await stubDashboardAnalytics(page);
const capture = mockMedicationsApi(page, {
id: STUB_MEDICATION_ID,
name: "Vitamin B12",
dose: "1000 mcg",
rrule: "FREQ=MONTHLY;BYMONTHDAY=15",
rollingIntervalDays: null,
oneShot: false,
});
await page.goto("/medications/new", { waitUntil: "domcontentloaded" });
await fillStep1(page, { name: "Vitamin B12", doseAmount: "1000" });
await clickNext(page);
await expectStep(page, 2);
await page.getByRole("radio", { name: /Wiederkehrend/i }).click();
await clickNext(page);
await expectStep(page, 3);
await page
.getByRole("radio", { name: /Monatlich an einem bestimmten Tag/i })
.click();
// Set day-of-month to 15. The sub-control input is the only number
// input inside the selected `monthly` option block.
const dayInput = page
.locator('[data-slot="cadence-number-input"]')
.first();
await dayInput.fill("15");
await clickNext(page);
await expectStep(page, 4);
await clickNext(page);
await expectStep(page, 5);
await clickNext(page);
await expectStep(page, 6);
await clickNext(page);
await expectStep(page, 7);
const summary = page.locator('[data-slot="wizard-step7-summary"]');
await expect(summary).toContainText(/Monatlich/i);
await clickCreate(page);
await expectRedirectToMedication(page, STUB_MEDICATION_ID);
expect(capture.postBody).toMatchObject({
name: "Vitamin B12",
oneShot: false,
schedules: [{ rrule: "FREQ=MONTHLY;BYMONTHDAY=15" }],
});
await expectMedicationOnList(page, {
name: "Vitamin B12",
withNextDueChip: true,
});
});
});
+98
View File
@@ -0,0 +1,98 @@
import { expect, test } from "@playwright/test";
import {
STUB_MEDICATION_ID,
clickCreate,
clickNext,
expectMedicationOnList,
expectRedirectToMedication,
expectStep,
fillStep1,
mockMedicationsApi,
stubDashboardAnalytics,
} from "./medications-wizard-helpers";
import { STORAGE_STATE_PATH } from "./setup/global-setup";
/**
* Medication wizard end-to-end — one-shot cadence.
*
* Picking "Einmaldosis" on step 2 short-circuits the wizard past
* steps 3 + 4 and lands on step 5, where the user picks the single
* dose date alongside the time-of-day chip. Step 6 keeps endsOn
* pinned to startsOn via `lockEndsToStart`, and step 7 surfaces the
* "Einmaldosis" summary phrase.
*
* The list-page next-due chip is suppressed for one-shot medications
* because the design-synthesis lifecycle deactivates the row the
* moment the single intake is logged. The spec only asserts the row
* name appears on the list.
*/
test.describe("medication wizard — one-shot", () => {
test.use({ storageState: STORAGE_STATE_PATH });
test("creates a single-dose medication end-to-end", async ({ page }) => {
await stubDashboardAnalytics(page);
const capture = mockMedicationsApi(page, {
id: STUB_MEDICATION_ID,
name: "Grippeimpfung",
dose: "0.5 ml",
rrule: null,
rollingIntervalDays: null,
oneShot: true,
});
await page.goto("/medications/new", { waitUntil: "domcontentloaded" });
await fillStep1(page, { name: "Grippeimpfung", doseAmount: "0.5" });
await page.locator("#wizard-dose-unit").click();
await page.getByRole("option", { name: /^ml$/i }).click();
await clickNext(page);
await expectStep(page, 2);
await page.getByRole("radio", { name: /Einmaldosis/i }).click();
// The wizard skips steps 3 + 4 in one-shot mode — clicking Next on
// step 2 jumps directly to step 5 (see `goNext` in CreationWizard).
await clickNext(page);
await expectStep(page, 5);
// One-shot variant renders the single time picker (maxChips=1) and
// a dose-date input. The default startsOn is today; we override to
// a fixed date so the assertion below is deterministic.
const oneShotDate = page.locator("#wizard-oneshot-date");
await expect(oneShotDate).toBeVisible();
await oneShotDate.fill("2026-10-15");
await clickNext(page);
await expectStep(page, 6);
// One-shot pins endsOn to startsOn — the end-date input is
// read-only and the "Kein Enddatum" Switch is hidden.
await expect(
page.locator('[data-slot="course-window-ends"]'),
).toHaveValue("2026-10-15");
await expect(
page.locator('[data-slot="course-window-oneshot-caption"]'),
).toBeVisible();
await clickNext(page);
await expectStep(page, 7);
const summary = page.locator('[data-slot="wizard-step7-summary"]');
await expect(summary).toContainText(/Einmaldosis/i);
await expect(summary).toContainText(/2026-10-15/);
await clickCreate(page);
await expectRedirectToMedication(page, STUB_MEDICATION_ID);
expect(capture.postBody).toMatchObject({
name: "Grippeimpfung",
oneShot: true,
startsOn: "2026-10-15",
endsOn: "2026-10-15",
});
await expectMedicationOnList(page, {
name: "Grippeimpfung",
withNextDueChip: false,
});
});
});
+105
View File
@@ -0,0 +1,105 @@
import { expect, test } from "@playwright/test";
import {
STUB_MEDICATION_ID,
clickCreate,
clickNext,
expectMedicationOnList,
expectRedirectToMedication,
expectStep,
fillStep1,
mockMedicationsApi,
stubDashboardAnalytics,
} from "./medications-wizard-helpers";
import { STORAGE_STATE_PATH } from "./setup/global-setup";
/**
* Medication wizard end-to-end — rolling cadence.
*
* The flexible-rolling cadence is the design-synthesis differentiator
* (only Dosecast + MedTimer model it among commercial competitors).
* The wizard emits a `rollingIntervalDays` integer in place of an
* rrule string; the step-7 summary surfaces the "Alle {n} Tage seit
* der letzten Einnahme" phrase.
*
* The spec pins rollingDays = 7 — the canonical Mounjaro / Ozempic
* cadence the design-synthesis P-2 case is anchored on.
*/
test.describe("medication wizard — rolling", () => {
test.use({ storageState: STORAGE_STATE_PATH });
test("creates an every-7-days-from-last-intake medication end-to-end", async ({
page,
}) => {
await stubDashboardAnalytics(page);
const capture = mockMedicationsApi(page, {
id: STUB_MEDICATION_ID,
name: "Mounjaro",
dose: "5 mg",
rrule: null,
rollingIntervalDays: 7,
oneShot: false,
});
await page.goto("/medications/new", { waitUntil: "domcontentloaded" });
await fillStep1(page, { name: "Mounjaro", doseAmount: "5" });
await clickNext(page);
await expectStep(page, 2);
await page.getByRole("radio", { name: /Wiederkehrend/i }).click();
await clickNext(page);
await expectStep(page, 3);
await page
.getByRole("radio", {
name: /Alle N Tage seit der letzten Einnahme \(flexibel\)/i,
})
.click();
// Set rolling-days to 7. The sub-control is the only number input
// inside the selected `rolling` option block.
const daysInput = page
.locator('[data-slot="cadence-number-input"]')
.first();
await daysInput.fill("7");
// The explainer is the rolling-cadence-specific guidance the
// design-synthesis mandates — pin its presence so a future copy
// edit that drops it surfaces here.
await expect(
page.locator('[data-slot="cadence-rolling-explainer"]'),
).toBeVisible();
await clickNext(page);
await expectStep(page, 4);
await clickNext(page);
await expectStep(page, 5);
await clickNext(page);
await expectStep(page, 6);
await clickNext(page);
await expectStep(page, 7);
const summary = page.locator('[data-slot="wizard-step7-summary"]');
await expect(summary).toContainText(
/Alle 7 Tage seit der letzten Einnahme/i,
);
await clickCreate(page);
await expectRedirectToMedication(page, STUB_MEDICATION_ID);
expect(capture.postBody).toMatchObject({
name: "Mounjaro",
oneShot: false,
schedules: [{ rollingIntervalDays: 7 }],
});
await expectMedicationOnList(page, {
name: "Mounjaro",
withNextDueChip: true,
});
});
});
+96
View File
@@ -0,0 +1,96 @@
import { expect, test } from "@playwright/test";
import {
STUB_MEDICATION_ID,
clickCreate,
clickNext,
expectMedicationOnList,
expectRedirectToMedication,
expectStep,
fillStep1,
mockMedicationsApi,
stubDashboardAnalytics,
} from "./medications-wizard-helpers";
import { STORAGE_STATE_PATH } from "./setup/global-setup";
/**
* Medication wizard end-to-end — weekdays cadence.
*
* Walks the seven-step wizard via the DE label surface and picks
* "An bestimmten Wochentagen" on step 3, toggles Mo + Mi + Fr on the
* weekday chip-row, then completes the rest of the flow. The emitted
* rrule is `FREQ=WEEKLY;BYDAY=MO,WE,FR`.
*/
test.describe("medication wizard — weekdays", () => {
test.use({ storageState: STORAGE_STATE_PATH });
test("creates a Mon/Wed/Fri medication end-to-end", async ({ page }) => {
await stubDashboardAnalytics(page);
const capture = mockMedicationsApi(page, {
id: STUB_MEDICATION_ID,
name: "Ibuprofen",
dose: "400 mg",
rrule: "FREQ=WEEKLY;BYDAY=MO,WE,FR",
rollingIntervalDays: null,
oneShot: false,
});
await page.goto("/medications/new", { waitUntil: "domcontentloaded" });
await fillStep1(page, { name: "Ibuprofen", doseAmount: "400" });
await clickNext(page);
await expectStep(page, 2);
await page.getByRole("radio", { name: /Wiederkehrend/i }).click();
await clickNext(page);
await expectStep(page, 3);
await page
.getByRole("radio", { name: /An bestimmten Wochentagen/i })
.click();
// Toggle Mo + Mi + Fr — `aria-label` uses the long DE name.
await page
.getByRole("button", { name: /^Montag$/i })
.click();
// The "weekdays" default is [MO] so Monday may already be active;
// re-toggle if so. Cheaper than peeking at aria-pressed: just lean
// on the data-active attribute.
const moChip = page.locator(
'[data-slot="cadence-weekday-chip"][data-token="MO"]',
);
if ((await moChip.getAttribute("data-active")) === "false") {
await moChip.click();
}
await page.getByRole("button", { name: /^Mittwoch$/i }).click();
await page.getByRole("button", { name: /^Freitag$/i }).click();
await clickNext(page);
await expectStep(page, 4);
await clickNext(page);
await expectStep(page, 5);
await clickNext(page);
await expectStep(page, 6);
await clickNext(page);
await expectStep(page, 7);
const summary = page.locator('[data-slot="wizard-step7-summary"]');
await expect(summary).toContainText(/An ausgewählten Wochentagen/i);
await clickCreate(page);
await expectRedirectToMedication(page, STUB_MEDICATION_ID);
expect(capture.postBody).toMatchObject({
name: "Ibuprofen",
oneShot: false,
schedules: [{ rrule: "FREQ=WEEKLY;BYDAY=MO,WE,FR" }],
});
await expectMedicationOnList(page, {
name: "Ibuprofen",
withNextDueChip: true,
});
});
});
+3
View File
@@ -749,6 +749,9 @@
},
"create": {
"wizard": {
"page": {
"backToList": "Zurück zu den Medikamenten"
},
"header": {
"stepOf": "Schritt {current} von {total}",
"title": {
+3
View File
@@ -749,6 +749,9 @@
},
"create": {
"wizard": {
"page": {
"backToList": "Back to medications"
},
"header": {
"stepOf": "Step {current} of {total}",
"title": {
+3
View File
@@ -749,6 +749,9 @@
},
"create": {
"wizard": {
"page": {
"backToList": "Back to medications"
},
"header": {
"stepOf": "Step {current} of {total}",
"title": {
+3
View File
@@ -749,6 +749,9 @@
},
"create": {
"wizard": {
"page": {
"backToList": "Back to medications"
},
"header": {
"stepOf": "Step {current} of {total}",
"title": {
+3
View File
@@ -749,6 +749,9 @@
},
"create": {
"wizard": {
"page": {
"backToList": "Back to medications"
},
"header": {
"stepOf": "Step {current} of {total}",
"title": {
+3
View File
@@ -749,6 +749,9 @@
},
"create": {
"wizard": {
"page": {
"backToList": "Back to medications"
},
"header": {
"stepOf": "Step {current} of {total}",
"title": {