Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

O3-2413: (test) Setup playwright for automated tests in Fast Data Entry App #88

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: E2E Tests

on:
push:
branches:
- main
pull_request:
branches:
- main

env:
TURBO_API: 'http://127.0.0.1:9080'
TURBO_TOKEN: 'turbo-token'
TURBO_TEAM: ${{ github.repository_owner }}

jobs:
run_e2e_tests:
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout repo
uses: actions/checkout@v4

- name: 📝 Copy test environment variables
run: cp example.env .env

- name: 🛠️ Setup node
uses: actions/setup-node@v4
with:
node-version: 18

- name: 💾 Cache dependencies
id: cache-dependencies
uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}

- name: 📦 Install dependencies
if: steps.cache-dependencies.outputs.cache-hit != 'true'
run: yarn install --immutable

- name: 🎭 Get installed Playwright version
id: playwright-version
run: echo "PLAYWRIGHT_VERSION=$(grep '@playwright/test@' yarn.lock | sed -n 's/.*npm:\([^":]*\).*/\1/p' | head -n 1)" >> $GITHUB_ENV

- name: 💾 Cache Playwright binaries
id: playwright-cache
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}

- name: 🎭 Install Playwright Browsers
run: npx playwright install chromium --with-deps
if: steps.playwright-cache.outputs.cache-hit != 'true'

- name: 🚀 Setup local cache server for Turborepo
uses: felixmosh/turborepo-gh-artifacts@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
server-token: ${{ env.TURBO_TOKEN }}

- name: 🏗️ Build apps
run: yarn turbo run build --color --concurrency=5

- name: 🚀 Run dev server
run: bash e2e/support/github/run-e2e-docker-env.sh

- name: ⏳ Wait for OpenMRS instance to start
run: while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' http://localhost:8080/openmrs/login.htm)" != "200" ]]; do sleep 10; done

- name: 🧪 Run E2E tests
run: yarn test-e2e

- name: 🛑 Stop dev server
if: '!cancelled()'
run: docker stop $(docker ps -a -q)

- name: 📤 Upload report
uses: actions/upload-artifact@v4
if: '!cancelled()'
with:
name: playwright-report
path: playwright-report/
retention-days: 30
overwrite: true

- name: 📝 Capture Server Logs
if: always()
uses: jwalton/gh-docker-logs@v2
with:
dest: './logs'

- name: 📤 Upload Logs as Artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: server-logs
path: './logs'
retention-days: 2
overwrite: true

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,7 @@ dist/
!.yarn/versions

.turbo
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
33 changes: 33 additions & 0 deletions e2e/core/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

import { request } from '@playwright/test';
import * as dotenv from 'dotenv';

dotenv.config();

/**
* This configuration is to reuse the signed-in state in the tests
* by log in only once using the API and then skip the log in step for all the tests.
*
* https://playwright.dev/docs/auth#reuse-signed-in-state
*/

async function globalSetup() {
const requestContext = await request.newContext();
const token = Buffer.from(`${process.env.E2E_USER_ADMIN_USERNAME}:${process.env.E2E_USER_ADMIN_PASSWORD}`).toString(
'base64',
);
await requestContext.post(`${process.env.E2E_BASE_URL}/ws/rest/v1/session`, {
data: {
sessionLocation: process.env.E2E_LOGIN_DEFAULT_LOCATION_UUID,
locale: 'en',
},
headers: {
Accept: 'application/json',
Authorization: `Basic ${token}`,
},
});
await requestContext.storageState({ path: 'e2e/storageState.json' });
await requestContext.dispose();
}

export default globalSetup;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 change: 1 addition & 0 deletions e2e/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './test';
20 changes: 20 additions & 0 deletions e2e/core/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { APIRequestContext, Page, test as base } from '@playwright/test';
import { api } from '../fixtures';

// 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 interface CustomTestFixtures {
loginAsAdmin: Page;
}

