Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions e2e/commands/location-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import { type APIRequestContext, expect } from '@playwright/test';
* Mostly taken from openmrs-esm-patient-management
*/

/**
* Sets the session location.
*
* Note this also covers the browser, not just the `api` fixture: the fixture's request context
* inherits `use.storageState` from the Playwright config, so it loads the same JSESSIONID as the
* browser context and the two share one server-side session. That matters because pihapps'
* RequireLoginLocationFilter is mapped to every URL and redirects to loginLocation.page unless the
* requesting session has a location -- so calling this is what lets `page.goto` reach a legacy
* .page URL at all.
*/
export const changeLocation = async (api: APIRequestContext, locationUuid: string) => {
const locationRes = await api.post('session', {
data: {
Expand Down
4 changes: 2 additions & 2 deletions e2e/commands/patient-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function getRandomBirthday(profile: PatientProfile): string {
export const generateRandomPatient = async (
api: APIRequestContext,
profile: PatientProfile,
locationUuid?: string,
locationUuid: string,
): Promise<Patient> => {
const identifierRes = await api.post(`idgen/identifiersource/${KGHEmrIdSourceUuid}/identifier`, {
data: {},
Expand All @@ -89,7 +89,7 @@ export const generateRandomPatient = async (
{
identifier,
identifierType: KGHEmrIdTypeUuid,
location: locationUuid || process.env.E2E_LOGIN_DEFAULT_LOCATION_UUID,
location: locationUuid,
preferred: true,
},
],
Expand Down
8 changes: 4 additions & 4 deletions e2e/commands/visit-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { KGHVisitType } from '../core';
* Mostly taken from openmrs-esm-patient-management
*/

export const startVisit = async (api: APIRequestContext, patientId: string, locationUuid?: string): Promise<Visit> => {
export const startVisit = async (api: APIRequestContext, patientId: string, locationUuid: string): Promise<Visit> => {
const visitRes = await api.post('visit', {
data: {
startDatetime: dayjs().format('YYYY-MM-DDTHH:mm:ss.SSSZZ'),
patient: patientId,
location: locationUuid || process.env.E2E_LOGIN_DEFAULT_LOCATION_UUID,
location: locationUuid,
visitType: KGHVisitType,
attributes: [],
},
Expand All @@ -22,10 +22,10 @@ export const startVisit = async (api: APIRequestContext, patientId: string, loca
return await visitRes.json();
};

export const endVisit = async (api: APIRequestContext, uuid: string, isWardTest = false) => {
export const endVisit = async (api: APIRequestContext, uuid: string, locationUuid: string) => {
await api.post(`visit/${uuid}`, {
data: {
location: isWardTest ? process.env.E2E_WARD_LOCATION_UUID : process.env.E2E_LOGIN_DEFAULT_LOCATION_UUID,
location: locationUuid,
visitType: KGHVisitType,
stopDatetime: dayjs().format('YYYY-MM-DDTHH:mm:ss.SSSZZ'),
},
Expand Down
1 change: 1 addition & 0 deletions e2e/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const KGHLocationsUuids = {
'Antenatal Ward': '11f5c9f9-40b8-46ad-9e7e-59473ce43246',
KGH: '074b2ab0-716a-11eb-8aa6-0242ac110002',
'Maternity Pharmacy': '84b9b680-786c-4388-9e7c-805614c13b5a',
Laboratory: '0d3704ab-17f7-4272-aa1b-9118ae51acce',
};

export const KGHEmrIdSourceUuid = '809b23e3-7162-11eb-8aa6-0242ac110002';
Expand Down
1 change: 0 additions & 1 deletion e2e/core/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ async function globalSetup() {
);
const res = await requestContext.post(`${process.env.E2E_BASE_URL}/ws/rest/v1/session`, {
data: {
sessionLocation: process.env.E2E_LOGIN_DEFAULT_LOCATION_UUID,
locale: 'en',
},
headers: {
Expand Down
8 changes: 4 additions & 4 deletions e2e/core/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const test = base.extend<CustomTestFixtures, CustomWorkerFixtures>({
api: [api, { scope: 'worker' }],
adultWoman: [
async ({ api }, use) => {
const patient = await generateRandomPatient(api, 'adultWomen');
const patient = await generateRandomPatient(api, 'adultWomen', KGHLocationsUuids.KGH);
await use(patient);
await deletePatient(api, patient.uuid);
},
Expand All @@ -41,7 +41,7 @@ export const test = base.extend<CustomTestFixtures, CustomWorkerFixtures>({
async ({ api, adultWoman }, use) => {
const visit = await startVisit(api, adultWoman.uuid, KGHLocationsUuids.KGH);
await use(visit);
await endVisit(api, visit.uuid);
await endVisit(api, visit.uuid, KGHLocationsUuids.KGH);
},
{ scope: 'test' },
],
Expand All @@ -58,7 +58,7 @@ export const test = base.extend<CustomTestFixtures, CustomWorkerFixtures>({
async ({ api, newborn }, use) => {
const visit = await startVisit(api, newborn.uuid, KGHLocationsUuids.KGH);
await use(visit);
await endVisit(api, visit.uuid);
await endVisit(api, visit.uuid, KGHLocationsUuids.KGH);
},
{ scope: 'test' },
],
Expand All @@ -75,7 +75,7 @@ export const test = base.extend<CustomTestFixtures, CustomWorkerFixtures>({
async ({ api, newborn2 }, use) => {
const visit = await startVisit(api, newborn2.uuid, KGHLocationsUuids.KGH);
await use(visit);
await endVisit(api, visit.uuid);
await endVisit(api, visit.uuid, KGHLocationsUuids.KGH);
},
{ scope: 'test' },
],
Expand Down
105 changes: 105 additions & 0 deletions e2e/pages/o2/labs/add-lab-orders-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { expect, type Page } from '@playwright/test';
import { type Patient } from '@openmrs/esm-framework';
import { step, test } from '../../../core';

/**
* The "Add Lab orders" page for a single patient (pihapps labOrder.page).
*
* Every test category is rendered into the DOM at once and only the active one is shown, so the
* lab test buttons are looked up inside the visible category pane. The first category
* (Haematology) activates itself on load.
*/
export class O2AddLabOrdersPage {
private constructor(readonly page: Page) {}

readonly saveButton = () => this.page.locator('#draft-save-button');
readonly discardAllButton = () => this.page.locator('#draft-discard-all');
readonly returnButton = () => this.page.locator('#cancel-button');
readonly draftOrderCount = () => this.page.locator('#num-draft-orders');
readonly draftOrders = () => this.page.locator('#draft-list-container li.draft-list');

categoryTab(categoryName: string) {
return this.page.locator('a.category-link').filter({ hasText: categoryName });
}

/**
* Panel buttons wrap a hover tooltip listing their member tests, so the button's text is more
* than just its label -- hasText (substring) handles that, where an exact-name role lookup
* would not. Scoped to the visible pane so that a name reused in another category can't match.
*/
labTestButton(labTestName: string) {
return this.page.locator('.lab-selection-form:visible button.lab-tests-btn').filter({ hasText: labTestName });
}

draftOrder(labTestName: string) {
return this.draftOrders().filter({ hasText: labTestName });
}

@step
static async open(page: Page, patient: Patient, returnUrl?: string) {
return test.step(`When I navigate to the add lab orders page for patient ${patient.uuid}`, async () => {
const addLabOrdersPage = new O2AddLabOrdersPage(page);
const returnUrlParam = returnUrl ? `&returnUrl=${encodeURIComponent(returnUrl)}` : '';
await page.goto(
`${process.env.E2E_BASE_URL}/pihapps/labs/labOrder.page?patient=${patient.uuid}${returnUrlParam}`,
);
await expect(addLabOrdersPage.saveButton()).toBeVisible();
return addLabOrdersPage;
});
}

/** For when we already navigated here (e.g. by selecting a patient from the search page). */
@step
static async at(page: Page) {
return test.step('When I land on the add lab orders page', async () => {
const addLabOrdersPage = new O2AddLabOrdersPage(page);
await expect(addLabOrdersPage.saveButton()).toBeVisible();
return addLabOrdersPage;
});
}

@step
async selectCategory(categoryName: string) {
return test.step(`When I select the "${categoryName}" lab category`, async () => {
await this.categoryTab(categoryName).click();
await expect(this.categoryTab(categoryName)).toHaveClass(/active-category/);
});
}

@step
async selectLabTest(labTestName: string) {
return test.step(`When I select the "${labTestName}" lab test`, async () => {
await this.labTestButton(labTestName).click();
await expect(this.draftOrder(labTestName)).toBeVisible();
});
}

/**
* Saves the drafted orders. On success the page immediately redirects to its returnUrl, which
* tears down the "Order Successfully Created" toast before it can reliably be asserted on -- so
* leaving this page is what we wait for instead.
*/
@step
async save() {
return test.step('When I save the lab orders', async () => {
await this.saveButton().click();
await this.page.waitForURL((url) => !url.pathname.endsWith('labOrder.page'));
});
}

@step
async expectDraftOrderCount(count: number) {
return test.step(`Then I should see ${count} draft order(s)`, async () => {
await expect(this.draftOrderCount()).toHaveText(String(count));
await expect(this.draftOrders()).toHaveCount(count);
});
}

@step
async expectOrdersSaved() {
return test.step('Then the orders should have been saved', async () => {
await expect(this.page).not.toHaveURL(/labOrder\.page/);
await expect(this.saveButton()).toHaveCount(0);
});
}
}
51 changes: 51 additions & 0 deletions e2e/pages/o2/labs/add-lab-orders-search-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { expect, type Page } from '@playwright/test';
import { type Patient } from '@openmrs/esm-framework';
import { getPatientIdentifierStr } from '../../../commands';
import { step, test } from '../../../core';
import { O2AddLabOrdersPage } from './add-lab-orders-page';

/**
* The find-patient screen that fronts lab ordering
* (coreapps findPatient.page for the pih.app.labs.ordering app).
*/
export class O2AddLabOrdersSearchPage {
private constructor(readonly page: Page) {}

readonly searchByIdOrNameField = () => this.page.getByPlaceholder('Search by ID or Name');
readonly searchResultsTable = () => this.page.locator('#patient-search-results-table');

searchResultRow(patientIdentifier: string) {
return this.searchResultsTable().locator('tbody tr').filter({ hasText: patientIdentifier });
}

@step
static async open(page: Page) {
return test.step('When I navigate to the add lab orders patient search page', async () => {
const searchPage = new O2AddLabOrdersSearchPage(page);
await page.goto(`${process.env.E2E_BASE_URL}/coreapps/findpatient/findPatient.page?app=pih.app.labs.ordering`);
await expect(searchPage.searchByIdOrNameField()).toBeVisible();
return searchPage;
});
}

/**
* Searches by EMR ID and lands on the add lab orders page for that patient.
*
* Typing alone issues no request -- the widget searches on Enter. And on an exact identifier
* match it does not populate the results table at all: it looks the patient up
* (GET /ws/rest/v1/patient?identifier=...) and navigates straight to the app's afterSelectedUrl.
* So there is no row to click in this flow; `searchResultRow` is there for a name search, which
* does render rows.
*/
@step
async searchForPatientByIdentifier(patient: Patient) {
const patientIdentifier = getPatientIdentifierStr(patient);
await test.step(`When I search for patient "${patientIdentifier}" by EMR ID`, async () => {
await this.searchByIdOrNameField().click();
await this.searchByIdOrNameField().pressSequentially(patientIdentifier);
await this.searchByIdOrNameField().press('Enter');
await expect(this.page).toHaveURL(/labOrder\.page/);
});
return O2AddLabOrdersPage.at(this.page);
}
}
5 changes: 5 additions & 0 deletions e2e/pages/o2/labs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './add-lab-orders-page';
export * from './add-lab-orders-search-page';
export * from './lab-orders-page';
export * from './patient-lab-orders-page';
export * from './patient-lab-results-page';
Loading
Loading