Stabilize frontend Playwright CI runner
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Executable
+98
@@ -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::"
|
||||
Executable
+28
@@ -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"');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user