Skip to content

Commit c638d9b

Browse files
authored
refactor: add Playwright PoC workspace and draft PR CI workflow (#5008)
1 parent 059d348 commit c638d9b

10 files changed

Lines changed: 531 additions & 89 deletions

File tree

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
name: Klicker automated testing with playwright
2+
on:
3+
pull_request:
4+
branches: ['v3', 'v3*']
5+
types: [opened, synchronize, reopened, ready_for_review]
6+
paths:
7+
- apps/**
8+
- packages/**
9+
- playwright/**
10+
- .github/workflows/playwright-testing.yml
11+
- .github/scripts/wait-for-services.sh
12+
- util/_create_hatchet_token_cypress.sh
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }}
16+
cancel-in-progress: true
17+
18+
env:
19+
REGISTRY: ghcr.io
20+
21+
jobs:
22+
playwright-run-draft:
23+
if: github.event_name == 'pull_request' && github.event.pull_request.draft == true
24+
runs-on: ubuntu-24.04
25+
timeout-minutes: 120
26+
services:
27+
postgres:
28+
image: postgres:15
29+
env:
30+
POSTGRES_USER: klicker-prod
31+
POSTGRES_PASSWORD: klicker
32+
POSTGRES_DB: klicker-prod
33+
options: >-
34+
--health-cmd pg_isready
35+
--health-interval 10s
36+
--health-timeout 5s
37+
--health-retries 5
38+
ports:
39+
- 5432:5432
40+
postgres_hatchet:
41+
image: postgres:15
42+
env:
43+
POSTGRES_USER: hatchet
44+
POSTGRES_PASSWORD: hatchet
45+
POSTGRES_DB: hatchet
46+
options: >-
47+
--health-cmd "pg_isready -U hatchet -d hatchet"
48+
--health-interval 5s
49+
--health-timeout 10s
50+
--health-retries 10
51+
--health-start-period 30s
52+
ports:
53+
- 5433:5432
54+
redis_cache:
55+
image: redis:7
56+
options: >-
57+
--health-cmd "redis-cli ping"
58+
--health-interval 10s
59+
--health-timeout 5s
60+
--health-retries 5
61+
ports:
62+
- 6380:6379
63+
redis_exec:
64+
image: redis:7
65+
options: >-
66+
--health-cmd "redis-cli ping"
67+
--health-interval 10s
68+
--health-timeout 5s
69+
--health-retries 5
70+
ports:
71+
- 6379:6379
72+
redis_assessment_exec:
73+
image: redis:7
74+
options: >-
75+
--health-cmd "redis-cli ping"
76+
--health-interval 10s
77+
--health-timeout 5s
78+
--health-retries 5
79+
ports:
80+
- 6381:6379
81+
hatchet:
82+
image: ghcr.io/hatchet-dev/hatchet/hatchet-lite:v0.73.1
83+
ports:
84+
- '8888:8888'
85+
- '7077:7077'
86+
env:
87+
DATABASE_URL: 'postgresql://hatchet:hatchet@postgres_hatchet:5432/hatchet?sslmode=disable'
88+
SERVER_AUTH_COOKIE_DOMAIN: localhost
89+
SERVER_AUTH_COOKIE_INSECURE: 't'
90+
SERVER_GRPC_BIND_ADDRESS: '0.0.0.0'
91+
SERVER_GRPC_INSECURE: 't'
92+
SERVER_GRPC_BROADCAST_ADDRESS: localhost:7077
93+
SERVER_INTERNAL_CLIENT_INTERNAL_GRPC_BROADCAST_ADDRESS: localhost:7077
94+
SERVER_GRPC_PORT: '7077'
95+
SERVER_URL: http://localhost:8888
96+
SERVER_AUTH_SET_EMAIL_VERIFIED: 't'
97+
SERVER_DEFAULT_ENGINE_VERSION: 'V1'
98+
SERVER_LOGGER_LEVEL: debug
99+
SERVER_LOGGER_FORMAT: console
100+
101+
steps:
102+
- run: echo "Running Playwright PoC tests for draft PR"
103+
104+
- name: Define node version
105+
uses: actions/setup-node@v3
106+
with:
107+
node-version: 20
108+
109+
- name: Check out repository code
110+
uses: actions/checkout@v3
111+
112+
- uses: pnpm/action-setup@v2
113+
with:
114+
version: 10.15.0
115+
run_install: false
116+
117+
- name: Get pnpm store directory
118+
shell: bash
119+
run: |
120+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
121+
122+
- name: Setup pnpm cache
123+
uses: actions/cache@v4
124+
with:
125+
path: |
126+
${{ env.STORE_PATH }}
127+
~/.cache/ms-playwright
128+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
129+
restore-keys: |
130+
${{ runner.os }}-pnpm-store-
131+
132+
- name: Cache turbo build artifacts
133+
uses: actions/cache@v4
134+
with:
135+
path: |
136+
.turbo
137+
key: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml', 'turbo.json') }}-${{ github.ref_name }}
138+
restore-keys: |
139+
${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml', 'turbo.json') }}-
140+
${{ runner.os }}-turbo-
141+
142+
- name: Install dependencies
143+
run: pnpm install
144+
145+
- name: Build all packages and apps
146+
run: pnpm run --filter @klicker-uzh/prisma build:test && pnpm run build:test
147+
148+
- name: Prepare .env files
149+
run: |
150+
./util/_create_hatchet_token_cypress.sh
151+
152+
- name: Apply migrations and reset database
153+
run: |
154+
cd packages/prisma
155+
pnpm run prisma:reset:raw -f
156+
env:
157+
DATABASE_URL: postgres://klicker-prod:klicker@localhost:5432/klicker-prod
158+
159+
- name: Seed test participants for Playwright
160+
run: pnpm --filter @klicker-uzh/prisma-data seed:test
161+
env:
162+
DATABASE_URL: postgres://klicker-prod:klicker@localhost:5432/klicker-prod
163+
164+
- name: Install Playwright browsers
165+
run: pnpm --filter @klicker-uzh/playwright exec playwright install --with-deps chromium firefox webkit
166+
167+
- name: Start services and wait for readiness
168+
run: |
169+
chmod +x .github/scripts/wait-for-services.sh
170+
.github/scripts/wait-for-services.sh
171+
env:
172+
APP_SECRET: abcd
173+
DATABASE_URL: postgres://klicker-prod:klicker@localhost:5432/klicker-prod
174+
NODE_ENV: production
175+
SERVICE_ENDPOINTS: 'http://127.0.0.1:3000/healthz http://127.0.0.1:3001 http://127.0.0.1:3002 http://127.0.0.1:3003 http://127.0.0.1:3010'
176+
TIMEOUT_SECONDS: '300'
177+
CHECK_INTERVAL: '5'
178+
REDIS_HOST: localhost
179+
REDIS_PASS: ''
180+
REDIS_PORT: 6379
181+
REDIS_ASSESSMENT_PORT: 6381
182+
REDIS_ASSESSMENT_PASS: ''
183+
REDIS_ASSESSMENT_HOST: localhost
184+
APP_ORIGIN_API: http://127.0.0.1:3000
185+
APP_ORIGIN_AUTH: http://127.0.0.1:3010
186+
APP_ORIGIN_LTI: http://127.0.0.1:3005
187+
APP_ORIGIN_PWA: http://127.0.0.1:3001
188+
APP_ORIGIN_MANAGE: http://127.0.0.1:3002
189+
APP_ORIGIN_CONTROL: http://127.0.0.1:3003
190+
APP_ORIGIN_ASSESSMENT_API: http://127.0.0.1:3000
191+
APP_ORIGIN_ASSESSMENT_PWA: http://127.0.0.1:3001
192+
193+
- name: Run Playwright tests
194+
run: pnpm --filter @klicker-uzh/playwright test:run
195+
env:
196+
PLAYWRIGHT_BASE_URL: http://127.0.0.1:3001
197+
PW_STUDENT_USERNAME: testuser1
198+
PW_STUDENT_PASSWORD: abcdabcd
199+
200+
- name: Upload Playwright artifacts
201+
if: always()
202+
uses: actions/upload-artifact@v4
203+
with:
204+
name: playwright-artifacts-draft
205+
path: |
206+
playwright/playwright-report
207+
playwright/test-results
208+
service.log
209+
if-no-files-found: ignore
210+
retention-days: 1

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"syncpack:mismatches:fix": "syncpack fix-mismatches",
9393
"syncpack:update": "syncpack update",
9494
"test:run": "turbo run test:run",
95+
"test:run:playwright": "pnpm --filter @klicker-uzh/playwright test:run",
9596
"test:watch": "run-p test dev:test"
9697
},
9798
"engines": {

playwright/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
playwright-report/
2+
test-results/
3+
blob-report/

playwright/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Playwright PoC
2+
3+
## Local run
4+
5+
1. Start test stack (same as Cypress flow): `pnpm run dev:test`
6+
2. Run Playwright tests: `pnpm --filter @klicker-uzh/playwright test:run`
7+
8+
## Useful commands
9+
10+
- UI mode: `pnpm --filter @klicker-uzh/playwright test:ui`
11+
- Headed Chromium only: `pnpm --filter @klicker-uzh/playwright test:headed`
12+
- Show report: `pnpm --filter @klicker-uzh/playwright show-report`

playwright/package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"private": true,
3+
"name": "@klicker-uzh/playwright",
4+
"devDependencies": {
5+
"@playwright/test": "~1.58.0",
6+
"@types/node": "^20.19.11",
7+
"typescript": "~5.6.3"
8+
},
9+
"scripts": {
10+
"show-report": "playwright show-report",
11+
"test": "playwright test",
12+
"test:headed": "playwright test --headed --project=chromium",
13+
"test:run": "playwright test",
14+
"test:ui": "playwright test --ui"
15+
},
16+
"engines": {
17+
"node": "=20"
18+
},
19+
"volta": {
20+
"extends": "../package.json"
21+
},
22+
"type": "module"
23+
}

playwright/playwright.config.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { defineConfig, devices } from '@playwright/test'
2+
3+
const isCI = !!process.env.CI
4+
const baseURL =
5+
process.env.PLAYWRIGHT_BASE_URL ??
6+
process.env.URL_STUDENT ??
7+
'http://127.0.0.1:3001'
8+
9+
export default defineConfig({
10+
testDir: './tests',
11+
testMatch: '**/*.spec.ts',
12+
fullyParallel: false,
13+
forbidOnly: isCI,
14+
retries: isCI ? 1 : 0,
15+
workers: isCI ? 1 : undefined,
16+
timeout: 60_000,
17+
expect: {
18+
timeout: 10_000,
19+
},
20+
reporter: isCI
21+
? [
22+
['list'],
23+
['html', { outputFolder: 'playwright-report', open: 'never' }],
24+
['junit', { outputFile: 'test-results/junit.xml' }],
25+
]
26+
: [
27+
['list'],
28+
['html', { outputFolder: 'playwright-report', open: 'never' }],
29+
],
30+
use: {
31+
baseURL,
32+
testIdAttribute: 'data-cy',
33+
trace: 'retain-on-failure',
34+
screenshot: 'only-on-failure',
35+
video: 'retain-on-failure',
36+
actionTimeout: 15_000,
37+
navigationTimeout: 30_000,
38+
ignoreHTTPSErrors: true,
39+
},
40+
projects: [
41+
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
42+
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
43+
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
44+
],
45+
})

