diff --git a/.eslintrc b/.eslintrc index 8ab6c1327..485b7ea01 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,11 +2,19 @@ "env": { "node": true }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:jest-dom/recommended", - "plugin:testing-library/react" + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:jest-dom/recommended"], + "overrides": [ + { + "files": ["**/*.test.tsx"], + "extends": ["plugin:testing-library/react"] + }, + { + "files": ["e2e/**/*.spec.ts"], + "extends": ["plugin:playwright/recommended"], + "rules": { + "testing-library/prefer-screen-queries": "off" + } + } ], "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint", "import", "jest-dom", "react-hooks", "testing-library"], @@ -71,35 +79,5 @@ "prefer-const": "off", "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn" - }, - "overrides": [ - { - "files": ["**/e2e/**"], - "rules": { - "testing-library/await-async-events": "off", - "testing-library/await-async-query": "off", - "testing-library/await-async-utils": "off", - "testing-library/no-await-sync-events": "off", - "testing-library/no-await-sync-queries": "off", - "testing-library/no-container": "off", - "testing-library/no-debugging-utils": "off", - "testing-library/no-dom-import": "off", - "testing-library/no-global-regexp-flag-in-query": "off", - "testing-library/no-manual-cleanup": "off", - "testing-library/no-node-access": "off", - "testing-library/no-promise-in-fire-event": "off", - "testing-library/no-render-in-lifecycle": "off", - "testing-library/no-unnecessary-act": "off", - "testing-library/no-wait-for-multiple-assertions": "off", - "testing-library/no-wait-for-side-effects": "off", - "testing-library/no-wait-for-snapshot": "off", - "testing-library/prefer-find-by": "off", - "testing-library/prefer-implicit-assert": "off", - "testing-library/prefer-presence-queries": "off", - "testing-library/prefer-query-by-disappearance": "off", - "testing-library/prefer-screen-queries": "off", - "testing-library/render-result-naming-convention": "off" - } - } - ] + } } diff --git a/e2e/commands/cohort-operations.ts b/e2e/commands/cohort-operations.ts index 496038970..2a7042abd 100644 --- a/e2e/commands/cohort-operations.ts +++ b/e2e/commands/cohort-operations.ts @@ -1,43 +1,5 @@ import { type APIRequestContext, expect } from '@playwright/test'; -import { type Patient } from './patient-operations'; - -export interface CohortType { - uuid: string; - name: string; - description: string; - display: string; - links: { rel: string; uri: string; resourceAlias: string }[]; - resourceVersion: string; -} - -export interface Cohort { - uuid: string; - name: string; - description: string; - attributes: any[]; - links: any[]; - location: any; - groupCohort: boolean | null; - startDate: Date; - endDate: Date; - voidReason: string | null; - voided: boolean; - isStarred?: boolean; - type?: string; - size: number; - cohortType?: CohortType; - resourceVersion: string; -} - -export interface CohortMember { - attributes: Array; - description: string; - endDate: string; - startDate: string; - name: string; - uuid: string; - patient: Patient; -} +import { type Cohort, type CohortMember } from '../types'; export const generateRandomCohort = async (api: APIRequestContext): Promise => { const cohortRes = await api.post('cohortm/cohort', { diff --git a/e2e/commands/encounter-operations.ts b/e2e/commands/encounter-operations.ts index aeb991013..efccad25e 100644 --- a/e2e/commands/encounter-operations.ts +++ b/e2e/commands/encounter-operations.ts @@ -1,6 +1,6 @@ -import { Encounter } from './../../packages/esm-active-visits-app/src/visits-summary/visit.resource'; -import { APIRequestContext, expect } from '@playwright/test'; +import { type APIRequestContext, expect } from '@playwright/test'; import dayjs from 'dayjs'; +import { type Encounter } from '../types'; export const createEncounter = async ( api: APIRequestContext, diff --git a/e2e/commands/patient-operations.ts b/e2e/commands/patient-operations.ts index cc9fe99ff..e2efa2491 100644 --- a/e2e/commands/patient-operations.ts +++ b/e2e/commands/patient-operations.ts @@ -1,50 +1,5 @@ import { type APIRequestContext, expect } from '@playwright/test'; - -export interface Patient { - uuid: string; - identifiers: Identifier[]; - display: string; - person: { - uuid: string; - display: string; - gender: string; - age: number; - birthdate: string; - birthdateEstimated: boolean; - dead: boolean; - deathDate?: any; - causeOfDeath?: any; - preferredAddress: { - address1: string; - cityVillage: string; - country: string; - postalCode: string; - stateProvince: string; - countyDistrict: string; - }; - attributes: any[]; - voided: boolean; - birthtime?: any; - deathdateEstimated: boolean; - resourceVersion: string; - }; - attributes: { value: string; attributeType: { uuid: string; display: string } }[]; - voided: boolean; -} - -export interface Address { - preferred: boolean; - address1: string; - cityVillage: string; - country: string; - postalCode: string; - stateProvince: string; -} - -export interface Identifier { - uuid: string; - display: string; -} +import { type Patient } from '../types'; export const generateRandomPatient = async (api: APIRequestContext): Promise => { const identifierRes = await api.post('idgen/identifiersource/8549f706-7e85-4c1d-9424-217d50a2988b/identifier', { diff --git a/e2e/commands/provider-operations.ts b/e2e/commands/provider-operations.ts index c925313dc..8c056665d 100644 --- a/e2e/commands/provider-operations.ts +++ b/e2e/commands/provider-operations.ts @@ -1,5 +1,5 @@ -import { Provider } from '../../packages/esm-appointments-app/src/types/index'; -import { APIRequestContext, expect } from '@playwright/test'; +import { type APIRequestContext, expect } from '@playwright/test'; +import { type Provider } from '../types'; export const getProvider = async (api: APIRequestContext): Promise => { const providerRes = await api.get('provider?q=admin', { diff --git a/e2e/commands/visit-operations.ts b/e2e/commands/visit-operations.ts index 2091740ab..4316c395d 100644 --- a/e2e/commands/visit-operations.ts +++ b/e2e/commands/visit-operations.ts @@ -1,6 +1,6 @@ -import { APIRequestContext, expect } from '@playwright/test'; -import { Visit } from '@openmrs/esm-framework'; +import { type APIRequestContext, expect } from '@playwright/test'; import dayjs from 'dayjs'; +import { type Visit } from '@openmrs/esm-framework'; export const startVisit = async (api: APIRequestContext, patientId: string): Promise => { const visitRes = await api.post('visit', { diff --git a/e2e/pages/home-page.ts b/e2e/pages/home-page.ts index 8359dd9a5..bb5ff1700 100644 --- a/e2e/pages/home-page.ts +++ b/e2e/pages/home-page.ts @@ -1,4 +1,4 @@ -import { Page } from '@playwright/test'; +import { type Page } from '@playwright/test'; export class HomePage { constructor(readonly page: Page) {} diff --git a/e2e/pages/patient-lists-page.ts b/e2e/pages/patient-lists-page.ts index b5ac5f7e4..8e0922d10 100644 --- a/e2e/pages/patient-lists-page.ts +++ b/e2e/pages/patient-lists-page.ts @@ -1,4 +1,4 @@ -import { Page } from '@playwright/test'; +import { type Page } from '@playwright/test'; export class PatientListsPage { constructor(readonly page: Page) {} diff --git a/e2e/pages/registration-and-edit-page.ts b/e2e/pages/registration-and-edit-page.ts index 3041689a2..753b6d194 100644 --- a/e2e/pages/registration-and-edit-page.ts +++ b/e2e/pages/registration-and-edit-page.ts @@ -1,27 +1,5 @@ import { type Locator, type Page, expect } from '@playwright/test'; - -export type PatientRegistrationSex = 'male' | 'female' | 'other' | 'unknown'; - -export interface PatientRegistrationFormValues { - givenName?: string; - middleName?: string; - familyName?: string; - sex?: PatientRegistrationSex; - birthdate?: { - day: string; - month: string; - year: string; - }; - postalCode?: string; - address1?: string; - address2?: string; - country?: string; - countyDistrict?: string; - stateProvince?: string; - cityVillage?: string; - phone?: string; - email?: string; -} +import { type PatientRegistrationFormValues, type PatientRegistrationSex } from '../types'; export class RegistrationAndEditPage { constructor(readonly page: Page) {} diff --git a/e2e/specs/active-visits.spec.ts b/e2e/specs/active-visits.spec.ts index fb70f607b..1ba677c18 100644 --- a/e2e/specs/active-visits.spec.ts +++ b/e2e/specs/active-visits.spec.ts @@ -1,19 +1,18 @@ import { expect } from '@playwright/test'; import type { Visit } from '@openmrs/esm-framework'; import { test } from '../core'; -import type { Provider } from '../../packages/esm-appointments-app/src/types/index'; -import type { Encounter } from '../../packages/esm-active-visits-app/src/visits-summary/visit.resource'; + import { createEncounter, deleteEncounter, deletePatient, endVisit, generateRandomPatient, - type Patient, - startVisit, getProvider, + startVisit, } from '../commands'; import { HomePage } from '../pages'; +import { type Encounter, type Patient, type Provider } from '../types'; let patient: Patient; let visit: Visit; diff --git a/e2e/specs/appointments.spec.ts b/e2e/specs/appointments.spec.ts index 87d387989..7705ea0a9 100644 --- a/e2e/specs/appointments.spec.ts +++ b/e2e/specs/appointments.spec.ts @@ -1,8 +1,9 @@ import { expect } from '@playwright/test'; -import { generateRandomPatient, deletePatient, type Patient, startVisit, endVisit } from '../commands'; +import { generateRandomPatient, deletePatient, startVisit, endVisit } from '../commands'; +import { type Visit } from '@openmrs/esm-framework'; +import { type Patient } from '../types'; import { test } from '../core'; import { AppointmentsPage } from '../pages'; -import { type Visit } from '@openmrs/esm-framework'; let patient: Patient; let visit: Visit; @@ -111,7 +112,7 @@ test('Add, edit and cancel an appointment', async ({ page, api }) => { await page.getByRole('button', { name: 'Options' }).click(); }); - await test.step('And I choose the "Cancel" option ', async () => { + await test.step('And I choose the "Cancel" option', async () => { await page.getByRole('menuitem', { name: 'Cancel' }).click(); }); diff --git a/e2e/specs/edit-patient.spec.ts b/e2e/specs/edit-patient.spec.ts index c76d228aa..0aa701512 100644 --- a/e2e/specs/edit-patient.spec.ts +++ b/e2e/specs/edit-patient.spec.ts @@ -1,8 +1,9 @@ import dayjs from 'dayjs'; import { expect } from '@playwright/test'; import { test } from '../core'; -import { deletePatient, generateRandomPatient, getPatient, type Patient } from '../commands'; -import { type PatientRegistrationFormValues, RegistrationAndEditPage } from '../pages'; +import { deletePatient, generateRandomPatient, getPatient } from '../commands'; +import { RegistrationAndEditPage } from '../pages'; +import { type Patient, type PatientRegistrationFormValues } from '../types'; let patient: Patient; diff --git a/e2e/specs/patient-list.spec.ts b/e2e/specs/patient-list.spec.ts index 69092ccea..1591b4b90 100644 --- a/e2e/specs/patient-list.spec.ts +++ b/e2e/specs/patient-list.spec.ts @@ -2,9 +2,6 @@ import { test } from '../core'; import { PatientListsPage } from '../pages'; import { expect } from '@playwright/test'; import { - type Cohort, - type CohortMember, - type Patient, addPatientToCohort, deleteCohort, deletePatient, @@ -12,6 +9,7 @@ import { generateRandomPatient, removePatientFromCohort, } from '../commands'; +import { type Cohort, type CohortMember, type Patient } from '../types'; let cohortMember: CohortMember; let cohortUuid: string; @@ -46,7 +44,8 @@ test('Create and edit a patient list', async ({ page }) => { await test.step('Then I should see the information about the list', async () => { await expect(page).toHaveURL(new RegExp('^[\\w\\d:\\/.-]+\\/patient-lists\\/[\\w\\d-]+$')); - cohortUuid = /patient-lists\/([\w\d-]+)/.exec(page.url())?.[1] ?? null; + const [, extractedUuid] = /patient-lists\/([\w\d-]+)/.exec(page.url()); + cohortUuid = extractedUuid; await expect(patientListPage.patientListHeader()).toHaveText(new RegExp(patientListName)); await expect(patientListPage.patientListHeader()).toHaveText(new RegExp(patientListDescription)); diff --git a/e2e/specs/patient-search.spec.ts b/e2e/specs/patient-search.spec.ts index b91f53c67..54c13b1ac 100644 --- a/e2e/specs/patient-search.spec.ts +++ b/e2e/specs/patient-search.spec.ts @@ -1,7 +1,8 @@ import { expect } from '@playwright/test'; import { test } from '../core'; import { HomePage } from '../pages'; -import { generateRandomPatient, deletePatient, type Patient } from '../commands'; +import { generateRandomPatient, deletePatient } from '../commands'; +import { type Patient } from '../types'; let patient: Patient; diff --git a/e2e/specs/register-new-patient.spec.ts b/e2e/specs/register-new-patient.spec.ts index 365b0bdf8..ed4fca8ae 100644 --- a/e2e/specs/register-new-patient.spec.ts +++ b/e2e/specs/register-new-patient.spec.ts @@ -1,6 +1,7 @@ import { expect } from '@playwright/test'; import { test } from '../core'; -import { type PatientRegistrationFormValues, RegistrationAndEditPage } from '../pages'; +import { RegistrationAndEditPage } from '../pages'; +import { type PatientRegistrationFormValues } from '../types'; import { deletePatient } from '../commands'; let patientUuid: string; @@ -101,7 +102,7 @@ test('Register an unknown patient', async ({ api, page }) => { }); await test.step('And then I fill in 25 as the estimated age in years', async () => { - const estimatedAgeField = await page.getByLabel(/estimated age in years/i); + const estimatedAgeField = page.getByLabel(/estimated age in years/i); await estimatedAgeField.clear(); await estimatedAgeField.fill('25'); }); @@ -123,7 +124,7 @@ test('Register an unknown patient', async ({ api, page }) => { await test.step("And I should see the newly registered patient's details displayed in the patient banner", async () => { const patientBanner = page.locator('header[aria-label="patient banner"]'); - expect(patientBanner).toBeVisible(); + await expect(patientBanner).toBeVisible(); await expect(patientBanner.getByText('Unknown Unknown')).toBeVisible(); await expect(patientBanner.getByText(/female/i)).toBeVisible(); await expect(patientBanner.getByText(/25 yrs/i)).toBeVisible(); diff --git a/e2e/specs/return-to-patient-list.spec.ts b/e2e/specs/return-to-patient-list.spec.ts index 2023f0840..a86c56572 100644 --- a/e2e/specs/return-to-patient-list.spec.ts +++ b/e2e/specs/return-to-patient-list.spec.ts @@ -3,15 +3,13 @@ import { PatientListsPage } from '../pages'; import { expect } from '@playwright/test'; import { addPatientToCohort, - type Cohort, - type CohortMember, deleteCohort, deletePatient, generateRandomCohort, generateRandomPatient, - type Patient, removePatientFromCohort, } from '../commands'; +import { type Cohort, type CohortMember, type Patient } from '../types'; let cohortMembership: CohortMember; let cohort: Cohort; @@ -123,7 +121,7 @@ test('Return to patient list after navigating to visits and refreshing the page' test('Return to patient list from the patient chart on a new tab', async ({ page, context }) => { const patientListPage = new PatientListsPage(page); - const locator = await page.locator('table tbody tr td:nth-child(1) a'); + const locator = page.locator('table tbody tr td:nth-child(1) a'); const pagePromise = context.waitForEvent('page'); await test.step('When I navigate to the patient list', async () => { diff --git a/e2e/types/index.ts b/e2e/types/index.ts new file mode 100644 index 000000000..540f16211 --- /dev/null +++ b/e2e/types/index.ts @@ -0,0 +1,218 @@ +import { type OpenmrsResource } from '@openmrs/esm-framework'; + +export type PatientRegistrationSex = 'male' | 'female' | 'other' | 'unknown'; + +export interface PatientRegistrationFormValues { + givenName?: string; + middleName?: string; + familyName?: string; + sex?: PatientRegistrationSex; + birthdate?: { + day: string; + month: string; + year: string; + }; + postalCode?: string; + address1?: string; + address2?: string; + country?: string; + countyDistrict?: string; + stateProvince?: string; + cityVillage?: string; + phone?: string; + email?: string; +} + +export interface Encounter { + uuid: string; + encounterDateTime: string; + encounterProviders: Array<{ + uuid: string; + display: string; + encounterRole: { + uuid: string; + display: string; + }; + provider: { + uuid: string; + person: { + uuid: string; + display: string; + }; + }; + }>; + encounterType: { + uuid: string; + display: string; + }; + obs: Array; + orders: Array; +} + +export interface Observation { + uuid: string; + concept: { + uuid: string; + display: string; + conceptClass: { + uuid: string; + display: string; + }; + }; + display: string; + groupMembers: null | Array<{ + uuid: string; + concept: { + uuid: string; + display: string; + }; + value: { + uuid: string; + display: string; + }; + }>; + value: any; + obsDatetime: string; +} + +export interface Order { + uuid: string; + dateActivated: string; + dateStopped?: Date | null; + dose: number; + dosingInstructions: string | null; + dosingType?: 'org.openmrs.FreeTextDosingInstructions' | 'org.openmrs.SimpleDosingInstructions'; + doseUnits: { + uuid: string; + display: string; + }; + drug: { + uuid: string; + name: string; + strength: string; + display: string; + }; + duration: number; + durationUnits: { + uuid: string; + display: string; + }; + frequency: { + uuid: string; + display: string; + }; + numRefills: number; + orderNumber: string; + orderReason: string | null; + orderReasonNonCoded: string | null; + orderer: { + uuid: string; + person: { + uuid: string; + display: string; + }; + }; + orderType: { + uuid: string; + display: string; + }; + route: { + uuid: string; + display: string; + }; + quantity: number; + quantityUnits: OpenmrsResource; +} + +export interface Provider { + uuid: string; + display: string; + comments?: string; + response?: string; + person: OpenmrsResource; + name?: string; +} + +export interface CohortType { + uuid: string; + name: string; + description: string; + display: string; + links: { rel: string; uri: string; resourceAlias: string }[]; + resourceVersion: string; +} + +export interface Cohort { + uuid: string; + name: string; + description: string; + attributes: any[]; + links: any[]; + location: any; + groupCohort: boolean | null; + startDate: Date; + endDate: Date; + voidReason: string | null; + voided: boolean; + isStarred?: boolean; + type?: string; + size: number; + cohortType?: CohortType; + resourceVersion: string; +} + +export interface CohortMember { + attributes: Array; + description: string; + endDate: string; + startDate: string; + name: string; + uuid: string; + patient: Patient; +} + +export interface Patient { + uuid: string; + identifiers: Identifier[]; + display: string; + person: { + uuid: string; + display: string; + gender: string; + age: number; + birthdate: string; + birthdateEstimated: boolean; + dead: boolean; + deathDate?: any; + causeOfDeath?: any; + preferredAddress: { + address1: string; + cityVillage: string; + country: string; + postalCode: string; + stateProvince: string; + countyDistrict: string; + }; + attributes: any[]; + voided: boolean; + birthtime?: any; + deathdateEstimated: boolean; + resourceVersion: string; + }; + attributes: { value: string; attributeType: { uuid: string; display: string } }[]; + voided: boolean; +} + +export interface Address { + preferred: boolean; + address1: string; + cityVillage: string; + country: string; + postalCode: string; + stateProvince: string; +} + +export interface Identifier { + uuid: string; + display: string; +} diff --git a/package.json b/package.json index 31bc64daf..fce37340b 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "eslint": "^8.55.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest-dom": "^5.4.0", + "eslint-plugin-playwright": "^2.1.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-testing-library": "^6.2.2", "husky": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 2a789df97..e00d80e26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3333,6 +3333,7 @@ __metadata: eslint: "npm:^8.55.0" eslint-plugin-import: "npm:^2.31.0" eslint-plugin-jest-dom: "npm:^5.4.0" + eslint-plugin-playwright: "npm:^2.1.0" eslint-plugin-react-hooks: "npm:^4.6.0" eslint-plugin-testing-library: "npm:^6.2.2" husky: "npm:^8.0.3" @@ -9942,6 +9943,17 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-playwright@npm:^2.1.0": + version: 2.1.0 + resolution: "eslint-plugin-playwright@npm:2.1.0" + dependencies: + globals: "npm:^13.23.0" + peerDependencies: + eslint: ">=8.40.0" + checksum: 10/5c36202a56760203bf3738b03fbd1fddce520f09772b998d2cd7631636f5dec1c4dda724af94ccafb462ffadd113bb8a940ef6991661095cab4997cf19863000 + languageName: node + linkType: hard + "eslint-plugin-react-hooks@npm:^4.6.0": version: 4.6.0 resolution: "eslint-plugin-react-hooks@npm:4.6.0" @@ -10970,7 +10982,7 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.19.0": +"globals@npm:^13.19.0, globals@npm:^13.23.0": version: 13.24.0 resolution: "globals@npm:13.24.0" dependencies: