Stabilize frontend Playwright CI runner
This commit is contained in:
@@ -213,7 +213,9 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
mkdir -p output/playwright
|
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:/source:ro" \
|
||||||
--volume "$PWD/output/playwright:/work/output/playwright" \
|
--volume "$PWD/output/playwright:/work/output/playwright" \
|
||||||
--workdir /work \
|
--workdir /work \
|
||||||
@@ -254,6 +256,11 @@ jobs:
|
|||||||
fi
|
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
|
- name: Upload Playwright report
|
||||||
if: failure() || cancelled()
|
if: failure() || cancelled()
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -278,7 +285,7 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 4
|
max-parallel: 2
|
||||||
matrix:
|
matrix:
|
||||||
browser: [chromium, webkit, firefox]
|
browser: [chromium, webkit, firefox]
|
||||||
device: [mobile, desktop, tablet]
|
device: [mobile, desktop, tablet]
|
||||||
@@ -297,6 +304,7 @@ jobs:
|
|||||||
PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-full-${{ matrix.browser }}-${{ matrix.device }}-${{ matrix.role }}
|
PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-full-${{ matrix.browser }}-${{ matrix.device }}-${{ matrix.role }}
|
||||||
PLAYWRIGHT_REPORTER_MODE: line-html
|
PLAYWRIGHT_REPORTER_MODE: line-html
|
||||||
PLAYWRIGHT_WORKERS: ${{ matrix.browser == 'firefox' && '1' || '2' }}
|
PLAYWRIGHT_WORKERS: ${{ matrix.browser == 'firefox' && '1' || '2' }}
|
||||||
|
PLAYWRIGHT_VIDEO_MODE: off
|
||||||
steps:
|
steps:
|
||||||
- name: Repair self-hosted workspace permissions
|
- name: Repair self-hosted workspace permissions
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -381,7 +389,9 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
mkdir -p output/playwright
|
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:/source:ro" \
|
||||||
--volume "$PWD/output/playwright:/work/output/playwright" \
|
--volume "$PWD/output/playwright:/work/output/playwright" \
|
||||||
--workdir /work \
|
--workdir /work \
|
||||||
@@ -390,6 +400,7 @@ jobs:
|
|||||||
--env PLAYWRIGHT_ARTIFACT_NAMESPACE="$PLAYWRIGHT_ARTIFACT_NAMESPACE" \
|
--env PLAYWRIGHT_ARTIFACT_NAMESPACE="$PLAYWRIGHT_ARTIFACT_NAMESPACE" \
|
||||||
--env PLAYWRIGHT_REPORTER_MODE="$PLAYWRIGHT_REPORTER_MODE" \
|
--env PLAYWRIGHT_REPORTER_MODE="$PLAYWRIGHT_REPORTER_MODE" \
|
||||||
--env PLAYWRIGHT_WORKERS="$PLAYWRIGHT_WORKERS" \
|
--env PLAYWRIGHT_WORKERS="$PLAYWRIGHT_WORKERS" \
|
||||||
|
--env PLAYWRIGHT_VIDEO_MODE="$PLAYWRIGHT_VIDEO_MODE" \
|
||||||
--env PLAYWRIGHT_DEV_PORT="$playwright_dev_port" \
|
--env PLAYWRIGHT_DEV_PORT="$playwright_dev_port" \
|
||||||
--env MATRIX_ROLE="$MATRIX_ROLE" \
|
--env MATRIX_ROLE="$MATRIX_ROLE" \
|
||||||
--env MATRIX_BROWSER="$MATRIX_BROWSER" \
|
--env MATRIX_BROWSER="$MATRIX_BROWSER" \
|
||||||
@@ -417,6 +428,11 @@ jobs:
|
|||||||
npm run test:e2e:full:slice -- --role="$MATRIX_ROLE" --project="$MATRIX_BROWSER-$MATRIX_DEVICE"
|
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
|
- name: Upload Playwright report
|
||||||
if: failure() || cancelled()
|
if: failure() || cancelled()
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|||||||
@@ -137,6 +137,11 @@ PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=1
|
|||||||
|
|
||||||
The runner fails fast if the combined worker count exceeds 5.
|
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:
|
Artifacts and summaries:
|
||||||
|
|
||||||
- `output/playwright/ci-parallel-report/index.html`
|
- `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 configuredWorkers = Number(process.env.PLAYWRIGHT_WORKERS || 2);
|
||||||
const workers = Number.isFinite(configuredWorkers) && configuredWorkers > 0 ? configuredWorkers : 2;
|
const workers = Number.isFinite(configuredWorkers) && configuredWorkers > 0 ? configuredWorkers : 2;
|
||||||
const reporterMode = (process.env.PLAYWRIGHT_REPORTER_MODE || "").trim();
|
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 =
|
const reporter =
|
||||||
reporterMode === "line-html"
|
reporterMode === "line-html"
|
||||||
? [["line"], ["html", { open: "never", outputFolder: htmlReportOutputFolder }]]
|
? [["line"], ["html", { open: "never", outputFolder: htmlReportOutputFolder }]]
|
||||||
@@ -59,7 +62,7 @@ export default defineConfig({
|
|||||||
baseURL,
|
baseURL,
|
||||||
trace: "retain-on-failure",
|
trace: "retain-on-failure",
|
||||||
screenshot: "only-on-failure",
|
screenshot: "only-on-failure",
|
||||||
video: "retain-on-failure",
|
video: videoMode,
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
buildProject("chromium-mobile", "chromium", "Pixel 5"),
|
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"
|
"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