export interface CustomWorkerFixtures {
api: APIRequestContext;
}

export const test = base.extend<CustomTestFixtures, CustomWorkerFixtures>({
api: [api, { scope: 'worker' }],
});
26 changes: 26 additions & 0 deletions e2e/fixtures/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { APIRequestContext, PlaywrightWorkerArgs, WorkerFixture } from '@playwright/test';

/**
* A fixture which initializes an [`APIRequestContext`](https://playwright.dev/docs/api/class-apirequestcontext)
* that is bound to the configured OpenMRS API server. The context is automatically authenticated
* using the configured admin account.
*
* Use the request context like this:
* ```ts
* test('your test', async ({ api }) => {
* const res = await api.get('patient/1234');
* await expect(res.ok()).toBeTruthy();
* });
* ```
*/
export const api: WorkerFixture<APIRequestContext, PlaywrightWorkerArgs> = async ({ playwright }, use) => {
const ctx = await playwright.request.newContext({
baseURL: `${process.env.E2E_BASE_URL}/ws/rest/v1/`,
httpCredentials: {
username: process.env.E2E_USER_ADMIN_USERNAME,
password: process.env.E2E_USER_ADMIN_PASSWORD,
},
});

await use(ctx);
};
1 change: 1 addition & 0 deletions e2e/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './api';
Empty file added e2e/pages/index.ts
Empty file.
56 changes: 56 additions & 0 deletions e2e/support/bamboo/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# This docker compose file is used to create a dockerized environment when running E2E tests on Bamboo.
version: '3.7'

services:
playwright:
build:
context: .
dockerfile: playwright.Dockerfile
args:
USER_ID: ${USER_ID}
GROUP_ID: ${GROUP_ID}
container_name: patient-chart-e2e-tests-container
working_dir: /app
command: sh /app/e2e/support/bamboo/e2e-test-runner.sh
volumes:
- ../../../:/app
networks:
- test

gateway:
image: openmrs/openmrs-reference-application-3-gateway:${TAG:-nightly}
depends_on:
- frontend
- backend
ports:
- '80:80'
networks:
- test

frontend:
image: openmrs/openmrs-reference-application-3-frontend:${TAG:-nightly}
environment:
SPA_PATH: /openmrs/spa
API_URL: /openmrs
SPA_CONFIG_URLS:
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost/']
timeout: 5s
depends_on:
- backend
networks:
- test

backend:
image: openmrs/openmrs-reference-application-3-backend:nightly-with-data
depends_on:
- db
networks:
- test
db:
image: openmrs/openmrs-reference-application-3-db:nightly-with-data
networks:
- test

networks:
test:
13 changes: 13 additions & 0 deletions e2e/support/bamboo/e2e-test-runner.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

export E2E_BASE_URL=http://gateway/openmrs
export CI=true

while [ "$(curl -s -o /dev/null -w ''%{http_code}'' http://gateway/openmrs/login.htm)" != "200" ]; do
echo "Waiting for the backend to be up..."
sleep 10
done

cp example.env .env

yarn test-e2e
13 changes: 13 additions & 0 deletions e2e/support/bamboo/playwright.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# The image version should match the Playwright version specified in the package.json file
FROM mcr.microsoft.com/playwright:v1.48.2-jammy

ARG USER_ID
ARG GROUP_ID

RUN if ! getent group $GROUP_ID > /dev/null; then \
groupadd -g $GROUP_ID myusergroup; \
fi

RUN useradd -u $USER_ID -g $GROUP_ID -m playwrightuser

USER playwrightuser
34 changes: 34 additions & 0 deletions e2e/support/github/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# syntax=docker/dockerfile:1.3
FROM --platform=$BUILDPLATFORM node:18-alpine as dev

ARG APP_SHELL_VERSION=next

RUN mkdir -p /app
WORKDIR /app

COPY . .