playwright/tests/pwa-login.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { expect, test } from '@playwright/test'
2+
3+
const credentials = {
4+
username:
5+
process.env.PW_STUDENT_USERNAME ??
6+
process.env.STUDENT_USERNAME ??
7+
'testuser1',
8+
password:
9+
process.env.PW_STUDENT_PASSWORD ??
10+
process.env.STUDENT_PASSWORD ??
11+
'abcdabcd',
12+
}
13+
14+
test.describe('PWA student login smoke', () => {
15+
test('student can sign in and sign out', async ({ page }) => {
16+
await test.step('Open login page', async () => {
17+
await page.goto('/login')
18+
await expect(page.getByTestId('login-logo')).toBeVisible()
19+
})
20+
21+
await test.step('Submit credentials and verify login via UI', async () => {
22+
await page.getByTestId('username-field').fill(credentials.username)
23+
await page.getByTestId('password-field').fill(credentials.password)
24+
await page.getByTestId('submit-login').click()
25+
26+
await expect(page.getByTestId('homepage')).toBeVisible()
27+
})
28+
29+
await test.step('Logout returns to login page', async () => {
30+
await expect(page.getByTestId('header-avatar')).toBeVisible()
31+
await page.getByTestId('header-avatar').click()
32+
await expect(page.getByTestId('logout')).toBeVisible()
33+
await page.getByTestId('logout').click()
34+
await expect(page.getByTestId('login-logo')).toBeVisible()
35+
})
36+
})
37+
})

playwright/tsconfig.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"module": "NodeNext",
5+
"moduleResolution": "NodeNext",
6+
"types": ["node", "@playwright/test"],
7+
"lib": ["ES2022", "DOM"],
8+
"strict": true,
9+
"noEmit": true,
10+
"resolveJsonModule": true,
11+
"skipLibCheck": true
12+
},
13+
"include": ["playwright.config.ts", "tests/**/*.ts"]
14+
}

0 commit comments

Comments
 (0)