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); diff --git a/tests/facility/patient/patientDetails/files/patientFiles.spec.ts b/tests/facility/patient/patientDetails/files/patientFiles.spec.ts index 8d5f06bae89..89aced76b57 100644 --- a/tests/facility/patient/patientDetails/files/patientFiles.spec.ts +++ b/tests/facility/patient/patientDetails/files/patientFiles.spec.ts @@ -56,6 +56,35 @@ 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(); + await fileRow.getByRole("button", { name: "View", exact: true }).click(); + + // 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(); + + // 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(); + }; + let facilityId: string; test.beforeEach(async ({ page }) => { @@ -166,55 +195,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 }) => { diff --git a/tests/facility/settings/devices/deviceServiceHistory.spec.ts b/tests/facility/settings/devices/deviceServiceHistory.spec.ts index 82f4ce8822a..ddf5b745eb1 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,8 +84,14 @@ test.describe("Device Service History", () => { .first() .click(); + // 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(); + const pastYear = new Date().getFullYear() - 1; - await page + await editSheet .locator('[data-slot="form-item"]') .filter({ hasText: "Service Date" }) .locator('[data-slot="popover-trigger"]') @@ -88,9 +99,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"]') @@ -176,6 +189,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 }) @@ -183,14 +201,22 @@ test.describe("Device Service History", () => { .first() .click(); - const updateButton = page.getByRole("button", { name: "Update" }); + // 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(); + + 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(); });