diff --git a/.env b/.env index e3d584e6..c43af12b 100644 --- a/.env +++ b/.env @@ -19,6 +19,11 @@ O3_URL_DEV=https://ozone-dev.mekomsolutions.net O3_URL_QA=https://ozone-qa.mekomsolutions.net O3_URL_DEMO=https://demo.ozone-his.com +# ERPNEXT +ERPNEXT_URL_DEV=https://erpnext.ozone-dev.mekomsolutions.net +ERPNEXT_URL_QA= +ERPNEXT_URL_DEMO= + # Odoo ODOO_URL_DEV=https://erp.ozone-dev.mekomsolutions.net ODOO_URL_QA=https://erp.ozone-qa.mekomsolutions.net @@ -47,6 +52,10 @@ KEYCLOAK_URL_DEMO=https://auth.demo.ozone-his.com O3_USERNAME_ON_FOSS=admin O3_PASSWORD_ON_FOSS=Admin123 +# ERPNext test user credentials +ERPNEXT_USERNAME=Administrator +ERPNEXT_PASSWORD=password + # Odoo test user credentials for FOSS ODOO_USERNAME_ON_FOSS=admin ODOO_PASSWORD_ON_FOSS=admin diff --git a/e2e/tests/erpnext-openmrs-flows.spec.ts b/e2e/tests/erpnext-openmrs-flows.spec.ts new file mode 100644 index 00000000..4e6c0c9e --- /dev/null +++ b/e2e/tests/erpnext-openmrs-flows.spec.ts @@ -0,0 +1,142 @@ +import { test, expect } from '@playwright/test'; +import { ERPNext } from '../utils/functions/erpnext'; +import { O3_URL, ERPNEXT_URL } from '../utils/configs/globalSetup'; +import { OpenMRS, patientName } from '../utils/functions/openmrs'; + +let openmrs: OpenMRS; +let erpnext: ERPNext; + +test.beforeEach(async ({ page }) => { + openmrs = new OpenMRS(page); + erpnext = new ERPNext(page); + + await openmrs.login(); + await expect(page).toHaveURL(/.*home/); + await openmrs.createPatient(); + await openmrs.startPatientVisit(); +}); + +test('Ordering a lab test for an OpenMRS patient creates the corresponding ERPNext customer.', async ({ page }) => { + // replay + await openmrs.createLabOrder(); + + // verify + await erpnext.open(); + await expect(page).toHaveURL(/.*home/); + await erpnext.searchCustomer(); + const customer = await page.locator(".bold a:nth-child(1)"); + await expect(customer).toContainText(`${patientName.firstName + ' ' + patientName.givenName}`); + await openmrs.deletePatient(); +}); + +test('Ordering a drug for an OpenMRS patient creates the corresponding ERPNext customer with a filled quotation.', async ({ page }) => { + // replay + await openmrs.createDrugOrder(); + + // verify + await erpnext.open(); + await expect(page).toHaveURL(/.*home/); + await erpnext.searchCustomer(); + const customer = await page.locator(".bold a:nth-child(1)"); + await expect(customer).toContainText(`${patientName.firstName + ' ' + patientName.givenName}`); + await page.getByRole('link', { name: `${patientName.firstName + ' ' + patientName.givenName}` }).click(); + await page.locator('#customer-dashboard_tab-tab').click(); + await page.getByLabel('Dashboard').getByText('Quotation').click(); + await erpnext.searchQuotation(); + await expect(page.getByText('Draft').nth(0)).toBeVisible(); + await openmrs.deletePatient(); + await erpnext.deleteQuotation(); +}); + +test('Ending an OpenMRS patient visit with a synced drug order updates the corresponding ERPNext draft quotation to an open state.', async ({ page }) => { + // setup + await openmrs.createDrugOrder(); + await erpnext.open(); + await expect(page).toHaveURL(/.*home/); + await erpnext.searchQuotation(); + + const quotationStatus = await page.locator('div.level-left.ellipsis div:nth-child(3) span span'); + await expect(quotationStatus).toHaveText('Draft'); + + // replay + await page.goto(`${O3_URL}`); + await openmrs.searchPatient(`${patientName.firstName + ' ' + patientName.givenName}`); + await openmrs.endPatientVisit(); + + // verify + await page.goto(`${ERPNEXT_URL}/app/home`); + await erpnext.searchQuotation(); + await expect(quotationStatus).toHaveText('Open'); + await erpnext.voidQuotation(); + await openmrs.deletePatient(); + await erpnext.deleteQuotation(); +}); + +test('Revising a synced OpenMRS drug order edits the corresponding ERPNext quotation item.', async ({ page }) => { + // setup + await openmrs.createDrugOrder(); + await erpnext.open(); + await expect(page).toHaveURL(/.*home/); + await erpnext.searchQuotation(); + await page.getByRole('link', { name: `${patientName.firstName + ' ' + patientName.givenName}` }).click(); + const quantity = await page.locator("div.bold:nth-child(4) div:nth-child(2) div"); + await expect(quantity).toHaveText('12'); + + // replay + await page.goto(`${O3_URL}`); + await openmrs.searchPatient(`${patientName.firstName + ' ' + patientName.givenName}`); + await openmrs.editDrugOrder(); + + // verify + await page.goto(`${ERPNEXT_URL}/app/home`); + await erpnext.searchQuotation(); + await page.getByRole('link', { name: `${patientName.firstName + ' ' + patientName.givenName}` }).click(); + await expect(quantity).toHaveText('8'); + await openmrs.deletePatient(); + await erpnext.deleteQuotation(); +}); + +test('Ordering a drug with a free text medication dosage for an OpenMRS patient creates the corresponding ERPNext customer with a filled quotation.', async ({ page }) => { + // replay + await openmrs.createDrugOrderWithFreeTextDosage(); + + // verify + await erpnext.open(); + await expect(page).toHaveURL(/.*home/); + await erpnext.searchCustomer(); + const customer = await page.locator(".bold a:nth-child(1)"); + await expect(customer).toContainText(`${patientName.firstName + ' ' + patientName.givenName}`); + await page.getByRole('link', { name: `${patientName.firstName + ' ' + patientName.givenName}` }).click(); + await page.locator('#customer-dashboard_tab-tab').click(); + await page.getByLabel('Dashboard').getByText('Quotation').click(); + await erpnext.searchQuotation(); + await expect(page.getByText('Draft').nth(0)).toBeVisible(); + await openmrs.deletePatient(); + await erpnext.deleteQuotation(); +}); + +test('Discontinuing a synced OpenMRS drug order for an ERPNext customer with a single quotation line removes the corresponding quotation.', async ({ page }) => { + // setup + await openmrs.createDrugOrder(); + await erpnext.open(); + await expect(page).toHaveURL(/.*home/); + await erpnext.searchQuotation(); + await expect(page.getByText(`${patientName.firstName + ' ' + patientName.givenName}`)).toBeVisible(); + + // replay + await page.goto(`${O3_URL}`); + await openmrs.searchPatient(`${patientName.firstName + ' ' + patientName.givenName}`); + await openmrs.discontinueDrugOrder(); + + // verify + await page.goto(`${ERPNEXT_URL}/app/home`); + await erpnext.searchQuotation(); + await expect(page.getByText(`${patientName.firstName + ' ' + patientName.givenName}`)).not.toBeVisible(); + await expect(page.getByText('No Quotation found')).toBeVisible(); + await openmrs.deletePatient(); +}); + +test.afterEach(async ({ page }) => { + await erpnext.deleteCustomer(); + await page.close(); +}); diff --git a/e2e/tests/odoo-openmrs-flows.spec.ts b/e2e/tests/odoo-openmrs-flows.spec.ts index 8f118796..7e9ca649 100644 --- a/e2e/tests/odoo-openmrs-flows.spec.ts +++ b/e2e/tests/odoo-openmrs-flows.spec.ts @@ -148,7 +148,6 @@ test('Discontinuing a synced OpenMRS drug order for an Odoo customer with a sing await expect(QuotationItem).not.toHaveText('Aspirin 325mg'); }); - test('Discontinuing a synced OpenMRS drug order for an Odoo customer with multiple quotation lines removes the corresponding quoatation.', async ({ page }) => { // setup await openmrs.goToLabOrderForm(); diff --git a/e2e/utils/configs/globalSetup.ts b/e2e/utils/configs/globalSetup.ts index 0587d5a4..90dcb12e 100644 --- a/e2e/utils/configs/globalSetup.ts +++ b/e2e/utils/configs/globalSetup.ts @@ -12,6 +12,7 @@ import { dotenv.config(); export const O3_URL = `${process.env.TEST_ENVIRONMENT}` == 'demo' ? `${process.env.O3_URL_DEMO}` : `${process.env.TEST_ENVIRONMENT}` == 'qa' ? `${process.env.O3_URL_QA}`: `${process.env.O3_URL_DEV}`; +export const ERPNEXT_URL = `${process.env.TEST_ENVIRONMENT}` == 'demo' ? `${process.env.ERPNEXT_URL_DEMO}` : `${process.env.TEST_ENVIRONMENT}` == 'qa' ? `${process.env.ERPNEXT_URL_QA}`: `${process.env.ERPNEXT_URL_DEV}`; export const ODOO_URL = `${process.env.TEST_ENVIRONMENT}` == 'demo' ? `${process.env.ODOO_URL_DEMO}` : `${process.env.TEST_ENVIRONMENT}` == 'qa' ? `${process.env.ODOO_URL_QA}`: `${process.env.ODOO_URL_DEV}`; export const SENAITE_URL = `${process.env.TEST_ENVIRONMENT}` == 'demo' ? `${process.env.SENAITE_URL_DEMO}` : `${process.env.TEST_ENVIRONMENT}` == 'qa' ? `${process.env.SENAITE_URL_QA}`: `${process.env.SENAITE_URL_DEV}`; export const KEYCLOAK_URL = `${process.env.TEST_ENVIRONMENT}` == 'demo' ? `${process.env.KEYCLOAK_URL_DEMO}` : `${process.env.TEST_ENVIRONMENT}` == 'qa' ? `${process.env.KEYCLOAK_URL_QA}`: `${process.env.KEYCLOAK_URL_DEV}`; diff --git a/e2e/utils/functions/erpnext.ts b/e2e/utils/functions/erpnext.ts new file mode 100644 index 00000000..090218a3 --- /dev/null +++ b/e2e/utils/functions/erpnext.ts @@ -0,0 +1,63 @@ +import { Page, expect } from '@playwright/test'; +import { delay, patientName } from './openmrs'; +import { ERPNEXT_URL } from '../configs/globalSetup'; + +export class ERPNext { + constructor(readonly page: Page) {} + + async open() { + await this.page.goto(`${ERPNEXT_URL}`); + await this.page.locator('input#login_email').fill(`${process.env.ERPNEXT_USERNAME}`); + await this.page.locator('input#login_password').fill(`${process.env.ERPNEXT_PASSWORD}`); + await this.page.locator('button.btn-login').click(); + } + + async searchCustomer() { + await this.page.getByRole('link', { name: /selling/i }).click(); + await this.page.getByRole('link', { name: 'Customer', exact: true }).click(); + await this.page.getByPlaceholder('Customer Name').clear(); + await this.page.getByPlaceholder('Customer Name').fill(`${patientName.givenName}`); + await delay(3000); + } + + async searchQuotation() { + await this.page.getByRole('link', { name: /selling/i }).click(); + await this.page.getByRole('link', { name: 'Quotation', exact: true }).click(); + await this.page.getByPlaceholder(/title/i).clear(); + await this.page.getByPlaceholder(/party/i).clear(); + await this.page.getByPlaceholder(/title/i).fill(`${patientName.givenName}`); + await delay(3000); + } + + async deleteQuotation() { + await this.page.goto(`${ERPNEXT_URL}/app/quotation`); + await this.searchQuotation(); + await this.page.getByRole('checkbox', { name: 'Select All' }).check(); + await delay(2500); + await this.page.getByRole('button', { name: 'Actions' }).click(); + await this.page.getByRole('link', { name: 'Delete' }).click(); + await this.page.getByRole('button', { name: 'Yes' }).click(); + await expect(this.page.getByText('No Quotation found')).toBeVisible(); + } + + async voidQuotation() { + await this.page.goto(`${ERPNEXT_URL}/app/quotation`); + await this.searchQuotation(); + await this.page.getByRole('checkbox', { name: 'Select All' }).check(); + await delay(2500); + await this.page.getByRole('button', { name: 'Actions' }).click(); + await this.page.getByRole('link', { name: 'Cancel' }).click(); + await this.page.getByRole('button', { name: 'Yes' }).click(); + } + + async deleteCustomer() { + await this.page.goto(`${ERPNEXT_URL}/app/customer`); + await this.searchCustomer(); + await this.page.getByRole('checkbox', { name: 'Select All' }).check(); + await delay(2500); + await this.page.getByRole('button', { name: 'Actions' }).click(); + await this.page.getByRole('link', { name: 'Delete' }).click(); + await this.page.getByRole('button', { name: 'Yes' }).click(); + await expect(this.page.getByText('No Customer found')).toBeVisible(); + } +} diff --git a/e2e/utils/functions/openmrs.ts b/e2e/utils/functions/openmrs.ts index 6f90019d..0ca9710f 100644 --- a/e2e/utils/functions/openmrs.ts +++ b/e2e/utils/functions/openmrs.ts @@ -35,16 +35,16 @@ export class OpenMRS { await this.page.getByRole('button', { name: 'Sign In' }).click(); } else { await this.page.locator('#username').fill(`${process.env.O3_USERNAME_ON_FOSS}`); - await this.page.waitForTimeout(1000); + await delay(1000); await this.page.getByRole('button', { name: 'Continue' }).click(); await this.page.locator('#password').fill(`${process.env.O3_PASSWORD_ON_FOSS}`); - await this.page.waitForTimeout(1000); + await delay(1000); await this.page.locator('button[type="submit"]').click(); } await this.page.locator('label').filter({ hasText: 'Inpatient Ward' }).locator('span').first().click(); await this.page.getByRole('button', { name: 'Confirm' }).click(); await delay(5000); - await this.expectAllButtonsToBePresent(); + await this.waitHomePageToLoad(); } async createPatient() { @@ -54,7 +54,7 @@ export class OpenMRS { updatedFirstName: `${(Math.random() + 1).toString(36).substring(2)}` } patientFullName = patientName.firstName + ' ' + patientName.givenName; - await this.expectAllButtonsToBePresent(); + await this.waitHomePageToLoad(); await this.page.getByRole('button', { name: 'Add Patient' }).click(); await expect(this.page.getByRole('button', { name: 'Register Patient' })).toBeEnabled(); await this.page.getByLabel('First Name').clear(); @@ -137,12 +137,12 @@ export class OpenMRS { } async endPatientVisit() { - await this.searchPatient(`${patientFullName}`) + await this.searchPatient(`${patientName.firstName + ' ' + patientName.givenName}`) await this.page.getByRole('button', { name: 'Actions', exact: true }).click(); await this.page.getByRole('menuitem', { name: 'End visit' }).click(); await this.page.getByRole('button', { name: 'danger End Visit' }).click(); await expect(this.page.getByText('Visit ended')).toBeVisible(); - await this.page.getByRole('button', { name: 'Close' }).click(); + await this.page.getByRole('button', { name: 'Close', exact: true }).click(); } async deletePatient() { @@ -153,7 +153,6 @@ export class OpenMRS { await this.page.getByRole('button', { name: 'Delete Patient', exact: true }).click(); const message = await this.page.locator('//*[@id="patientFormVoided"]').textContent(); await expect(message?.includes('This patient has been deleted')).toBeTruthy(); - await this.page.getByRole('link', { name: 'Log out' }).click(); } async addPatientCondition() { @@ -201,6 +200,18 @@ export class OpenMRS { await expect(appointmentStatus).toHaveText('Scheduled'); } + async createLabOrder() { + await this.page.getByLabel('Order basket', { exact: true }).click(); + await this.page.getByRole('button', { name: 'Add', exact: true }).nth(1).click(); + await this.page.getByPlaceholder('Search for a test type').fill('Urobilinogen'); + await delay(3000); + await this.page.getByRole('button', { name: 'Order form' }).click(); + await this.page.getByRole('button', { name: 'Save order' }).click(); + await this.page.getByRole('button', { name: 'Sign and close' }).click(); + await expect(this.page.getByText('Placed orders')).toBeVisible(); + await delay(3000); + } + async goToLabOrderForm() { await this.page.getByLabel('Clinical forms').click(); await delay(3000); @@ -245,12 +256,10 @@ export class OpenMRS { async createDrugOrder() { await this.page.getByLabel('Order basket', { exact: true }).click(); - await delay(3000); await this.page.getByRole('button', { name: 'Add', exact: true }).nth(0).click(); - await delay(2000); await this.page.getByPlaceholder('Search for a drug or orderset (e.g. "Aspirin")').fill('Aspirin 325mg'); await this.page.getByRole('button', { name: 'Order form' }).click(); - await delay(4000); + await delay(2000); await this.page.getByPlaceholder('Dose').fill('4'); await this.page.getByRole('button', { name: 'Open', exact: true }).nth(1).click(); await this.page.getByText('Intravenous', {exact: true}).click(); @@ -272,12 +281,10 @@ export class OpenMRS { async createDrugOrderWithFreeTextDosage() { await this.page.getByLabel('Order basket', { exact: true }).click(); - await delay(3000); await this.page.getByRole('button', { name: 'Add', exact: true }).nth(0).click(); - await delay(2000); await this.page.getByPlaceholder('Search for a drug or orderset (e.g. "Aspirin")').fill('Aspirin 325mg'); await this.page.getByRole('button', { name: 'Order form' }).click(); - await delay(4000); + await delay(2000); await this.page.locator('div').filter({ hasText: /^Off$/ }).locator('div').click(); await this.page.getByPlaceholder('Free text dosage').fill('Take up to three tablets per day'); await this.page.getByLabel('Duration', { exact: true }).fill('3'); @@ -296,13 +303,14 @@ export class OpenMRS { async editDrugOrder() { await this.page.getByRole('button', { name: 'Options', exact: true }).click(); await this.page.getByRole('menuitem', { name: 'Modify', exact: true }).click(); - await delay(4000); await this.page.getByPlaceholder('Dose').clear(); await this.page.getByPlaceholder('Dose').fill('8'); await this.page.getByPlaceholder('Frequency').click(); await this.page.getByText('Thrice daily').click(); await this.page.getByLabel('Duration', { exact: true }).clear(); await this.page.getByLabel('Duration', { exact: true }).fill('6'); + await this.page.getByLabel('Quantity to dispense').clear(); + await this.page.getByLabel('Quantity to dispense').fill('8'); await this.page.getByRole('button', { name: 'Save order' }).focus(); await this.page.getByRole('button', { name: 'Save order' }).dispatchEvent('click'); await expect(this.page.getByText('Sign and close')).toBeVisible(); @@ -394,10 +402,9 @@ export class OpenMRS { await this.page.getByRole('link', { name: 'Log out' }).click(); } - async expectAllButtonsToBePresent() { + async waitHomePageToLoad() { await expect(this.page.getByRole('button', { name: 'Search Patient' })).toBeEnabled(); await expect(this.page.getByRole('button', { name: 'Add Patient' })).toBeEnabled(); - await expect(this.page.getByRole('button', { name: 'Implementer Tools' })).toBeEnabled(); await expect(this.page.getByRole('button', { name: 'My Account' })).toBeEnabled(); await expect(this.page.getByRole('button', { name: 'App Menu' })).toBeEnabled(); } diff --git a/package.json b/package.json index fed2a293..e40b1c50 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ ], "scripts": { "e2e-tests-pro": "npx playwright test", - "e2e-tests-foss": "npx playwright test odoo-openmrs openmrs-senaite" + "e2e-tests-foss": "npx playwright test odoo-openmrs erpnext-openmrs openmrs-senaite" }, "publishConfig": { "registry": "https://nexus.mekomsolutions.net/repository/npm-public/"