👷 Improve Playwright CI speed: sharding (paralel runs), run in Docker to use cache, use env vars (#1405)
This commit is contained in:

committed by
GitHub

parent
d3d370cad0
commit
e684f3c8d6
86
.github/workflows/playwright.yml
vendored
86
.github/workflows/playwright.yml
vendored
@@ -16,10 +16,36 @@ on:
|
|||||||
default: 'false'
|
default: 'false'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
changes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Set job outputs to values from filter step
|
||||||
|
outputs:
|
||||||
|
changed: ${{ steps.filter.outputs.changed }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
# For pull requests it's not necessary to checkout the code but for the main branch it is
|
||||||
|
- uses: dorny/paths-filter@v3
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
changed:
|
||||||
|
- backend/**
|
||||||
|
- frontend/**
|
||||||
|
- .env
|
||||||
|
- docker-compose*.yml
|
||||||
|
- .github/workflows/playwright.yml
|
||||||
|
|
||||||
test:
|
test-playwright:
|
||||||
|
needs:
|
||||||
|
- changes
|
||||||
|
if: ${{ needs.changes.outputs.changed == 'true' }}
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
shardIndex: [1, 2, 3, 4]
|
||||||
|
shardTotal: [4]
|
||||||
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
@@ -33,35 +59,61 @@ jobs:
|
|||||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
||||||
with:
|
with:
|
||||||
limit-access-to-actor: true
|
limit-access-to-actor: true
|
||||||
|
- run: docker compose build
|
||||||
|
- run: docker compose down -v --remove-orphans
|
||||||
|
- name: Run Playwright tests
|
||||||
|
run: docker compose run --rm playwright npx playwright test --fail-on-flaky-tests --trace=retain-on-failure --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||||
|
- run: docker compose down -v --remove-orphans
|
||||||
|
- name: Upload blob report to GitHub Actions Artifacts
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: blob-report-${{ matrix.shardIndex }}
|
||||||
|
path: frontend/blob-report
|
||||||
|
include-hidden-files: true
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
merge-playwright-reports:
|
||||||
|
needs:
|
||||||
|
- test-playwright
|
||||||
|
- changes
|
||||||
|
# Merge reports after playwright-tests, even if some shards have failed
|
||||||
|
if: ${{ !cancelled() && needs.changes.outputs.changed == 'true' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
- name: Install Playwright Browsers
|
- name: Download blob reports from GitHub Actions Artifacts
|
||||||
run: npx playwright install --with-deps
|
uses: actions/download-artifact@v4
|
||||||
working-directory: frontend
|
|
||||||
- run: docker compose build
|
|
||||||
- run: docker compose down -v --remove-orphans
|
|
||||||
- run: docker compose up -d --wait backend mailcatcher
|
|
||||||
- name: Run Playwright tests
|
|
||||||
run: npx playwright test --fail-on-flaky-tests --trace=retain-on-failure
|
|
||||||
working-directory: frontend
|
|
||||||
- run: docker compose down -v --remove-orphans
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: always()
|
|
||||||
with:
|
with:
|
||||||
name: playwright-report
|
path: frontend/all-blob-reports
|
||||||
path: frontend/playwright-report/
|
pattern: blob-report-*
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Merge into HTML Report
|
||||||
|
run: npx playwright merge-reports --reporter html ./all-blob-reports
|
||||||
|
working-directory: frontend
|
||||||
|
- name: Upload HTML report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: html-report--attempt-${{ github.run_attempt }}
|
||||||
|
path: frontend/playwright-report
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
|
|
||||||
# https://github.com/marketplace/actions/alls-green#why
|
# https://github.com/marketplace/actions/alls-green#why
|
||||||
e2e-alls-green: # This job does nothing and is only used for the branch protection
|
alls-green-playwright: # This job does nothing and is only used for the branch protection
|
||||||
if: always()
|
if: always()
|
||||||
needs:
|
needs:
|
||||||
- test
|
- test-playwright
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Decide whether the needed jobs succeeded or failed
|
- name: Decide whether the needed jobs succeeded or failed
|
||||||
uses: re-actors/alls-green@release/v1
|
uses: re-actors/alls-green@release/v1
|
||||||
with:
|
with:
|
||||||
jobs: ${{ toJSON(needs) }}
|
jobs: ${{ toJSON(needs) }}
|
||||||
|
allowed-skips: test-playwright
|
||||||
|
2
.github/workflows/test-docker-compose.yml
vendored
2
.github/workflows/test-docker-compose.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- run: docker compose build
|
- run: docker compose build
|
||||||
- run: docker compose down -v --remove-orphans
|
- run: docker compose down -v --remove-orphans
|
||||||
- run: docker compose up -d --wait
|
- run: docker compose up -d --wait backend frontend adminer
|
||||||
- name: Test backend is up
|
- name: Test backend is up
|
||||||
run: curl http://localhost:8000/api/v1/utils/health-check
|
run: curl http://localhost:8000/api/v1/utils/health-check
|
||||||
- name: Test frontend is up
|
- name: Test frontend is up
|
||||||
|
@@ -102,6 +102,31 @@ services:
|
|||||||
- VITE_API_URL=http://localhost:8000
|
- VITE_API_URL=http://localhost:8000
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
|
|
||||||
|
playwright:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile.playwright
|
||||||
|
args:
|
||||||
|
- VITE_API_URL=http://backend:8000
|
||||||
|
- NODE_ENV=production
|
||||||
|
ipc: host
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
- mailcatcher
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- VITE_API_URL=http://backend:8000
|
||||||
|
- MAILCATCHER_HOST=http://mailcatcher:1080
|
||||||
|
# For the reports when run locally
|
||||||
|
- PLAYWRIGHT_HTML_HOST=0.0.0.0
|
||||||
|
- CI=${CI}
|
||||||
|
volumes:
|
||||||
|
- ./frontend/blob-report:/app/blob-report
|
||||||
|
- ./frontend/test-results:/app/test-results
|
||||||
|
ports:
|
||||||
|
- 9323:9323
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
traefik-public:
|
traefik-public:
|
||||||
# For local dev, don't expect an external Traefik network
|
# For local dev, don't expect an external Traefik network
|
||||||
|
@@ -1 +1,2 @@
|
|||||||
VITE_API_URL=http://localhost:8000
|
VITE_API_URL=http://localhost:8000
|
||||||
|
MAILCATCHER_HOST=http://localhost:1080
|
||||||
|
13
frontend/Dockerfile.playwright
Normal file
13
frontend/Dockerfile.playwright
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM node:20
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json /app/
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
RUN npx -y playwright install --with-deps
|
||||||
|
|
||||||
|
COPY ./ /app/
|
||||||
|
|
||||||
|
ARG VITE_API_URL=${VITE_API_URL}
|
@@ -1,11 +1,10 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test';
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
import 'dotenv/config'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
* https://github.com/motdotla/dotenv
|
* https://github.com/motdotla/dotenv
|
||||||
*/
|
*/
|
||||||
// require('dotenv').config();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
@@ -21,7 +20,7 @@ export default defineConfig({
|
|||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: 'html',
|
reporter: process.env.CI ? 'blob' : 'html',
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
@@ -49,7 +49,7 @@ function UsersTable() {
|
|||||||
const { page } = Route.useSearch()
|
const { page } = Route.useSearch()
|
||||||
const navigate = useNavigate({ from: Route.fullPath })
|
const navigate = useNavigate({ from: Route.fullPath })
|
||||||
const setPage = (page: number) =>
|
const setPage = (page: number) =>
|
||||||
navigate({ search: (prev) => ({ ...prev, page }) })
|
navigate({ search: (prev: {[key: string]: string}) => ({ ...prev, page }) })
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: users,
|
data: users,
|
||||||
|
@@ -45,7 +45,7 @@ function ItemsTable() {
|
|||||||
const { page } = Route.useSearch()
|
const { page } = Route.useSearch()
|
||||||
const navigate = useNavigate({ from: Route.fullPath })
|
const navigate = useNavigate({ from: Route.fullPath })
|
||||||
const setPage = (page: number) =>
|
const setPage = (page: number) =>
|
||||||
navigate({ search: (prev) => ({ ...prev, page }) })
|
navigate({ search: (prev: {[key: string]: string}) => ({ ...prev, page }) })
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: items,
|
data: items,
|
||||||
|
@@ -50,7 +50,9 @@ test("User can reset password successfully using the link", async ({
|
|||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.goto(`http://localhost:1080/messages/${emailData.id}.html`)
|
await page.goto(
|
||||||
|
`${process.env.MAILCATCHER_HOST}/messages/${emailData.id}.html`,
|
||||||
|
)
|
||||||
|
|
||||||
const selector = 'a[href*="/reset-password?token="]'
|
const selector = 'a[href*="/reset-password?token="]'
|
||||||
|
|
||||||
@@ -103,7 +105,9 @@ test("Weak new password validation", async ({ page, request }) => {
|
|||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.goto(`http://localhost:1080/messages/${emailData.id}.html`)
|
await page.goto(
|
||||||
|
`${process.env.MAILCATCHER_HOST}/messages/${emailData.id}.html`,
|
||||||
|
)
|
||||||
|
|
||||||
const selector = 'a[href*="/reset-password?token="]'
|
const selector = 'a[href*="/reset-password?token="]'
|
||||||
let url = await page.getAttribute(selector, "href")
|
let url = await page.getAttribute(selector, "href")
|
||||||
|
@@ -10,7 +10,7 @@ async function findEmail({
|
|||||||
request,
|
request,
|
||||||
filter,
|
filter,
|
||||||
}: { request: APIRequestContext; filter?: (email: Email) => boolean }) {
|
}: { request: APIRequestContext; filter?: (email: Email) => boolean }) {
|
||||||
const response = await request.get("http://localhost:1080/messages")
|
const response = await request.get(`${process.env.MAILCATCHER_HOST}/messages`)
|
||||||
|
|
||||||
let emails = await response.json()
|
let emails = await response.json()
|
||||||
|
|
||||||
|
@@ -20,6 +20,6 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["src", "*.ts", "**/*.ts"],
|
"include": ["src/**/*.ts", "tests/**/*.ts", "playwright.config.ts"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user