@openmrs/esm-dispensing-app 1.7.1-pre.181 → 1.7.1-pre.182
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/cache/a3e5e64281a958be-meta.json +1 -0
- package/.turbo/cache/{27edf80628bc1fab.tar.zst → a3e5e64281a958be.tar.zst} +0 -0
- package/.turbo/turbo-build.log +1 -1
- package/dist/routes.json +1 -1
- package/e2e/README.md +119 -0
- package/e2e/commands/index.ts +2 -0
- package/e2e/commands/patient-operations.ts +109 -0
- package/e2e/commands/visit-operations.ts +38 -0
- package/e2e/core/global-setup.ts +32 -0
- package/e2e/core/index.ts +1 -0
- package/e2e/core/test.ts +35 -0
- package/e2e/fixtures/api.ts +27 -0
- package/e2e/fixtures/index.ts +1 -0
- package/e2e/pages/dispensing-page.ts +9 -0
- package/e2e/pages/index.ts +1 -0
- package/e2e/specs/active-prescriptions.spec.ts +15 -0
- package/e2e/support/github/Dockerfile +34 -0
- package/e2e/support/github/docker-compose.yml +24 -0
- package/e2e/support/github/run-e2e-docker-env.sh +48 -0
- package/e2e/types/index.ts +157 -0
- package/example.env +7 -0
- package/jest.config.js +8 -0
- package/package.json +5 -2
- package/playwright.config.ts +36 -0
- package/.turbo/cache/27edf80628bc1fab-meta.json +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hash":"a3e5e64281a958be","duration":42060}
|
|
Binary file
|
package/.turbo/turbo-build.log
CHANGED
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.2.0"},"pages":[{"component":"dispensing","route":"dispensing","online":true,"offline":true}],"extensions":[{"name":"dispensing-link","slot":"app-menu-slot","component":"dispensingLink","online":true,"offline":true},{"name":"patient-diagnoses","component":"patientDiagnoses","slot":"dispensing-condition-and-diagnoses","online":true,"offline":true},{"name":"patient-conditions","component":"patientConditions","slot":"dispensing-condition-and-diagnoses","online":true,"offline":true},{"name":"dispensing-dashboard","slot":"dispensing-dashboard-slot","component":"dispensingDashboard","online":true,"offline":true},{"name":"dispensing-dashboard-link","component":"dispensingDashboardLink","meta":{"name":"dispensing","slot":"dispensing-dashboard-slot","title":"Dispensing"}},{"name":"dispense-action-button","slot":"prescription-action-button-slot","component":"dispenseActionButton"},{"name":"pause-action-button","slot":"prescription-action-button-slot","component":"pauseActionButton"},{"name":"close-action-button","slot":"prescription-action-button-slot","component":"closeActionButton"}],"workspaces":[{"name":"close-dispense-workspace","component":"closeDispenseWorkspace","type":"dispense","title":"Close prescription"},{"name":"pause-dispense-workspace","component":"pauseDispenseWorkspace","type":"dispense","title":"Pause prescription"},{"name":"dispense-workspace","component":"dispenseWorkspace","type":"dispense","title":"Dispense prescription","width":"wider"}],"modals":[{"name":"prescription-print-preview-modal","component":"printPrescriptionPreviewModal"},{"name":"delete-confirm-modal","component":"deleteConfirmModal"}],"version":"1.7.1-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.2.0"},"pages":[{"component":"dispensing","route":"dispensing","online":true,"offline":true}],"extensions":[{"name":"dispensing-link","slot":"app-menu-slot","component":"dispensingLink","online":true,"offline":true},{"name":"patient-diagnoses","component":"patientDiagnoses","slot":"dispensing-condition-and-diagnoses","online":true,"offline":true},{"name":"patient-conditions","component":"patientConditions","slot":"dispensing-condition-and-diagnoses","online":true,"offline":true},{"name":"dispensing-dashboard","slot":"dispensing-dashboard-slot","component":"dispensingDashboard","online":true,"offline":true},{"name":"dispensing-dashboard-link","component":"dispensingDashboardLink","meta":{"name":"dispensing","slot":"dispensing-dashboard-slot","title":"Dispensing"}},{"name":"dispense-action-button","slot":"prescription-action-button-slot","component":"dispenseActionButton"},{"name":"pause-action-button","slot":"prescription-action-button-slot","component":"pauseActionButton"},{"name":"close-action-button","slot":"prescription-action-button-slot","component":"closeActionButton"}],"workspaces":[{"name":"close-dispense-workspace","component":"closeDispenseWorkspace","type":"dispense","title":"Close prescription"},{"name":"pause-dispense-workspace","component":"pauseDispenseWorkspace","type":"dispense","title":"Pause prescription"},{"name":"dispense-workspace","component":"dispenseWorkspace","type":"dispense","title":"Dispense prescription","width":"wider"}],"modals":[{"name":"prescription-print-preview-modal","component":"printPrescriptionPreviewModal"},{"name":"delete-confirm-modal","component":"deleteConfirmModal"}],"version":"1.7.1-pre.182"}
|
package/e2e/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# E2E Tests
|
|
2
|
+
|
|
3
|
+
This directory contains an E2E test suite using the [Playwright](https://playwright.dev)
|
|
4
|
+
framework.
|
|
5
|
+
|
|
6
|
+
## Getting Started
|
|
7
|
+
|
|
8
|
+
Please ensure that you have followed the basic installation guide in the [root README](../README.md). Once everything is set up, make sure the dev server is running by using:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
yarn start
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then, in a separate terminal, run:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
yarn test-e2e --headed
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
By default, the test suite will run against the http://localhost:8080. You can override this by exporting `E2E_BASE_URL` environment variables beforehand:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
# Ex: Set the server URL to dev3:
|
|
24
|
+
export E2E_BASE_URL=https://dev3.openmrs.org/openmrs
|
|
25
|
+
|
|
26
|
+
# Run all e2e tests:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
yarn test-e2e --headed
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
To run a specific test by title:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
yarn test-e2e --headed -g "title of the test"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Read the [e2e testing guide](https://o3-docs.openmrs.org/docs/frontend-modules/end-to-end-testing) to learn more about End-to-End tests in this project.
|
|
39
|
+
|
|
40
|
+
### Updating Playwright
|
|
41
|
+
|
|
42
|
+
The Playwright version in the [Bamboo e2e Dockerfile](e2e/support/bamboo/playwright.Dockerfile#L2) and the `package.json` file must match. If you update the Playwright version in one place, you must update it in the other.
|
|
43
|
+
|
|
44
|
+
## Troubleshooting
|
|
45
|
+
|
|
46
|
+
If you notice that your local version of the application is not working or that there's a mismatch between what you see locally versus what's in [dev3](https://dev3.openmrs.org/openmrs/spa), you likely have outdated versions of core libraries. To update core libraries, run the following commands:
|
|
47
|
+
|
|
48
|
+
Check [this documentation](https://playwright.dev/docs/running-tests#command-line) for more running options.
|
|
49
|
+
|
|
50
|
+
It is also highly recommended to install the companion VS Code extension:
|
|
51
|
+
https://playwright.dev/docs/getting-started-vscode
|
|
52
|
+
|
|
53
|
+
## Writing New Tests
|
|
54
|
+
|
|
55
|
+
In general, it is recommended to read through the official [Playwright docs](https://playwright.dev/docs/intro)
|
|
56
|
+
before writing new test cases. The project uses the official Playwright test runner and,
|
|
57
|
+
generally, follows a very simple project structure:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
e2e
|
|
61
|
+
|__ commands
|
|
62
|
+
| ^ Contains "commands" (simple reusable functions) that can be used in test cases/specs,
|
|
63
|
+
| e.g. generate a random patient.
|
|
64
|
+
|__ core
|
|
65
|
+
| ^ Contains code related to the test runner itself, e.g. setting up the custom fixtures.
|
|
66
|
+
| You probably need to touch this infrequently.
|
|
67
|
+
|__ fixtures
|
|
68
|
+
| ^ Contains fixtures (https://playwright.dev/docs/test-fixtures) which are used
|
|
69
|
+
| to run reusable setup/teardown tasks
|
|
70
|
+
|__ pages
|
|
71
|
+
| ^ Contains page object model classes for interacting with the frontend.
|
|
72
|
+
| See https://playwright.dev/docs/test-pom for details.
|
|
73
|
+
|__ specs
|
|
74
|
+
| ^ Contains the actual test cases/specs. New tests should be placed in this folder.
|
|
75
|
+
|__ support
|
|
76
|
+
^ Contains support files that requires to run e2e tests, e.g. docker compose files.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
When you want to write a new test case, start by creating a new spec in `./specs`.
|
|
80
|
+
Depending on what you want to achieve, you might want to create new fixtures and/or
|
|
81
|
+
page object models. To see examples, have a look at the existing code to see how these different concepts play together.
|
|
82
|
+
|
|
83
|
+
## Open reports from GitHub Actions / Bamboo
|
|
84
|
+
To download the report from the GitHub action/Bamboo plan, follow these steps:
|
|
85
|
+
1. Go to the artifact section of the action/plan and locate the report file.
|
|
86
|
+
2. Download the report file and unzip it using a tool of your choice.
|
|
87
|
+
3. Open the index.html file in a web browser to view the report.
|
|
88
|
+
The report will show you a full summary of your tests, including information on which
|
|
89
|
+
tests passed, failed, were skipped, or were flaky. You can filter the report by browser
|
|
90
|
+
and explore the details of individual tests, including any errors or failures, video
|
|
91
|
+
recordings, and the steps involved in each test. Simply click on a test to view its details.
|
|
92
|
+
|
|
93
|
+
## Debugging Tests
|
|
94
|
+
Refer to [this documentation](https://playwright.dev/docs/debug) on how to debug a test.
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
This is very much underdeveloped/WIP. At the moment, there exists a (git-shared) `.env`
|
|
98
|
+
file which can be used for configuring certain test attributes. This is most likely
|
|
99
|
+
about to change in the future. Stay tuned for updates!
|
|
100
|
+
|
|
101
|
+
## Github Actions integration
|
|
102
|
+
The e2e.yml workflow is made up of two jobs: one for running on pull requests (PRs) and
|
|
103
|
+
one for running on commits.
|
|
104
|
+
1. When running on PRs, the workflow will start the dev server, use dev3.openmrs.org as the backend,
|
|
105
|
+
and run tests only on chromium. This is done in order to quickly provide feedback to the developer.
|
|
106
|
+
The tests are designed to generate their own data and clean up after themselves once they are finished.
|
|
107
|
+
This ensures that the tests will have minimum effect from changes made to dev3 by other developers.
|
|
108
|
+
In the future, we plan to use a docker container to run the tests in an isolated environment once we
|
|
109
|
+
figure out a way to spin up the container within a small amount of time.
|
|
110
|
+
|
|
111
|
+
2. When running on commits, the workflow will spin up a docker container and run the dev server against
|
|
112
|
+
it in order to provide a known and isolated environment. In addition, tests will be run on multiple
|
|
113
|
+
browsers (chromium, firefox, and WebKit) to ensure compatibility.
|
|
114
|
+
|
|
115
|
+
## Troubleshooting tips
|
|
116
|
+
On MacOS, you might run into the following error:
|
|
117
|
+
```browserType.launch: Executable doesn't exist at /Users/<user>/Library/Caches/ms-playwright/chromium-1015/chrome-mac/Chromium.app/Contents/MacOS/Chromium```
|
|
118
|
+
In order to fix this, you can attempt to force the browser reinstallation by running:
|
|
119
|
+
```PLAYWRIGHT_BROWSERS_PATH=/Users/$USER/Library/Caches/ms-playwright npx playwright install```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type APIRequestContext, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export interface Patient {
|
|
4
|
+
uuid: string;
|
|
5
|
+
identifiers: Identifier[];
|
|
6
|
+
display: string;
|
|
7
|
+
person: {
|
|
8
|
+
uuid: string;
|
|
9
|
+
display: string;
|
|
10
|
+
gender: string;
|
|
11
|
+
age: number;
|
|
12
|
+
birthdate: string;
|
|
13
|
+
birthdateEstimated: boolean;
|
|
14
|
+
dead: boolean;
|
|
15
|
+
deathDate?: string;
|
|
16
|
+
causeOfDeath?: string;
|
|
17
|
+
preferredAddress: {
|
|
18
|
+
address1: string;
|
|
19
|
+
cityVillage: string;
|
|
20
|
+
country: string;
|
|
21
|
+
postalCode: string;
|
|
22
|
+
stateProvince: string;
|
|
23
|
+
countyDistrict: string;
|
|
24
|
+
};
|
|
25
|
+
attributes: Array<Record<string, unknown>>;
|
|
26
|
+
voided: boolean;
|
|
27
|
+
birthtime?: string;
|
|
28
|
+
deathdateEstimated: boolean;
|
|
29
|
+
resourceVersion: string;
|
|
30
|
+
};
|
|
31
|
+
attributes: { value: string; attributeType: { uuid: string; display: string } }[];
|
|
32
|
+
voided: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Address {
|
|
36
|
+
preferred: boolean;
|
|
37
|
+
address1: string;
|
|
38
|
+
cityVillage: string;
|
|
39
|
+
country: string;
|
|
40
|
+
postalCode: string;
|
|
41
|
+
stateProvince: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Identifier {
|
|
45
|
+
uuid: string;
|
|
46
|
+
display: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const generateRandomPatient = async (api: APIRequestContext): Promise<Patient> => {
|
|
50
|
+
const identifierRes = await api.post('idgen/identifiersource/8549f706-7e85-4c1d-9424-217d50a2988b/identifier', {
|
|
51
|
+
data: {},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
55
|
+
await expect(identifierRes.ok()).toBeTruthy();
|
|
56
|
+
const { identifier } = await identifierRes.json();
|
|
57
|
+
|
|
58
|
+
const patientRes = await api.post('patient', {
|
|
59
|
+
// TODO: This is not configurable right now. It probably should be.
|
|
60
|
+
data: {
|
|
61
|
+
identifiers: [
|
|
62
|
+
{
|
|
63
|
+
identifier,
|
|
64
|
+
identifierType: '05a29f94-c0ed-11e2-94be-8c13b969e334',
|
|
65
|
+
location: '44c3efb0-2583-4c80-a79e-1f756a03c0a1',
|
|
66
|
+
preferred: true,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
person: {
|
|
70
|
+
addresses: [
|
|
71
|
+
{
|
|
72
|
+
address1: 'Bom Jesus Street',
|
|
73
|
+
address2: '',
|
|
74
|
+
cityVillage: 'Recife',
|
|
75
|
+
country: 'Brazil',
|
|
76
|
+
postalCode: '50030-310',
|
|
77
|
+
stateProvince: 'Pernambuco',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
attributes: [],
|
|
81
|
+
birthdate: '2020-2-1',
|
|
82
|
+
birthdateEstimated: true,
|
|
83
|
+
dead: false,
|
|
84
|
+
gender: 'M',
|
|
85
|
+
names: [
|
|
86
|
+
{
|
|
87
|
+
familyName: `Smith${Math.floor(Math.random() * 10000)}`,
|
|
88
|
+
givenName: `John${Math.floor(Math.random() * 10000)}`,
|
|
89
|
+
middleName: '',
|
|
90
|
+
preferred: true,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
98
|
+
await expect(patientRes.ok()).toBeTruthy();
|
|
99
|
+
return await patientRes.json();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const getPatient = async (api: APIRequestContext, uuid: string): Promise<Patient> => {
|
|
103
|
+
const patientRes = await api.get(`patient/${uuid}?v=full`);
|
|
104
|
+
return await patientRes.json();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const deletePatient = async (api: APIRequestContext, uuid: string) => {
|
|
108
|
+
await api.delete(`patient/${uuid}`, { data: {} });
|
|
109
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type APIRequestContext, expect } from '@playwright/test';
|
|
2
|
+
import { type Visit } from '@openmrs/esm-framework';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
|
|
5
|
+
export const visitStartDatetime = dayjs().subtract(1, 'D');
|
|
6
|
+
|
|
7
|
+
export const startVisit = async (api: APIRequestContext, patientId: string): Promise<Visit> => {
|
|
8
|
+
const visitRes = await api.post('visit', {
|
|
9
|
+
data: {
|
|
10
|
+
startDatetime: visitStartDatetime.format('YYYY-MM-DDTHH:mm:ss.SSSZZ'),
|
|
11
|
+
patient: patientId,
|
|
12
|
+
location: process.env.E2E_LOGIN_DEFAULT_LOCATION_UUID,
|
|
13
|
+
visitType: '7b0f5697-27e3-40c4-8bae-f4049abfb4ed',
|
|
14
|
+
attributes: [],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(visitRes.ok()).toBeTruthy();
|
|
19
|
+
return await visitRes.json();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const endVisit = async (api: APIRequestContext, visit: Visit) => {
|
|
23
|
+
const visitRes = await api.post(`visit/${visit.uuid}`, {
|
|
24
|
+
data: {
|
|
25
|
+
location: visit.location.uuid,
|
|
26
|
+
startDatetime: visit.startDatetime,
|
|
27
|
+
visitType: visit.visitType.uuid,
|
|
28
|
+
stopDatetime: dayjs().format('YYYY-MM-DDTHH:mm:ss.SSSZZ'),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return await visitRes.json();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getVisit = async (api: APIRequestContext, uuid: string): Promise<Visit> => {
|
|
36
|
+
const visitRes = await api.get(`visit/${uuid}?v=full`);
|
|
37
|
+
return await visitRes.json();
|
|
38
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { request } from '@playwright/test';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This configuration is to reuse the signed-in state in the tests
|
|
8
|
+
* by log in only once using the API and then skip the log in step for all the tests.
|
|
9
|
+
*
|
|
10
|
+
* https://playwright.dev/docs/auth#reuse-signed-in-state
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
async function globalSetup() {
|
|
14
|
+
const requestContext = await request.newContext();
|
|
15
|
+
const token = Buffer.from(`${process.env.E2E_USER_ADMIN_USERNAME}:${process.env.E2E_USER_ADMIN_PASSWORD}`).toString(
|
|
16
|
+
'base64',
|
|
17
|
+
);
|
|
18
|
+
await requestContext.post(`${process.env.E2E_BASE_URL}/ws/rest/v1/session`, {
|
|
19
|
+
data: {
|
|
20
|
+
sessionLocation: process.env.E2E_LOGIN_DEFAULT_LOCATION_UUID,
|
|
21
|
+
locale: 'en',
|
|
22
|
+
},
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
Authorization: `Basic ${token}`,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
await requestContext.storageState({ path: 'e2e/storageState.json' });
|
|
29
|
+
await requestContext.dispose();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default globalSetup;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './test';
|
package/e2e/core/test.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type APIRequestContext, type Page, test as base } from '@playwright/test';
|
|
2
|
+
import { api } from '../fixtures';
|
|
3
|
+
import { type Patient } from '../types';
|
|
4
|
+
import { generateRandomPatient, deletePatient } from '../commands';
|
|
5
|
+
|
|
6
|
+
// This file sets up our custom test harness using the custom fixtures.
|
|
7
|
+
// See https://playwright.dev/docs/test-fixtures#creating-a-fixture for details.
|
|
8
|
+
// If a spec intends to use one of the custom fixtures, the special `test` function
|
|
9
|
+
// exported from this file must be used instead of the default `test` function
|
|
10
|
+
// provided by playwright.
|
|
11
|
+
|
|
12
|
+
export interface CustomTestFixtures {
|
|
13
|
+
loginAsAdmin: Page;
|
|
14
|
+
patient: Patient;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CustomWorkerFixtures {
|
|
18
|
+
api: APIRequestContext;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const test = base.extend<CustomTestFixtures, CustomWorkerFixtures>({
|
|
22
|
+
api: [api, { scope: 'worker' }],
|
|
23
|
+
patient: [
|
|
24
|
+
async ({ api }, use) => {
|
|
25
|
+
const patient = await generateRandomPatient(api);
|
|
26
|
+
await use(patient);
|
|
27
|
+
try {
|
|
28
|
+
if (patient) await deletePatient(api, patient.uuid);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.warn('Failed to delete patient:', e);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{ scope: 'test', auto: true },
|
|
34
|
+
],
|
|
35
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
import { type APIRequestContext, type PlaywrightWorkerArgs, type WorkerFixture } from '@playwright/test';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A fixture which initializes an [`APIRequestContext`](https://playwright.dev/docs/api/class-apirequestcontext)
|
|
6
|
+
* that is bound to the configured OpenMRS API server. The context is automatically authenticated
|
|
7
|
+
* using the configured admin account.
|
|
8
|
+
*
|
|
9
|
+
* Use the request context like this:
|
|
10
|
+
* ```ts
|
|
11
|
+
* test('your test', async ({ api }) => {
|
|
12
|
+
* const res = await api.get('patient/1234');
|
|
13
|
+
* await expect(res.ok()).toBeTruthy();
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export const api: WorkerFixture<APIRequestContext, PlaywrightWorkerArgs> = async ({ playwright }, use) => {
|
|
18
|
+
const ctx = await playwright.request.newContext({
|
|
19
|
+
baseURL: `${process.env.E2E_BASE_URL}/ws/rest/v1/`,
|
|
20
|
+
httpCredentials: {
|
|
21
|
+
username: process.env.E2E_USER_ADMIN_USERNAME,
|
|
22
|
+
password: process.env.E2E_USER_ADMIN_PASSWORD,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await use(ctx);
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './api';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './dispensing-page';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import { test } from '../core';
|
|
3
|
+
import { DispensingPage } from '../pages';
|
|
4
|
+
|
|
5
|
+
test('View active prescriptions', async ({ page }) => {
|
|
6
|
+
const dispensingPage = new DispensingPage(page);
|
|
7
|
+
|
|
8
|
+
await test.step('Given I am on the dispensing page', async () => {
|
|
9
|
+
await dispensingPage.goTo();
|
|
10
|
+
|
|
11
|
+
await test.step('Then I should be at the prescriptions page', async () => {
|
|
12
|
+
await expect(page).toHaveURL(`${process.env.E2E_BASE_URL}/spa/dispensing`);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.3
|
|
2
|
+
FROM --platform=$BUILDPLATFORM node:18-alpine as dev
|
|
3
|
+
|
|
4
|
+
ARG APP_SHELL_VERSION=next
|
|
5
|
+
|
|
6
|
+
RUN mkdir -p /app
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
COPY . .
|
|
10
|
+
|
|
11
|
+
RUN npm_config_legacy_peer_deps=true npm install -g openmrs@${APP_SHELL_VERSION:-next}
|
|
12
|
+
ARG CACHE_BUST
|
|
13
|
+
RUN npm_config_legacy_peer_deps=true openmrs assemble --manifest --mode config --config spa-assemble-config.json --target ./spa
|
|
14
|
+
|
|
15
|
+
FROM --platform=$BUILDPLATFORM openmrs/openmrs-reference-application-3-frontend:nightly as frontend
|
|
16
|
+
FROM nginx:1.23-alpine
|
|
17
|
+
|
|
18
|
+
RUN apk update && \
|
|
19
|
+
apk upgrade && \
|
|
20
|
+
# add more utils for sponge to support our startup script
|
|
21
|
+
apk add --no-cache moreutils
|
|
22
|
+
|
|
23
|
+
# clear any default files installed by nginx
|
|
24
|
+
RUN rm -rf /usr/share/nginx/html/*
|
|
25
|
+
|
|
26
|
+
COPY --from=frontend /etc/nginx/nginx.conf /etc/nginx/nginx.conf
|
|
27
|
+
# this assumes that NOTHING in the framework is in a subdirectory
|
|
28
|
+
COPY --from=frontend /usr/share/nginx/html/* /usr/share/nginx/html/
|
|
29
|
+
COPY --from=frontend /usr/local/bin/startup.sh /usr/local/bin/startup.sh
|
|
30
|
+
RUN chmod +x /usr/local/bin/startup.sh
|
|
31
|
+
|
|
32
|
+
COPY --from=dev /app/spa/ /usr/share/nginx/html/
|
|
33
|
+
|
|
34
|
+
CMD ["/usr/local/bin/startup.sh"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# This docker compose file is used to create a backend environment for the e2e.yml workflow.
|
|
2
|
+
# The images are pre-filled with data so that the backend environment can be started within a short time.
|
|
3
|
+
version: "3.7"
|
|
4
|
+
|
|
5
|
+
services:
|
|
6
|
+
gateway:
|
|
7
|
+
image: openmrs/openmrs-reference-application-3-gateway:${TAG:-nightly}
|
|
8
|
+
ports:
|
|
9
|
+
- "8080:80"
|
|
10
|
+
|
|
11
|
+
frontend:
|
|
12
|
+
build:
|
|
13
|
+
context: .
|
|
14
|
+
environment:
|
|
15
|
+
SPA_PATH: /openmrs/spa
|
|
16
|
+
API_URL: /openmrs
|
|
17
|
+
|
|
18
|
+
backend:
|
|
19
|
+
image: openmrs/openmrs-reference-application-3-backend:nightly-with-data
|
|
20
|
+
depends_on:
|
|
21
|
+
- db
|
|
22
|
+
|
|
23
|
+
db:
|
|
24
|
+
image: openmrs/openmrs-reference-application-3-db:nightly-with-data
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash -eu
|
|
2
|
+
|
|
3
|
+
# get the dir containing the script
|
|
4
|
+
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
5
|
+
# create a temporary working directory
|
|
6
|
+
working_dir=$(mktemp -d "${TMPDIR:-/tmp/}openmrs-e2e-frontends.XXXXXXXXXX")
|
|
7
|
+
# get a list of all the apps in this workspace
|
|
8
|
+
apps=$(yarn workspaces list --json | jq -r 'select(.name | test("-app")) | .name')
|
|
9
|
+
# this array will hold all of the packed app names
|
|
10
|
+
app_names=()
|
|
11
|
+
|
|
12
|
+
echo "Copying local dispensing app..."
|
|
13
|
+
cp ./_openmrs_esm_dispensing_app.tgz "$working_dir/_openmrs_esm_dispensing_app.tgz"
|
|
14
|
+
app_names+=("_openmrs_esm_dispensing_app.tgz")
|
|
15
|
+
|
|
16
|
+
echo "Packing other apps..."
|
|
17
|
+
for app in $apps; do
|
|
18
|
+
if [ "$app" != "@openmrs/esm-dispensing-app" ]; then
|
|
19
|
+
app_name=$(echo "$app" | tr '[:punct:]' '_')
|
|
20
|
+
app_names+=("$app_name.tgz")
|
|
21
|
+
yarn workspace "$app" pack -o "$working_dir/$app_name.tgz" >/dev/null
|
|
22
|
+
fi
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
echo "Generating spa-assemble-config.json..."
|
|
26
|
+
jq -n \
|
|
27
|
+
--arg apps "$apps" \
|
|
28
|
+
--arg app_names "$(echo ${app_names[@]})" \
|
|
29
|
+
'{
|
|
30
|
+
"@openmrs/esm-primary-navigation-app": "next",
|
|
31
|
+
"@openmrs/esm-patient-banner-app": "next"
|
|
32
|
+
} + (
|
|
33
|
+
($apps | split("\n")) as $apps |
|
|
34
|
+
($app_names | split(" ") | map("/app/" + .)) as $app_files
|
|
35
|
+
| [$apps, $app_files]
|
|
36
|
+
| transpose
|
|
37
|
+
| map({"key": .[0], "value": .[1]})
|
|
38
|
+
| from_entries
|
|
39
|
+
)' | jq '{"frontendModules": .}' > "$working_dir/spa-assemble-config.json"
|
|
40
|
+
|
|
41
|
+
echo "Setting up Docker..."
|
|
42
|
+
cp "$script_dir/Dockerfile" "$working_dir/Dockerfile"
|
|
43
|
+
cp "$script_dir/docker-compose.yml" "$working_dir/docker-compose.yml"
|
|
44
|
+
|
|
45
|
+
cd "$working_dir"
|
|
46
|
+
docker compose build --build-arg CACHE_BUST=$(date +%s) frontend
|
|
47
|
+
docker compose up -d
|
|
48
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
export interface Encounter {
|
|
4
|
+
uuid: string;
|
|
5
|
+
encounterDateTime: string;
|
|
6
|
+
encounterProviders: Array<{
|
|
7
|
+
uuid: string;
|
|
8
|
+
display: string;
|
|
9
|
+
encounterRole: {
|
|
10
|
+
uuid: string;
|
|
11
|
+
display: string;
|
|
12
|
+
};
|
|
13
|
+
provider: {
|
|
14
|
+
uuid: string;
|
|
15
|
+
person: {
|
|
16
|
+
uuid: string;
|
|
17
|
+
display: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
encounterType: {
|
|
22
|
+
uuid: string;
|
|
23
|
+
display: string;
|
|
24
|
+
};
|
|
25
|
+
obs: Array<Observation>;
|
|
26
|
+
orders: Array<Order>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Observation {
|
|
30
|
+
uuid: string;
|
|
31
|
+
concept: {
|
|
32
|
+
uuid: string;
|
|
33
|
+
display: string;
|
|
34
|
+
conceptClass: {
|
|
35
|
+
uuid: string;
|
|
36
|
+
display: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
display: string;
|
|
40
|
+
groupMembers: null | Array<{
|
|
41
|
+
uuid: string;
|
|
42
|
+
concept: {
|
|
43
|
+
uuid: string;
|
|
44
|
+
display: string;
|
|
45
|
+
};
|
|
46
|
+
value: {
|
|
47
|
+
uuid: string;
|
|
48
|
+
display: string;
|
|
49
|
+
};
|
|
50
|
+
}>;
|
|
51
|
+
value: any;
|
|
52
|
+
obsDatetime: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface Order {
|
|
56
|
+
uuid: string;
|
|
57
|
+
dateActivated: string;
|
|
58
|
+
dateStopped?: Date | null;
|
|
59
|
+
dose: number;
|
|
60
|
+
dosingInstructions: string | null;
|
|
61
|
+
dosingType?: 'org.openmrs.FreeTextDosingInstructions' | 'org.openmrs.SimpleDosingInstructions';
|
|
62
|
+
doseUnits: {
|
|
63
|
+
uuid: string;
|
|
64
|
+
display: string;
|
|
65
|
+
};
|
|
66
|
+
drug: {
|
|
67
|
+
uuid: string;
|
|
68
|
+
name: string;
|
|
69
|
+
strength: string;
|
|
70
|
+
display: string;
|
|
71
|
+
};
|
|
72
|
+
duration: number;
|
|
73
|
+
durationUnits: {
|
|
74
|
+
uuid: string;
|
|
75
|
+
display: string;
|
|
76
|
+
};
|
|
77
|
+
frequency: {
|
|
78
|
+
uuid: string;
|
|
79
|
+
display: string;
|
|
80
|
+
};
|
|
81
|
+
numRefills: number;
|
|
82
|
+
orderNumber: string;
|
|
83
|
+
orderReason: string | null;
|
|
84
|
+
orderReasonNonCoded: string | null;
|
|
85
|
+
orderer: {
|
|
86
|
+
uuid: string;
|
|
87
|
+
person: {
|
|
88
|
+
uuid: string;
|
|
89
|
+
display: string;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
orderType: {
|
|
93
|
+
uuid: string;
|
|
94
|
+
display: string;
|
|
95
|
+
};
|
|
96
|
+
route: {
|
|
97
|
+
uuid: string;
|
|
98
|
+
display: string;
|
|
99
|
+
};
|
|
100
|
+
quantity: number;
|
|
101
|
+
quantityUnits: OpenmrsResource;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface Provider {
|
|
105
|
+
uuid: string;
|
|
106
|
+
display: string;
|
|
107
|
+
comments?: string;
|
|
108
|
+
response?: string;
|
|
109
|
+
person: OpenmrsResource;
|
|
110
|
+
name?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface Patient {
|
|
114
|
+
uuid: string;
|
|
115
|
+
identifiers: Identifier[];
|
|
116
|
+
display: string;
|
|
117
|
+
person: {
|
|
118
|
+
uuid: string;
|
|
119
|
+
display: string;
|
|
120
|
+
gender: string;
|
|
121
|
+
age: number;
|
|
122
|
+
birthdate: string;
|
|
123
|
+
birthdateEstimated: boolean;
|
|
124
|
+
dead: boolean;
|
|
125
|
+
deathDate?: any;
|
|
126
|
+
causeOfDeath?: any;
|
|
127
|
+
preferredAddress: {
|
|
128
|
+
address1: string;
|
|
129
|
+
cityVillage: string;
|
|
130
|
+
country: string;
|
|
131
|
+
postalCode: string;
|
|
132
|
+
stateProvince: string;
|
|
133
|
+
countyDistrict: string;
|
|
134
|
+
};
|
|
135
|
+
attributes: any[];
|
|
136
|
+
voided: boolean;
|
|
137
|
+
birthtime?: any;
|
|
138
|
+
deathdateEstimated: boolean;
|
|
139
|
+
resourceVersion: string;
|
|
140
|
+
};
|
|
141
|
+
attributes: { value: string; attributeType: { uuid: string; display: string } }[];
|
|
142
|
+
voided: boolean;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface Address {
|
|
146
|
+
preferred: boolean;
|
|
147
|
+
address1: string;
|
|
148
|
+
cityVillage: string;
|
|
149
|
+
country: string;
|
|
150
|
+
postalCode: string;
|
|
151
|
+
stateProvince: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface Identifier {
|
|
155
|
+
uuid: string;
|
|
156
|
+
display: string;
|
|
157
|
+
}
|
package/example.env
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# This is an example environment file for configuring dynamic values.
|
|
2
|
+
E2E_BASE_URL=http://localhost:8080/openmrs
|
|
3
|
+
E2E_USER_ADMIN_USERNAME=admin
|
|
4
|
+
E2E_USER_ADMIN_PASSWORD=Admin123
|
|
5
|
+
E2E_LOGIN_DEFAULT_LOCATION_UUID=44c3efb0-2583-4c80-a79e-1f756a03c0a1
|
|
6
|
+
# The above location UUID is for the "Outpatient Clinic" location in the reference application
|
|
7
|
+
|
package/jest.config.js
CHANGED
|
@@ -13,6 +13,14 @@ module.exports = {
|
|
|
13
13
|
'^uuid$': '<rootDir>/node_modules/uuid/dist/index.js',
|
|
14
14
|
dexie: require.resolve('dexie'),
|
|
15
15
|
},
|
|
16
|
+
collectCoverageFrom: [
|
|
17
|
+
'!**/node_modules/**',
|
|
18
|
+
'!**/e2e/**',
|
|
19
|
+
],
|
|
20
|
+
testPathIgnorePatterns: [
|
|
21
|
+
"/node_modules/",
|
|
22
|
+
"/e2e/" // Ignore the e2e directory containing Playwright tests
|
|
23
|
+
],
|
|
16
24
|
setupFilesAfterEnv: ['<rootDir>/tools/setup-tests.ts'],
|
|
17
25
|
testEnvironment: 'jsdom',
|
|
18
26
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-dispensing-app",
|
|
3
|
-
"version": "1.7.1-pre.
|
|
3
|
+
"version": "1.7.1-pre.182",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Medication dispensing application",
|
|
6
6
|
"browser": "dist/openmrs-esm-dispensing-app.js",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"verify": "turbo run lint test typescript",
|
|
19
19
|
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ./tools/i18next-parser.config.js",
|
|
20
20
|
"coverage": "yarn test --coverage",
|
|
21
|
-
"postinstall": "husky install"
|
|
21
|
+
"postinstall": "husky install",
|
|
22
|
+
"test-e2e": "playwright test"
|
|
22
23
|
},
|
|
23
24
|
"browserslist": [
|
|
24
25
|
"extends browserslist-config-openmrs"
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@openmrs/esm-framework": "next",
|
|
62
63
|
"@openmrs/esm-patient-common-lib": "next",
|
|
64
|
+
"@playwright/test": "^1.52.0",
|
|
63
65
|
"@swc/core": "^1.7.14",
|
|
64
66
|
"@swc/jest": "^0.2.36",
|
|
65
67
|
"@testing-library/dom": "^8.20.1",
|
|
@@ -75,6 +77,7 @@
|
|
|
75
77
|
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
|
76
78
|
"@typescript-eslint/parser": "^7.5.0",
|
|
77
79
|
"dayjs": "^1.11.13",
|
|
80
|
+
"dotenv": "^16.3.1",
|
|
78
81
|
"eslint": "^8.57.0",
|
|
79
82
|
"eslint-plugin-import": "^2.31.0",
|
|
80
83
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { devices, type PlaywrightTestConfig } from '@playwright/test';
|
|
2
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
dotenvConfig({ path: resolve(process.cwd(), '.env') });
|
|
5
|
+
dotenvConfig();
|
|
6
|
+
|
|
7
|
+
// See https://playwright.dev/docs/test-configuration.
|
|
8
|
+
const config: PlaywrightTestConfig = {
|
|
9
|
+
testDir: './e2e/specs',
|
|
10
|
+
timeout: 3 * 60 * 1000,
|
|
11
|
+
expect: {
|
|
12
|
+
timeout: 40 * 1000,
|
|
13
|
+
},
|
|
14
|
+
fullyParallel: true,
|
|
15
|
+
forbidOnly: !!process.env.CI,
|
|
16
|
+
retries: 0,
|
|
17
|
+
reporter: process.env.CI ? [['junit', { outputFile: 'results.xml' }], ['html']] : [['html']],
|
|
18
|
+
globalSetup: require.resolve('./e2e/core/global-setup'),
|
|
19
|
+
use: {
|
|
20
|
+
baseURL: `${process.env.E2E_BASE_URL}/spa/`,
|
|
21
|
+
storageState: 'e2e/storageState.json',
|
|
22
|
+
video: 'retain-on-failure',
|
|
23
|
+
trace: 'retain-on-failure',
|
|
24
|
+
},
|
|
25
|
+
projects: [
|
|
26
|
+
{
|
|
27
|
+
name: 'chromium',
|
|
28
|
+
use: {
|
|
29
|
+
...devices['Desktop Chrome'],
|
|
30
|
+
channel: 'chromium',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default config;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"hash":"27edf80628bc1fab","duration":42075}
|