From 6456c186f608f111e0309997b0c2ff2c73359106 Mon Sep 17 00:00:00 2001 From: nihal467 Date: Wed, 15 Jul 2026 17:20:17 +0530 Subject: [PATCH 1/8] test: fix flaky device service history edit flow Wait for the Add Service Record sheet to fully close before opening the edit sheet. Previously the test clicked edit while the Add sheet was still animating out, so two Service Date popover-triggers were present and the locator hit a strict-mode violation. Ref: QA-214 --- .../settings/devices/deviceServiceHistory.spec.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/facility/settings/devices/deviceServiceHistory.spec.ts b/tests/facility/settings/devices/deviceServiceHistory.spec.ts index 82f4ce8822a..d4a493a75c4 100644 --- a/tests/facility/settings/devices/deviceServiceHistory.spec.ts +++ b/tests/facility/settings/devices/deviceServiceHistory.spec.ts @@ -72,6 +72,11 @@ test.describe("Device Service History", () => { await expect(page.getByText(notes)).toBeVisible(); + // Wait for the "Add Service Record" sheet to fully close before editing, + // otherwise its Service Date picker and Notes field overlap with the edit + // sheet's, causing a strict-mode violation. + await expect(page.getByRole("button", { name: "Save" })).toBeHidden(); + await page .locator('[data-slot="card"]') .filter({ hasText: notes }) @@ -176,6 +181,11 @@ test.describe("Device Service History", () => { await expect(page.getByText(notes)).toBeVisible(); + // Wait for the "Add Service Record" sheet to fully close before editing, + // otherwise its Service Date picker and Notes field overlap with the edit + // sheet's, causing a strict-mode violation. + await expect(page.getByRole("button", { name: "Save" })).toBeHidden(); + await page .locator('[data-slot="card"]') .filter({ hasText: notes }) From 7dedb17f2f4cc83a8520f19093a8ad9c3b027c40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:08:51 +0000 Subject: [PATCH 2/8] test: scope edit-sheet interactions to dialog to fix strict-mode violation Co-authored-by: nihal467 <57055998+nihal467@users.noreply.github.com> --- .../devices/deviceServiceHistory.spec.ts | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/tests/facility/settings/devices/deviceServiceHistory.spec.ts b/tests/facility/settings/devices/deviceServiceHistory.spec.ts index d4a493a75c4..df74b8f1db6 100644 --- a/tests/facility/settings/devices/deviceServiceHistory.spec.ts +++ b/tests/facility/settings/devices/deviceServiceHistory.spec.ts @@ -72,11 +72,6 @@ test.describe("Device Service History", () => { await expect(page.getByText(notes)).toBeVisible(); - // Wait for the "Add Service Record" sheet to fully close before editing, - // otherwise its Service Date picker and Notes field overlap with the edit - // sheet's, causing a strict-mode violation. - await expect(page.getByRole("button", { name: "Save" })).toBeHidden(); - await page .locator('[data-slot="card"]') .filter({ hasText: notes }) @@ -84,8 +79,15 @@ test.describe("Device Service History", () => { .first() .click(); + // Scope all edit-sheet interactions to its dialog to avoid strict-mode + // violations when the Add sheet is still in the DOM during its close animation. + const editSheet = page.getByRole("dialog", { + name: "Edit Service Record", + }); + await expect(editSheet).toBeVisible({ timeout: 10000 }); + const pastYear = new Date().getFullYear() - 1; - await page + await editSheet .locator('[data-slot="form-item"]') .filter({ hasText: "Service Date" }) .locator('[data-slot="popover-trigger"]') @@ -93,9 +95,11 @@ test.describe("Device Service History", () => { await page.locator(".rdp-years_dropdown").selectOption(pastYear.toString()); await page.locator('[role="gridcell"]:not([data-outside])').first().click(); - await page.getByRole("textbox", { name: "Notes *" }).fill(updatedNotes); + await editSheet + .getByRole("textbox", { name: "Notes *" }) + .fill(updatedNotes); - await page.getByRole("button", { name: "Update" }).click(); + await editSheet.getByRole("button", { name: "Update" }).click(); const updatedCard = page .locator('[data-slot="card"]') @@ -181,11 +185,6 @@ test.describe("Device Service History", () => { await expect(page.getByText(notes)).toBeVisible(); - // Wait for the "Add Service Record" sheet to fully close before editing, - // otherwise its Service Date picker and Notes field overlap with the edit - // sheet's, causing a strict-mode violation. - await expect(page.getByRole("button", { name: "Save" })).toBeHidden(); - await page .locator('[data-slot="card"]') .filter({ hasText: notes }) @@ -193,14 +192,21 @@ test.describe("Device Service History", () => { .first() .click(); - const updateButton = page.getByRole("button", { name: "Update" }); + // Scope all edit-sheet interactions to its dialog to avoid strict-mode + // violations when the Add sheet is still in the DOM during its close animation. + const editSheet = page.getByRole("dialog", { + name: "Edit Service Record", + }); + await expect(editSheet).toBeVisible({ timeout: 10000 }); + + const updateButton = editSheet.getByRole("button", { name: "Update" }); await expect(updateButton).toBeDisabled(); - await page.getByRole("textbox", { name: "Notes *" }).fill(updatedNotes); + await editSheet.getByRole("textbox", { name: "Notes *" }).fill(updatedNotes); await expect(updateButton).toBeEnabled(); - await page.getByRole("textbox", { name: "Notes *" }).fill(notes); + await editSheet.getByRole("textbox", { name: "Notes *" }).fill(notes); await expect(updateButton).toBeDisabled(); }); From bd42346b92b55db0cbe62cb21422c34702034fb3 Mon Sep 17 00:00:00 2001 From: nihal467 Date: Thu, 16 Jul 2026 12:12:00 +0530 Subject: [PATCH 3/8] test: wait for Add sheet close and drop hard-coded dialog title Address review feedback: assert the Add Service Record sheet's Save button is hidden before opening the edit sheet (fixes the close-animation race at its source), and scope to the sole remaining dialog instead of the locale-dependent 'Edit Service Record' title. Ref: QA-214 --- .../devices/deviceServiceHistory.spec.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/facility/settings/devices/deviceServiceHistory.spec.ts b/tests/facility/settings/devices/deviceServiceHistory.spec.ts index df74b8f1db6..6b631a83517 100644 --- a/tests/facility/settings/devices/deviceServiceHistory.spec.ts +++ b/tests/facility/settings/devices/deviceServiceHistory.spec.ts @@ -72,6 +72,11 @@ test.describe("Device Service History", () => { await expect(page.getByText(notes)).toBeVisible(); + // Wait for the Add sheet to fully close before opening Edit; otherwise its + // controls linger in the DOM during the close animation and collide with + // the edit sheet's controls. + await expect(page.getByRole("button", { name: "Save" })).toBeHidden(); + await page .locator('[data-slot="card"]') .filter({ hasText: notes }) @@ -79,11 +84,9 @@ test.describe("Device Service History", () => { .first() .click(); - // Scope all edit-sheet interactions to its dialog to avoid strict-mode - // violations when the Add sheet is still in the DOM during its close animation. - const editSheet = page.getByRole("dialog", { - name: "Edit Service Record", - }); + // With the Add sheet closed, the edit sheet is the only open dialog, so + // scope interactions to it (locale-independent, no hard-coded title). + const editSheet = page.getByRole("dialog"); await expect(editSheet).toBeVisible({ timeout: 10000 }); const pastYear = new Date().getFullYear() - 1; @@ -185,6 +188,11 @@ test.describe("Device Service History", () => { await expect(page.getByText(notes)).toBeVisible(); + // Wait for the Add sheet to fully close before opening Edit; otherwise its + // controls linger in the DOM during the close animation and collide with + // the edit sheet's controls. + await expect(page.getByRole("button", { name: "Save" })).toBeHidden(); + await page .locator('[data-slot="card"]') .filter({ hasText: notes }) @@ -192,17 +200,17 @@ test.describe("Device Service History", () => { .first() .click(); - // Scope all edit-sheet interactions to its dialog to avoid strict-mode - // violations when the Add sheet is still in the DOM during its close animation. - const editSheet = page.getByRole("dialog", { - name: "Edit Service Record", - }); + // With the Add sheet closed, the edit sheet is the only open dialog, so + // scope interactions to it (locale-independent, no hard-coded title). + const editSheet = page.getByRole("dialog"); await expect(editSheet).toBeVisible({ timeout: 10000 }); const updateButton = editSheet.getByRole("button", { name: "Update" }); await expect(updateButton).toBeDisabled(); - await editSheet.getByRole("textbox", { name: "Notes *" }).fill(updatedNotes); + await editSheet + .getByRole("textbox", { name: "Notes *" }) + .fill(updatedNotes); await expect(updateButton).toBeEnabled(); From 13cd44408dbb786a0035d32314f84320e5d108f5 Mon Sep 17 00:00:00 2001 From: nihal467 Date: Thu, 16 Jul 2026 12:15:17 +0530 Subject: [PATCH 4/8] test: clarify edit-sheet scoping comment Reword to avoid overclaiming full locale independence; the tests still use English accessible names. The point is scoping to the active sheet instead of a hard-coded dialog title. Ref: QA-214 --- tests/facility/settings/devices/deviceServiceHistory.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/facility/settings/devices/deviceServiceHistory.spec.ts b/tests/facility/settings/devices/deviceServiceHistory.spec.ts index 6b631a83517..ef21fe0465b 100644 --- a/tests/facility/settings/devices/deviceServiceHistory.spec.ts +++ b/tests/facility/settings/devices/deviceServiceHistory.spec.ts @@ -85,7 +85,7 @@ test.describe("Device Service History", () => { .click(); // With the Add sheet closed, the edit sheet is the only open dialog, so - // scope interactions to it (locale-independent, no hard-coded title). + // scope interactions to it instead of matching a hard-coded dialog title. const editSheet = page.getByRole("dialog"); await expect(editSheet).toBeVisible({ timeout: 10000 }); @@ -201,7 +201,7 @@ test.describe("Device Service History", () => { .click(); // With the Add sheet closed, the edit sheet is the only open dialog, so - // scope interactions to it (locale-independent, no hard-coded title). + // scope interactions to it instead of matching a hard-coded dialog title. const editSheet = page.getByRole("dialog"); await expect(editSheet).toBeVisible({ timeout: 10000 }); From 3b98c948a7a27640dce2a27169dc94a65293dd1b Mon Sep 17 00:00:00 2001 From: nihal467 Date: Thu, 16 Jul 2026 13:21:50 +0530 Subject: [PATCH 5/8] test: prevent prescription highlight flake via distinct medicines The highlighted (non-unit) and unit prescription tests run serially against the same encounter and picked a medicine at random from a 5-item list. A 1-in-5 collision made the unit test match the other test's highlighted row, failing toHaveCount(0) intermittently. Allocate two distinct medicines so their rows never overlap. --- .../encounter/medicine/prescriptionCreate.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/facility/patient/encounter/medicine/prescriptionCreate.spec.ts b/tests/facility/patient/encounter/medicine/prescriptionCreate.spec.ts index 5f361a41d0b..3da51d74186 100644 --- a/tests/facility/patient/encounter/medicine/prescriptionCreate.spec.ts +++ b/tests/facility/patient/encounter/medicine/prescriptionCreate.spec.ts @@ -14,6 +14,16 @@ test.describe("Create Patient Prescription", () => { test.describe.configure({ mode: "serial" }); let facilityId: string; + // These tests run serially against the same encounter, so every prescription + // they create shows up in one shared table. Pick two distinct medicines so + // the highlighted (non-unit) test and the unit test never land on rows that + // match the same medicine name — otherwise the unit test could match the + // other test's highlighted row and fail intermittently. + const [highlightedMedicine, unitMedicine] = faker.helpers.arrayElements( + medicineNames, + 2, + ); + test.beforeEach(async ({ page }) => { facilityId = getFacilityId(); const createdDateAfter = format(subDays(new Date(), 90), "yyyy-MM-dd"); @@ -26,7 +36,7 @@ test.describe("Create Patient Prescription", () => { }); test("Add medication to patient prescription", async ({ page }) => { - const medicineName = faker.helpers.arrayElement(medicineNames); + const medicineName = highlightedMedicine; // Use a non-unit dose (value !== 1) so the dosage is highlighted const dosage = faker.number.int({ min: 2, max: 100 }).toString(); const frequency = faker.helpers.arrayElement(frequencies); @@ -119,7 +129,7 @@ test.describe("Create Patient Prescription", () => { test("Unit dosage is not highlighted in patient prescription", async ({ page, }) => { - const medicineName = faker.helpers.arrayElement(medicineNames); + const medicineName = unitMedicine; // Use a unit dose (value === 1) so the dosage is NOT highlighted const dosage = "1"; const frequency = faker.helpers.arrayElement(frequencies); From 07a73d66e6c9aad596cbae68ca7fc372c41030e3 Mon Sep 17 00:00:00 2001 From: nihal467 Date: Thu, 16 Jul 2026 13:46:06 +0530 Subject: [PATCH 6/8] test: harden cross-user file access test against shared-patient state The test picked the first "View" button on a patient whose file list is shared across tests. That could land on an archived file, whose viewer is a different dialog with two "Close" buttons (strict-mode violation), and the nurse-side "Close" locator was unscoped. Target the specific uploaded file's row by a unique name, assert the File Preview dialog opens, and scope the close button to that dialog. --- .../patientDetails/files/patientFiles.spec.ts | 91 ++++++++++--------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/tests/facility/patient/patientDetails/files/patientFiles.spec.ts b/tests/facility/patient/patientDetails/files/patientFiles.spec.ts index 8d5f06bae89..d8168c7d803 100644 --- a/tests/facility/patient/patientDetails/files/patientFiles.spec.ts +++ b/tests/facility/patient/patientDetails/files/patientFiles.spec.ts @@ -56,6 +56,33 @@ test.describe("Patient Files", () => { return response; }; + // Opens the preview for a specific uploaded file and closes it again, + // confirming the file is accessible to the current user. + const openFilePreview = async (page: Page, displayName: string) => { + // Scope to the row for this exact file so other files on the shared + // patient (including archived ones, whose "View" opens a different + // dialog) can't be picked up by mistake. + const fileRow = page + .getByRole("row") + .filter({ hasText: displayName }) + .first(); + await expect(fileRow).toBeVisible({ timeout: 10000 }); + await fileRow.getByRole("button", { name: "View", exact: true }).click(); + + // The file preview dialog (not the archived-file dialog) must open. + const previewDialog = page.getByRole("dialog"); + await expect(previewDialog.getByText("File Preview")).toBeVisible({ + timeout: 10000, + }); + + // Close it, scoping to the dialog so page-level toasts named "Close" + // can't cause a strict-mode match. + await previewDialog + .getByRole("button", { name: "Close", exact: true }) + .click(); + await expect(previewDialog).toBeHidden({ timeout: 10000 }); + }; + let facilityId: string; test.beforeEach(async ({ page }) => { @@ -166,55 +193,35 @@ test.describe("Patient Files", () => { page, browser, }) => { - const inputFileName1 = faker.system.fileName(); - - // Upload file as first user (doctor) - await uploadFile(page, `tests/fixtures/images/${fileName}`, inputFileName1); - - // Wait for the file to appear in the list - await expect( - page.getByRole("button", { name: /view/i }).first(), - ).toBeVisible({ timeout: 10000 }); - - // View the uploaded file - await page.getByRole("button", { name: /view/i }).first().click(); + // Unique, extension-free display name so this exact file's row can be + // located reliably among other files on the shared patient. + const uploadedFileName = `access-${faker.string.alphanumeric(10)}`; + + // Upload the file as the first user (doctor). + await uploadFile( + page, + `tests/fixtures/images/${fileName}`, + uploadedFileName, + ); - // Wait for file viewer to load - await expect( - page.getByRole("button", { name: "Close", exact: true }), - ).toBeVisible({ - timeout: 5000, - }); - await page.getByRole("button", { name: "Close", exact: true }).click(); + // Capture the stable Files-tab URL to reopen as a different user. + const filesUrl = page.url(); - // Save current URL for navigation - const currentUrl = page.url(); + // The uploader can preview the file they just added. + await openFilePreview(page, uploadedFileName); - // Create a new browser context with nurse authentication + // The same file must be accessible to a different user (nurse). const nurseContext = await browser.newContext({ storageState: "tests/.auth/nurse.json", }); const nursePage = await nurseContext.newPage(); - - // Navigate to the patient files page as nurse - await nursePage.goto(currentUrl); - - // Wait for the files tab to load - await expect( - nursePage.getByRole("button", { name: /view/i }).first(), - ).toBeVisible({ timeout: 10000 }); - - // View the file as nurse - await nursePage.getByRole("button", { name: /view/i }).first().click(); - - // Verify file viewer loaded for nurse - await expect(nursePage.getByRole("button", { name: "Close" })).toBeVisible({ - timeout: 5000, - }); - - // Clean up - await nursePage.close(); - await nurseContext.close(); + try { + await nursePage.goto(filesUrl); + await openFilePreview(nursePage, uploadedFileName); + } finally { + await nursePage.close(); + await nurseContext.close(); + } }); test("Add a new patient file and rename it", async ({ page }) => { From 3f14a6c72c7a34470784e50e352f6f5040574b1e Mon Sep 17 00:00:00 2001 From: nihal467 Date: Thu, 16 Jul 2026 18:46:10 +0530 Subject: [PATCH 7/8] test: narrow dialog locators to avoid strict-mode multi-match Address Copilot review feedback on PR #16572: getByRole("dialog") can match multiple dialogs while a previous sheet is still unmounting. - deviceServiceHistory: target the edit sheet via .last() - patientFiles: filter the preview dialog by its "File Preview" title --- .../patientDetails/files/patientFiles.spec.ts | 10 +++++++--- .../settings/devices/deviceServiceHistory.spec.ts | 14 ++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/facility/patient/patientDetails/files/patientFiles.spec.ts b/tests/facility/patient/patientDetails/files/patientFiles.spec.ts index d8168c7d803..8b0bb286911 100644 --- a/tests/facility/patient/patientDetails/files/patientFiles.spec.ts +++ b/tests/facility/patient/patientDetails/files/patientFiles.spec.ts @@ -69,9 +69,13 @@ test.describe("Patient Files", () => { await expect(fileRow).toBeVisible({ timeout: 10000 }); await fileRow.getByRole("button", { name: "View", exact: true }).click(); - // The file preview dialog (not the archived-file dialog) must open. - const previewDialog = page.getByRole("dialog"); - await expect(previewDialog.getByText("File Preview")).toBeVisible({ + // The file preview dialog (not the archived-file dialog) must open. Filter + // by its title so an upload sheet or other dialog that may still be mounted + // can't cause a strict-mode match on the Close button. + const previewDialog = page + .getByRole("dialog") + .filter({ hasText: "File Preview" }); + await expect(previewDialog).toBeVisible({ timeout: 10000, }); diff --git a/tests/facility/settings/devices/deviceServiceHistory.spec.ts b/tests/facility/settings/devices/deviceServiceHistory.spec.ts index ef21fe0465b..9a6e894db20 100644 --- a/tests/facility/settings/devices/deviceServiceHistory.spec.ts +++ b/tests/facility/settings/devices/deviceServiceHistory.spec.ts @@ -84,9 +84,10 @@ test.describe("Device Service History", () => { .first() .click(); - // With the Add sheet closed, the edit sheet is the only open dialog, so - // scope interactions to it instead of matching a hard-coded dialog title. - const editSheet = page.getByRole("dialog"); + // With the Add sheet closed, the edit sheet is the only open dialog. Use + // .last() to target the most recently opened dialog so a still-unmounting + // sheet can't reintroduce a strict-mode match. + const editSheet = page.getByRole("dialog").last(); await expect(editSheet).toBeVisible({ timeout: 10000 }); const pastYear = new Date().getFullYear() - 1; @@ -200,9 +201,10 @@ test.describe("Device Service History", () => { .first() .click(); - // With the Add sheet closed, the edit sheet is the only open dialog, so - // scope interactions to it instead of matching a hard-coded dialog title. - const editSheet = page.getByRole("dialog"); + // With the Add sheet closed, the edit sheet is the only open dialog. Use + // .last() to target the most recently opened dialog so a still-unmounting + // sheet can't reintroduce a strict-mode match. + const editSheet = page.getByRole("dialog").last(); await expect(editSheet).toBeVisible({ timeout: 10000 }); const updateButton = editSheet.getByRole("button", { name: "Update" }); From 221c36dc4382a6009a7e83110f107bc4e7b44cb2 Mon Sep 17 00:00:00 2001 From: nihal467 Date: Thu, 16 Jul 2026 19:24:25 +0530 Subject: [PATCH 8/8] test: drop hardcoded timeouts, rely on global config --- .../patient/patientDetails/files/patientFiles.spec.ts | 8 +++----- .../settings/devices/deviceServiceHistory.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/facility/patient/patientDetails/files/patientFiles.spec.ts b/tests/facility/patient/patientDetails/files/patientFiles.spec.ts index 8b0bb286911..89aced76b57 100644 --- a/tests/facility/patient/patientDetails/files/patientFiles.spec.ts +++ b/tests/facility/patient/patientDetails/files/patientFiles.spec.ts @@ -66,7 +66,7 @@ test.describe("Patient Files", () => { .getByRole("row") .filter({ hasText: displayName }) .first(); - await expect(fileRow).toBeVisible({ timeout: 10000 }); + await expect(fileRow).toBeVisible(); await fileRow.getByRole("button", { name: "View", exact: true }).click(); // The file preview dialog (not the archived-file dialog) must open. Filter @@ -75,16 +75,14 @@ test.describe("Patient Files", () => { const previewDialog = page .getByRole("dialog") .filter({ hasText: "File Preview" }); - await expect(previewDialog).toBeVisible({ - timeout: 10000, - }); + await expect(previewDialog).toBeVisible(); // Close it, scoping to the dialog so page-level toasts named "Close" // can't cause a strict-mode match. await previewDialog .getByRole("button", { name: "Close", exact: true }) .click(); - await expect(previewDialog).toBeHidden({ timeout: 10000 }); + await expect(previewDialog).toBeHidden(); }; let facilityId: string; diff --git a/tests/facility/settings/devices/deviceServiceHistory.spec.ts b/tests/facility/settings/devices/deviceServiceHistory.spec.ts index 9a6e894db20..ddf5b745eb1 100644 --- a/tests/facility/settings/devices/deviceServiceHistory.spec.ts +++ b/tests/facility/settings/devices/deviceServiceHistory.spec.ts @@ -88,7 +88,7 @@ test.describe("Device Service History", () => { // .last() to target the most recently opened dialog so a still-unmounting // sheet can't reintroduce a strict-mode match. const editSheet = page.getByRole("dialog").last(); - await expect(editSheet).toBeVisible({ timeout: 10000 }); + await expect(editSheet).toBeVisible(); const pastYear = new Date().getFullYear() - 1; await editSheet @@ -205,7 +205,7 @@ test.describe("Device Service History", () => { // .last() to target the most recently opened dialog so a still-unmounting // sheet can't reintroduce a strict-mode match. const editSheet = page.getByRole("dialog").last(); - await expect(editSheet).toBeVisible({ timeout: 10000 }); + await expect(editSheet).toBeVisible(); const updateButton = editSheet.getByRole("button", { name: "Update" }); await expect(updateButton).toBeDisabled();