RUN npm_config_legacy_peer_deps=true npm install -g openmrs@${APP_SHELL_VERSION:-next}
ARG CACHE_BUST
RUN npm_config_legacy_peer_deps=true openmrs assemble --manifest --mode config --config spa-assemble-config.json --target ./spa

FROM --platform=$BUILDPLATFORM openmrs/openmrs-reference-application-3-frontend:nightly as frontend
FROM nginx:1.23-alpine

RUN apk update && \
apk upgrade && \
# add more utils for sponge to support our startup script
apk add --no-cache moreutils

# clear any default files installed by nginx
RUN rm -rf /usr/share/nginx/html/*

COPY --from=frontend /etc/nginx/nginx.conf /etc/nginx/nginx.conf
# this assumes that NOTHING in the framework is in a subdirectory
COPY --from=frontend /usr/share/nginx/html/* /usr/share/nginx/html/
COPY --from=frontend /usr/local/bin/startup.sh /usr/local/bin/startup.sh
RUN chmod +x /usr/local/bin/startup.sh

COPY --from=dev /app/spa/ /usr/share/nginx/html/

CMD ["/usr/local/bin/startup.sh"]
24 changes: 24 additions & 0 deletions e2e/support/github/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# This docker compose file is used to create a backend environment for the e2e.yml workflow.
version: '3.7'

services:
gateway:
image: openmrs/openmrs-reference-application-3-gateway:${TAG:-nightly}
ports:
- '8080:80'

frontend:
build:
context: .
environment:
SPA_PATH: /openmrs/spa
API_URL: /openmrs

backend:
image: openmrs/openmrs-reference-application-3-backend:nightly-with-data
depends_on:
- db

# MariaDB
db:
image: openmrs/openmrs-reference-application-3-db:nightly-with-data
49 changes: 49 additions & 0 deletions e2e/support/github/run-e2e-docker-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
!/usr/bin/env bash -eu

# get the dir containing the script
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# create a temporary working directory
working_dir=$(mktemp -d "${TMPDIR:-/tmp/}openmrs-e2e-frontends.XXXXXXXXXX")
# get a list of all the apps in this workspace
apps=$(yarn workspaces list --json | jq -r 'if ((.location == ".") or (.location | test("-app") | not)) then halt else .name end')
# this array will hold all of the packed app names
app_names=()

echo "Creating packed archives of apps..."
# for each app
for app in $apps
do
# @openmrs/esm-whatever -> _openmrs_esm_whatever
app_name=$(echo "$app" | tr '[:punct:]' '_');
# add to our array
app_names+=("$app_name.tgz");
# run yarn pack for our app and add it to the working directory
yarn workspace "$app" pack -o "$working_dir/$app_name.tgz" >/dev/null;
done;
echo "Created packed app archives"

echo "Creating dynamic spa-assemble-config.json..."
# dynamically assemble our list of frontend modules, prepending the login app and
# primary navigation apps; apps will all be in the /app directory of the Docker
# container
jq -n \
--arg apps "$apps" \
--arg app_names "$(echo ${app_names[@]})" \
'{"@openmrs/esm-primary-navigation-app": "next", "@openmrs/esm-home-app": "next"} + (
($apps | split("\n")) as $apps | ($app_names | split(" ") | map("/app/" + .)) as $app_files
| [$apps, $app_files]
| transpose
| map({"key": .[0], "value": .[1]})
| from_entries
)' | jq '{"frontendModules": .}' > "$working_dir/spa-assemble-config.json"
echo "Created dynamic spa-assemble-config.json"

echo "Copying Docker configuration..."
cp "$script_dir/Dockerfile" "$working_dir/Dockerfile"
cp "$script_dir/docker-compose.yml" "$working_dir/docker-compose.yml"

cd $working_dir
echo "Starting Docker containers..."
# CACHE_BUST to ensure the assemble step is always run
docker compose build --build-arg CACHE_BUST=$(date +%s) frontend
docker compose up -d
Loading
Loading