Stabilize frontend Playwright CI runner

This commit is contained in:
Jeppe Bundgaard
2026-07-05 15:13:21 +02:00
parent ad999b7e71
commit 02d77847da
6 changed files with 171 additions and 4 deletions
+19 -3
View File
@@ -213,7 +213,9 @@ jobs:
exit 1
fi
mkdir -p output/playwright
"${docker_cmd[@]}" run --rm --ipc=host --network host \
scripts/ci/runner-diagnostics.sh "before Playwright PR ${MATRIX_SUITE}/${MATRIX_PROJECT}" -- "${docker_cmd[@]}"
SYSTEMD_INHIBIT_REASON="Frontend Playwright PR ${MATRIX_SUITE}/${MATRIX_PROJECT}" \
scripts/ci/with-systemd-inhibit.sh "${docker_cmd[@]}" run --rm --ipc=host --network host \
--volume "$PWD:/source:ro" \
--volume "$PWD/output/playwright:/work/output/playwright" \
--workdir /work \
@@ -254,6 +256,11 @@ jobs:
fi
'
- name: Runner diagnostics after Playwright failure
if: failure() || cancelled()
continue-on-error: true
run: scripts/ci/runner-diagnostics.sh "after Playwright PR ${{ matrix.suite }}/${{ matrix.project }}"
- name: Upload Playwright report
if: failure() || cancelled()
continue-on-error: true
@@ -278,7 +285,7 @@ jobs:
timeout-minutes: 60
strategy:
fail-fast: false
max-parallel: 4
max-parallel: 2
matrix:
browser: [chromium, webkit, firefox]
device: [mobile, desktop, tablet]
@@ -297,6 +304,7 @@ jobs:
PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-full-${{ matrix.browser }}-${{ matrix.device }}-${{ matrix.role }}
PLAYWRIGHT_REPORTER_MODE: line-html
PLAYWRIGHT_WORKERS: ${{ matrix.browser == 'firefox' && '1' || '2' }}
PLAYWRIGHT_VIDEO_MODE: off
steps:
- name: Repair self-hosted workspace permissions
shell: bash
@@ -381,7 +389,9 @@ jobs:
exit 1
fi
mkdir -p output/playwright
"${docker_cmd[@]}" run --rm --ipc=host --network host \
scripts/ci/runner-diagnostics.sh "before Playwright full ${MATRIX_BROWSER}/${MATRIX_DEVICE}/${MATRIX_ROLE}" -- "${docker_cmd[@]}"
SYSTEMD_INHIBIT_REASON="Frontend Playwright full ${MATRIX_BROWSER}/${MATRIX_DEVICE}/${MATRIX_ROLE}" \
scripts/ci/with-systemd-inhibit.sh "${docker_cmd[@]}" run --rm --ipc=host --network host \
--volume "$PWD:/source:ro" \
--volume "$PWD/output/playwright:/work/output/playwright" \
--workdir /work \
@@ -390,6 +400,7 @@ jobs:
--env PLAYWRIGHT_ARTIFACT_NAMESPACE="$PLAYWRIGHT_ARTIFACT_NAMESPACE" \
--env PLAYWRIGHT_REPORTER_MODE="$PLAYWRIGHT_REPORTER_MODE" \
--env PLAYWRIGHT_WORKERS="$PLAYWRIGHT_WORKERS" \
--env PLAYWRIGHT_VIDEO_MODE="$PLAYWRIGHT_VIDEO_MODE" \
--env PLAYWRIGHT_DEV_PORT="$playwright_dev_port" \
--env MATRIX_ROLE="$MATRIX_ROLE" \
--env MATRIX_BROWSER="$MATRIX_BROWSER" \
@@ -417,6 +428,11 @@ jobs:
npm run test:e2e:full:slice -- --role="$MATRIX_ROLE" --project="$MATRIX_BROWSER-$MATRIX_DEVICE"
'
- name: Runner diagnostics after Playwright failure
if: failure() || cancelled()
continue-on-error: true
run: scripts/ci/runner-diagnostics.sh "after Playwright full ${{ matrix.browser }}/${{ matrix.device }}/${{ matrix.role }}"
- name: Upload Playwright report
if: failure() || cancelled()
continue-on-error: true
+5
View File
@@ -137,6 +137,11 @@ PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=1
The runner fails fast if the combined worker count exceeds 5.
GitHub Actions keeps the full browser/device/role matrix stable by capping full-suite
matrix parallelism at 2 jobs, wrapping Docker Playwright runs with `systemd-inhibit`
when available, and setting `PLAYWRIGHT_VIDEO_MODE=off` for the full matrix. Traces
and screenshots are still retained on failure.
Artifacts and summaries:
- `output/playwright/ci-parallel-report/index.html`
+4 -1
View File
@@ -13,6 +13,9 @@ const htmlReportOutputFolder = path.join(artifactRoot, "report");
const configuredWorkers = Number(process.env.PLAYWRIGHT_WORKERS || 2);
const workers = Number.isFinite(configuredWorkers) && configuredWorkers > 0 ? configuredWorkers : 2;
const reporterMode = (process.env.PLAYWRIGHT_REPORTER_MODE || "").trim();
const configuredVideoMode = (process.env.PLAYWRIGHT_VIDEO_MODE || "retain-on-failure").trim();
const allowedVideoModes = new Set(["off", "on", "retain-on-failure", "on-first-retry"]);
const videoMode = allowedVideoModes.has(configuredVideoMode) ? configuredVideoMode : "retain-on-failure";
const reporter =
reporterMode === "line-html"
? [["line"], ["html", { open: "never", outputFolder: htmlReportOutputFolder }]]
@@ -59,7 +62,7 @@ export default defineConfig({
baseURL,
trace: "retain-on-failure",
screenshot: "only-on-failure",
video: "retain-on-failure",
video: videoMode,
},
projects: [
buildProject("chromium-mobile", "chromium", "Pixel 5"),
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -uo pipefail
label="${1:-runner}"
if [[ $# -gt 0 ]]; then
shift
fi
if [[ "${1:-}" == "--" ]]; then
shift
fi
docker_cmd=("$@")
run_bounded() {
local seconds="$1"
shift
if command -v timeout >/dev/null 2>&1; then
timeout "${seconds}s" "$@" || true
return
fi
"$@" || true
}
run_silent_bounded() {
local seconds="$1"
shift
if command -v timeout >/dev/null 2>&1; then
timeout "${seconds}s" "$@" >/dev/null 2>&1
return
fi
"$@" >/dev/null 2>&1
}
resolve_docker_cmd() {
if [[ "${#docker_cmd[@]}" -gt 0 ]]; then
return 0
fi
if command -v docker >/dev/null 2>&1 && run_silent_bounded 10 docker info; then
docker_cmd=(docker)
return 0
fi
if command -v sudo >/dev/null 2>&1 && run_silent_bounded 10 sudo -n docker info; then
docker_cmd=(sudo docker)
return 0
fi
docker_cmd=()
return 1
}
echo "::group::Runner diagnostics (${label})"
date --iso-8601=seconds 2>/dev/null || date
hostname || true
uname -a || true
if command -v free >/dev/null 2>&1; then
echo
echo "Memory:"
free -h || true
fi
if command -v df >/dev/null 2>&1; then
echo
echo "Disk:"
df -h / /tmp "$PWD" 2>/dev/null || df -h / /tmp 2>/dev/null || true
fi
if command -v systemd-inhibit >/dev/null 2>&1; then
echo
echo "Systemd inhibitors:"
run_bounded 10 systemd-inhibit --list
fi
if resolve_docker_cmd; then
echo
echo "Docker info:"
run_bounded 20 "${docker_cmd[@]}" info
echo
echo "Docker disk usage:"
run_bounded 20 "${docker_cmd[@]}" system df
echo
echo "Recent Docker containers:"
run_bounded 20 "${docker_cmd[@]}" ps -a --no-trunc --format "table {{.ID}}\t{{.Status}}\t{{.Names}}" | head -n 20 || true
else
echo
echo "Docker diagnostics unavailable to this runner user."
fi
echo "::endgroup::"
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$#" -eq 0 ]]; then
echo "Usage: $0 <command> [args...]" >&2
exit 64
fi
reason="${SYSTEMD_INHIBIT_REASON:-GitHub Actions frontend E2E job is running}"
can_use_systemd_inhibit() {
if ! command -v systemd-inhibit >/dev/null 2>&1; then
return 1
fi
if command -v timeout >/dev/null 2>&1; then
timeout 10s systemd-inhibit --what=sleep:shutdown --mode=block --who=frontend-ci --why="frontend-ci probe" true >/dev/null 2>&1
return
fi
systemd-inhibit --what=sleep:shutdown --mode=block --who=frontend-ci --why="frontend-ci probe" true >/dev/null 2>&1
}
if can_use_systemd_inhibit; then
exec systemd-inhibit --what=sleep:shutdown --mode=block --who=frontend-ci --why="$reason" "$@"
fi
exec "$@"
@@ -28,4 +28,21 @@ describe("Playwright full E2E workflow grouping", () => {
"output/playwright/test-lists/${{ matrix.browser }}-${{ matrix.device }}-${{ matrix.role }}.txt"
);
});
it("keeps full-suite runner pressure bounded and diagnosable", () => {
const source = workflowSource();
expect(source).toMatch(/e2e-full:[\s\S]*?max-parallel: 2/u);
expect(source).toMatch(/e2e-full:[\s\S]*?PLAYWRIGHT_VIDEO_MODE: off/u);
expect(source).toContain("scripts/ci/with-systemd-inhibit.sh");
expect(source).toContain("scripts/ci/runner-diagnostics.sh");
});
it("allows CI to reduce Playwright video artifact pressure", () => {
const source = readFileSync(join(root, "playwright.config.ts"), "utf8");
expect(source).toContain("PLAYWRIGHT_VIDEO_MODE");
expect(source).toContain('"retain-on-failure"');
expect(source).toContain('"on-first-retry"');
});
});