diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b9f205ed4..68c173baf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,7 +2,7 @@ - [ ] This PR has a title that briefly describes the work done including the ticket number. If there is a ticket, make sure your PR title includes a [conventional commit](https://o3-dev.docs.openmrs.org/#/getting_started/contributing?id=your-pr-title-should-indicate-the-type-of-change-it-is) label. See existing PR titles for inspiration. #### For changes to apps -- [ ] My work conforms to the [**OpenMRS 3.0 Styleguide**](https://om.rs/styleguide) and [**design documentation**](https://zeroheight.com/23a080e38/p/880723-introduction). +- [ ] My work conforms to the [**OpenMRS 3.0 Styleguide**](https://om.rs/styleguide) and **design documentation**. #### If applicable - [ ] My work includes tests or is validated by existing tests. diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 000000000..8f0c8461c --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,107 @@ +name: E2E Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + testOnPR: + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Copy test environment variables + run: | + cp example.env .env + sed -i 's/8080/8180/g' .env + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Cache dependencies + id: cache + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + + - name: Install dependencies + run: yarn setup + + - name: Install Playwright Browsers + run: yarn playwright install chromium --with-deps + + - name: Run dev server + run: yarn run:omrs develop --sources packages/apps/esm-*-app --port 8180 & # Refer to O3-1994 + + - name: Run E2E tests + run: yarn playwright test + + - name: Upload Report + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + + testOnPush: + if: ${{ github.event_name == 'push' }} + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Copy test environment variables + run: | + cp example.env .env + sed -i 's/8080/8180/g' .env + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Cache dependencies + id: cache + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + + - name: Install dependencies + run: yarn setup + + - name: Install Playwright Browsers + run: yarn playwright install chromium --with-deps + + - name: Run db and web containers + run: | + cd e2e/support + docker-compose up -d + + - name: Wait for OpenMRS instance to start + run: while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' http://localhost:9000/openmrs/login.htm)" != "200" ]]; do sleep 10; done + + - name: Run dev server + run: yarn run:omrs develop --sources packages/apps/esm-login-app --backend "http://localhost:9000" --port 8180 & # Refer to O3-1994 + + - name: Run E2E tests + run: yarn playwright test + + - name: Upload report + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index b88058c3b..684aaa528 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,9 @@ package-lock.json !.yarn/releases !.yarn/sdks !.yarn/versions + +# Playwright and e2e tests +/test-results/ +/playwright-report/ +/playwright/.cache/ +e2e/storageState.json diff --git a/README.md b/README.md index 902c41d6e..11f6626c0 100644 --- a/README.md +++ b/README.md @@ -184,5 +184,5 @@ will cause GitHub Actions to publish the packages, completing the release proces ## Design Patterns -For documentation about our design patterns, please visit our [design system](https://zeroheight.com/23a080e38/p/880723--introduction) documentation website. +For documentation about our design patterns, please visit our design system documentation website. diff --git a/docs/main/upgrade_3_to_4.md b/docs/main/upgrade_3_to_4.md index 8d89b35b6..adebd2575 100644 --- a/docs/main/upgrade_3_to_4.md +++ b/docs/main/upgrade_3_to_4.md @@ -62,7 +62,7 @@ For a complete look at what needs to be upgraded, please read the relevant docum - [React Router 5 to 6](https://reactrouter.com/en/main/upgrading/v5) - [Carbon 10 to 11](https://github.com/carbon-design-system/carbon/blob/main/docs/migration/v11.md) - [Jest 26 to 27](https://jestjs.io/blog/2021/05/25/jest-27) -- [Jest 27 to 28](https://jestjs.io/docs/28.x/migration-guide) +- [Jest 27 to 28](https://jestjs.io/blog/2022/04/25/jest-28) - [User Event 12 to 13](https://github.com/testing-library/user-event/releases/tag/v12.0.0) - [User Event 13 to 14](https://github.com/testing-library/user-event/releases/tag/v14.0.0) diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 000000000..0416ad8c9 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,115 @@ +# E2E Tests + +This directory contains an E2E test suite using the [Playwright](https://playwright.dev) +framework. + +## Getting Started + +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: + +```sh +yarn start --sources 'packages/esm-*-app/' +``` +Then, in a separate terminal, run: + +```sh +yarn test-e2e --headed +``` + +By default, the test suite will run against the `http://localhost:8080`. +You can override this by exporting `E2E_BASE_URL` environment variables beforehand: + +```sh +# Ex: Set the server URL to dev3: +export E2E_BASE_URL=https://dev3.openmrs.org/openmrs + +# Run all e2e tests: +yarn test-e2e --headed +``` +To run a specific test by title: +```sh +yarn test-e2e --headed -g "title of the test" +``` +Check [this documentation](https://playwright.dev/docs/running-tests#command-line) for more running options. + +It is also highly recommended to install the companion VS Code extension: +(https://playwright.dev/docs/getting-started-vscode) + + +## Writing New Tests + +In general, it is recommended to read through the official [Playwright docs](https://playwright.dev/docs/intro) +before writing new test cases. The project uses the official Playwright test runner and, +generally, follows a very simple project structure: + +``` +e2e +|__ commands +| ^ Contains "commands" (simple reusable functions) that can be used in test cases/specs, +| e.g. generate a random patient. +|__ core +| ^ Contains code related to the test runner itself, e.g. setting up the custom fixtures. +| You probably need to touch this infrequently. +|__ fixtures +| ^ Contains fixtures (https://playwright.dev/docs/test-fixtures) which are used +| to run reusable setup/teardown tasks +|__ pages +| ^ Contains page object model classes for interacting with the frontend. +| See https://playwright.dev/docs/test-pom for details. +|__ specs +| ^ Contains the actual test cases/specs. New tests should be placed in this folder. +|__ support + ^ Contains support files that requires to run e2e tests, e.g. docker compose files. +``` + +When you want to write a new test case, start by creating a new spec in `./specs`. +Depending on what you want to achieve, you might want to create new fixtures and/or +page object models. To see examples, have a look at the existing code to see how these +different concepts play together. + +## Open reports from GitHub Actions / Bamboo + +To download the report from the GitHub action/Bamboo plan, follow these steps: + +1. Go to the artifact section of the action/plan and locate the report file. +2. Download the report file and unzip it using a tool of your choice. +3. Open the index.html file in a web browser to view the report. + +The report will show you a full summary of your tests, including information on which +tests passed, failed, were skipped, or were flaky. You can filter the report by browser +and explore the details of individual tests, including any errors or failures, video +recordings, and the steps involved in each test. Simply click on a test to view its details. + +## Debugging Tests + +Refer to [this documentation](https://playwright.dev/docs/debug) on how to debug a test. + +## Configuration + +This is very much underdeveloped/WIP. At the moment, there exists a (git-shared) `.env` +file which can be used for configuring certain test attributes. This is most likely +about to change in the future. Stay tuned for updates! + + +## Github Action integration +The e2e.yml workflow is made up of two jobs: one for running on pull requests (PRs) and +one for running on commits. + +1. When running on PRs, the workflow will start the dev server, use dev3.openmrs.org as the backend, +and run tests only on chromium. This is done in order to quickly provide feedback to the developer. +The tests are designed to generate their own data and clean up after themselves once they are finished. +This ensures that the tests will have minimum effect from changes made to dev3 by other developers. +In the future, we plan to use a docker container to run the tests in an isolated environment once we +figure out a way to spin up the container within a small amount of time. +2. When running on commits, the workflow will spin up a docker container and run the dev server against +it in order to provide a known and isolated environment. In addition, tests will be run on multiple +browsers (chromium, firefox, and WebKit) to ensure compatibility. + +## Troubleshooting tips + +On MacOS, you might run into the following error: +```browserType.launch: Executable doesn't exist at /Users//Library/Caches/ms-playwright/chromium-1015/chrome-mac/Chromium.app/Contents/MacOS/Chromium``` +In order to fix this, you can attempt to force the browser reinstallation by running: +```PLAYWRIGHT_BROWSERS_PATH=/Users/$USER/Library/Caches/ms-playwright npx playwright install``` diff --git a/e2e/core/index.ts b/e2e/core/index.ts new file mode 100644 index 000000000..481fabade --- /dev/null +++ b/e2e/core/index.ts @@ -0,0 +1 @@ +export * from "./test"; diff --git a/e2e/core/test.ts b/e2e/core/test.ts new file mode 100644 index 000000000..2b2f6b3b5 --- /dev/null +++ b/e2e/core/test.ts @@ -0,0 +1,9 @@ +import { test as base } from "@playwright/test"; + +// This file sets up our custom test harness using the custom fixtures. +// See https://playwright.dev/docs/test-fixtures#creating-a-fixture for details. +// If a spec intends to use one of the custom fixtures, the special `test` function +// exported from this file must be used instead of the default `test` function +// provided by playwright. + +export const test = base.extend({}); diff --git a/e2e/pages/index.ts b/e2e/pages/index.ts new file mode 100644 index 000000000..5277d15b5 --- /dev/null +++ b/e2e/pages/index.ts @@ -0,0 +1 @@ +export * from "./login-page"; diff --git a/e2e/pages/login-page.ts b/e2e/pages/login-page.ts new file mode 100644 index 000000000..06605b0d2 --- /dev/null +++ b/e2e/pages/login-page.ts @@ -0,0 +1,9 @@ +import { Page } from "@playwright/test"; + +export class LoginPage { + constructor(readonly page: Page) {} + + async goto() { + await this.page.goto(`${process.env.E2E_BASE_URL}/spa/login`); + } +} diff --git a/e2e/specs/login.spec.ts b/e2e/specs/login.spec.ts new file mode 100644 index 000000000..bffd74d2b --- /dev/null +++ b/e2e/specs/login.spec.ts @@ -0,0 +1,42 @@ +import { test } from "../core"; +import { expect } from "@playwright/test"; +import { LoginPage } from "../pages"; + +test("Should login as Admin", async ({ page }) => { + const loginPage = new LoginPage(page); + + await test.step("When I goto the login page", async () => { + await loginPage.goto(); + }); + + await test.step("And I enter the username", async () => { + await page + .locator("#username") + .fill(`${process.env.E2E_USER_ADMIN_USERNAME}`); + await page.getByText("Continue").click(); + }); + + await test.step("And I enter the password", async () => { + await page + .locator("#password") + .fill(`${process.env.E2E_USER_ADMIN_PASSWORD}`); + }); + + await test.step("And I click login buttion", async () => { + await page.getByText("Log in").click(); + }); + + await test.step("And I choose the location", async () => { + await page.getByText("Outpatient clinic").click(); + await page.getByText("Confirm").click(); + }); + + await test.step("Then I should be logged in", async () => { + await expect(page).toHaveURL(`${process.env.E2E_BASE_URL}/spa/home`); + }); + + await test.step("Then I logged out", async () => { + await page.getByRole("button", { name: "Users" }).click(); + await page.getByText("Logout").click(); + }); +}); diff --git a/e2e/support/docker-compose.yml b/e2e/support/docker-compose.yml new file mode 100644 index 000000000..23c3b5a43 --- /dev/null +++ b/e2e/support/docker-compose.yml @@ -0,0 +1,39 @@ +# This docker compose file is used to create a backend environment for the e2e.yml workflow. +version: "3.7" + +services: + backend: + image: openmrs/openmrs-reference-application-3-backend:${TAG:-nightly} + depends_on: + - db + environment: + OMRS_CONFIG_MODULE_WEB_ADMIN: "true" + OMRS_CONFIG_AUTO_UPDATE_DATABASE: "true" + OMRS_CONFIG_CREATE_TABLES: "true" + OMRS_CONFIG_CONNECTION_SERVER: db + OMRS_CONFIG_CONNECTION_DATABASE: openmrs + OMRS_CONFIG_CONNECTION_USERNAME: ${OPENMRS_DB_USER:-openmrs} + OMRS_CONFIG_CONNECTION_PASSWORD: ${OPENMRS_DB_PASSWORD:-openmrs} + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/openmrs"] + timeout: 5s + volumes: + - openmrs-data:/openmrs/data + ports: + - 9000:8080 + + # MariaDB + db: + image: mariadb:10.8.2 + command: "mysqld --character-set-server=utf8 --collation-server=utf8_general_ci" + environment: + MYSQL_DATABASE: openmrs + MYSQL_USER: ${OPENMRS_DB_USER:-openmrs} + MYSQL_PASSWORD: ${OPENMRS_DB_PASSWORD:-openmrs} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-openmrs} + volumes: + - db-data:/var/lib/mysql + +volumes: + openmrs-data: ~ + db-data: ~ diff --git a/example.env b/example.env new file mode 100644 index 000000000..e1bb001ab --- /dev/null +++ b/example.env @@ -0,0 +1,6 @@ +# This is an example environment file for configuring dynamic values. +E2E_BASE_URL=http://localhost:8080/openmrs +E2E_USER_ADMIN_USERNAME=admin +E2E_USER_ADMIN_PASSWORD=Admin123 +E2E_LOGIN_DEFAULT_LOCATION_UUID=44c3efb0-2583-4c80-a79e-1f756a03c0a1 +# The above location UUID is for the "Outpatient Clinic" location in the reference application diff --git a/package.json b/package.json index 08409715f..7ca13ede4 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,16 @@ "verify": "turbo run lint && turbo run test && turbo run typescript", "prettier": "prettier \"packages/**/src/**/*\" --write", "postinstall": "husky install", - "extract-translations": "lerna run extract-translations -- --config ../../../tools/i18next-parser.config.js" + "extract-translations": "lerna run extract-translations -- --config ../../../tools/i18next-parser.config.js", + "test": "cross-env TZ=UTC jest --config jest.config.json --verbose false --passWithNoTests", + "test-watch": "cross-env TZ=UTC jest --watch --config jest.config.json", + "test-e2e": "playwright test", + "coverage": "yarn test --coverage" }, "devDependencies": { "@babel/highlight": "^7.18.6", "@jest/types": "^28.1.3", + "@playwright/test": "^1.30.0", "@swc/core": "^1.3.58", "@swc/jest": "^0.2.22", "@testing-library/dom": "^8.16.0", @@ -38,6 +43,7 @@ "autoprefixer": "^10.4.2", "cross-env": "7.0.2", "docsify": "^4.12.2", + "dotenv": "^16.0.3", "eslint": "^7.10.0", "eslint-config-prettier": "^6.11.0", "eslint-config-ts-react-important-stuff": "^3.0.0", diff --git a/packages/framework/esm-styleguide/README.md b/packages/framework/esm-styleguide/README.md index 3b4eb647e..192344062 100644 --- a/packages/framework/esm-styleguide/README.md +++ b/packages/framework/esm-styleguide/README.md @@ -6,4 +6,4 @@ All our frontend UI development is based on [Carbon Design System](https://www.c Please see the Developer Documentation page [Using Carbon and the Styleguide](https://openmrs.github.io/openmrs-esm-core/#/main/carbon). -Check out the [full, detailed styleguide in Zeplin here](https://app.zeplin.io/styleguide/60d5ecb9efdcd81256117e7d/components) (let us know if you require an invitation) and our [design documentation](https://zeroheight.com/23a080e38/p/880723--introduction). +Check out the [full, detailed styleguide in Zeplin here](https://app.zeplin.io/styleguide/60d5ecb9efdcd81256117e7d/components) (let us know if you require an invitation) and our design documentation. diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..62569a2cb --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,33 @@ +import { devices, PlaywrightTestConfig } from "@playwright/test"; +import * as dotenv from "dotenv"; +dotenv.config(); + +// See https://playwright.dev/docs/test-configuration. +const config: PlaywrightTestConfig = { + testDir: "./e2e/specs", + timeout: 3 * 60 * 1000, + expect: { + timeout: 40 * 1000, + }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: 0, + reporter: process.env.CI + ? [["junit", { outputFile: "results.xml" }], ["html"]] + : [["html"]], + use: { + baseURL: `${process.env.E2E_BASE_URL}/spa/`, + trace: "retain-on-failure", + video: "retain-on-failure", + }, + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + }, + }, + ], +}; + +export default config; diff --git a/yarn.lock b/yarn.lock index 0d1e3f40d..700a69071 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3577,6 +3577,7 @@ __metadata: dependencies: "@babel/highlight": ^7.18.6 "@jest/types": ^28.1.3 + "@playwright/test": ^1.30.0 "@swc/core": ^1.3.58 "@swc/jest": ^0.2.22 "@testing-library/dom": ^8.16.0 @@ -3590,6 +3591,7 @@ __metadata: autoprefixer: ^10.4.2 cross-env: 7.0.2 docsify: ^4.12.2 + dotenv: ^16.0.3 eslint: ^7.10.0 eslint-config-prettier: ^6.11.0 eslint-config-ts-react-important-stuff: ^3.0.0 @@ -3973,6 +3975,22 @@ __metadata: languageName: unknown linkType: soft +"@playwright/test@npm:^1.30.0": + version: 1.36.2 + resolution: "@playwright/test@npm:1.36.2" + dependencies: + "@types/node": "*" + fsevents: 2.3.2 + playwright-core: 1.36.2 + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 659304e0bbbafb2fa36395fbd8bd2c5db2b7791bbb55fa62409946ec7ec726cf8fff89f2b8a1a74fe831bf50a8780a37a5322a1251a6f7db2a9220a57ac408f0 + languageName: node + linkType: hard + "@pnpm/config.env-replace@npm:^1.0.0": version: 1.0.0 resolution: "@pnpm/config.env-replace@npm:1.0.0" @@ -8765,6 +8783,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.0.3": + version: 16.3.1 + resolution: "dotenv@npm:16.3.1" + checksum: 15d75e7279018f4bafd0ee9706593dd14455ddb71b3bcba9c52574460b7ccaf67d5cf8b2c08a5af1a9da6db36c956a04a1192b101ee102a3e0cf8817bbcf3dfd + languageName: node + linkType: hard + "downshift@npm:5.2.1": version: 5.2.1 resolution: "downshift@npm:5.2.1" @@ -9945,7 +9970,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": +"fsevents@npm:2.3.2, fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -9955,7 +9980,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": +"fsevents@patch:fsevents@2.3.2#~builtin, fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" dependencies: @@ -14965,6 +14990,15 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.36.2": + version: 1.36.2 + resolution: "playwright-core@npm:1.36.2" + bin: + playwright-core: cli.js + checksum: 2193ce802ef93c28b9b5e11a0b1d7b60778c686015659978d1cbf0eb9cda2cdc85ec5575b887c1346e9d161cc2805bf27638d76a2f7f857dffeae968e6ceffcd + languageName: node + linkType: hard + "pngjs@npm:^3.0.0, pngjs@npm:^3.3.3": version: 3.4.0 resolution: "pngjs@npm:3.4.0"