mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
The config held literals for values vars/main.yml already owned, and the two drifted without a sound. actionTimeout carried `|| 30_000` for its whole life while nothing ever set PLAYWRIGHT_ACTION_TIMEOUT, so every run used the unscaled 30 s. expect had no entry at all, leaving Playwright's built-in 5 s to govern every assertion over Tor while its neighbours were scaled by the onion multiplier. That is what killed the second Nextcloud login attempt: an expect(usernameField).toBeVisible() that gave up after 5 s inside a 150 s budget. vars/main.yml now owns the values in TEST_E2E_PLAYWRIGHT_CONFIG_TIMEOUTS, the harness renders that map into the staged .env both the deploy and scripts/tests/e2e/rerun-spec.sh already pass, and the config demands each name through requiredTimeout() instead of inventing a fallback. The two `-e` flags that fed the old pair are gone with it. scripts/lint/playwright.sh stages the same config and runs `playwright test --list`, which loads it, but built its stub env only from each role's playwright.env.j2 and no such template carries a timeout. Without this it would have aborted all 87 specs on a missing variable. It now takes the names from the same map; the value is a placeholder because --list consumes none of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
207 lines
6.7 KiB
Bash
Executable File
207 lines
6.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# shellcheck shell=bash
|
|
#
|
|
# Lint each role's Playwright spec by staging it the same way the
|
|
# `test-e2e-playwright` role does at deploy time, then running
|
|
# `npx playwright test --list` to validate that:
|
|
#
|
|
# - every spec parses without throwing at module-load time
|
|
# - every `require("./personas")` / `require("./service-gating")`
|
|
# resolves against the staged personas tree
|
|
# - every `test(...)` registration is reachable for the runner
|
|
#
|
|
# `--list` skips the browser launch and any test body, so this is a
|
|
# fast structural check (catches refactor regressions like missing
|
|
# helpers, broken exports, or syntax errors) — not a functional one.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
|
|
|
|
cd "${REPO_ROOT}"
|
|
|
|
# shellcheck source=scripts/meta/env/load.sh
|
|
source scripts/meta/env/load.sh
|
|
|
|
: "${INFINITO_WORKER_CPU:?INFINITO_WORKER_CPU must be set (provided by default.env via the env loader)}"
|
|
|
|
if [[ ! -d "node_modules/@playwright/test" ]]; then
|
|
echo "@playwright/test not installed. Run 'make install-lint' first." >&2
|
|
exit 127
|
|
fi
|
|
|
|
HELPERS_SRC="${REPO_ROOT}/roles/test-e2e-playwright/files"
|
|
PERSONAS_SRC="${HELPERS_SRC}/personas"
|
|
PLAYWRIGHT_CONFIG_SRC="${HELPERS_SRC}/playwright.config.js"
|
|
ROLE_VARS_SRC="${REPO_ROOT}/roles/test-e2e-playwright/vars/main.yml"
|
|
|
|
mapfile -t CONFIG_TIMEOUT_ENV < <(
|
|
sed -n '/^TEST_E2E_PLAYWRIGHT_CONFIG_TIMEOUTS:/,/^[^[:space:]#]/p' "${ROLE_VARS_SRC}" |
|
|
grep -oE '^[[:space:]]+[A-Z][A-Z0-9_]+:' | tr -d ' :' | sed 's/$/=1/'
|
|
)
|
|
|
|
if [[ ${#CONFIG_TIMEOUT_ENV[@]} -eq 0 ]]; then
|
|
echo "lint-playwright: no timeouts read from ${ROLE_VARS_SRC}; every config load would throw" >&2
|
|
exit 2
|
|
fi
|
|
|
|
for asset in "${PERSONAS_SRC}" "${PLAYWRIGHT_CONFIG_SRC}"; do
|
|
if [[ ! -e "${asset}" ]]; then
|
|
echo "lint-playwright: missing staging asset: ${asset}" >&2
|
|
exit 2
|
|
fi
|
|
done
|
|
|
|
mapfile -t SPEC_FILES < <(find roles -type f -path '*/files/playwright/playwright.spec.js' | sort)
|
|
|
|
if [[ ${#SPEC_FILES[@]} -eq 0 ]]; then
|
|
echo "lint-playwright: no Playwright specs found under roles/*/files/playwright/" >&2
|
|
exit 1
|
|
fi
|
|
|
|
STAGE_ROOT="$(mktemp -d -t infinito-lint-playwright.XXXXXX)"
|
|
trap 'rm -rf "${STAGE_ROOT}"' EXIT
|
|
|
|
# Stub env values keyed off the variable-name suffix. Some specs evaluate
|
|
# `new URL(process.env.FOO_URL)` or `process.env.BAR.replace(...)` at
|
|
# module-load time, so they crash under `--list` unless the env is
|
|
# populated. We can't render the Jinja .env templates, but the suffix
|
|
# is enough to satisfy URL constructors and `.replace()` calls without
|
|
# pretending real values exist.
|
|
stub_env_for_key() {
|
|
local key="$1"
|
|
case "${key}" in
|
|
*_URL | *URL) printf '%s' "https://example.test" ;;
|
|
*_DOMAIN | *DOMAIN) printf '%s' "example.test" ;;
|
|
*_SERVICE_ENABLED) printf '%s' "true" ;;
|
|
*_BLOCKED | *BLOCKED) printf '%s' "false" ;;
|
|
*_EMAIL | *EMAIL) printf '%s' "stub@example.test" ;;
|
|
*_JSON | *JSON | *SLUGS | *OVERRIDE) printf '%s' "[]" ;;
|
|
*_HANDLE | *HANDLE) printf '%s' "stubhandle" ;;
|
|
*_PASSWORD | *PASSWORD) printf '%s' "stubpass" ;;
|
|
*_TOKEN | *TOKEN | *_KEY | *KEY) printf '%s' "stubtoken" ;;
|
|
*_USERNAME | *USERNAME) printf '%s' "stubuser" ;;
|
|
*) printf '%s' "stub" ;;
|
|
esac
|
|
}
|
|
|
|
# Reset reporter output paths to a per-role writable location. The
|
|
# central playwright.config.js hard-codes `/reports/...` (only writable
|
|
# inside the Playwright container), which spams `ENOENT: mkdir`
|
|
# stderr noise under lint. Re-pointing them keeps the run silent and
|
|
# self-cleaning under STAGE_ROOT.
|
|
config_stub() {
|
|
cat <<-'EOF_CFG'
|
|
const base = require("./playwright.config.original.js");
|
|
const path = require("path");
|
|
const here = __dirname;
|
|
module.exports = {
|
|
...base,
|
|
outputDir: path.join(here, "test-results"),
|
|
reporter: [["list"]],
|
|
};
|
|
EOF_CFG
|
|
}
|
|
|
|
# Lint a single role end-to-end: stage assets, derive stub env, run
|
|
# `npx playwright test --list`. All output goes to stdout/stderr of the
|
|
# caller, so the parallel driver redirects it into a per-role log file.
|
|
lint_one_role() {
|
|
local spec="$1"
|
|
local role_files_dir
|
|
local role
|
|
role_files_dir="$(dirname "${spec}")"
|
|
role="${spec#roles/}"
|
|
role="${role%%/*}"
|
|
|
|
echo "::group::lint-playwright ${role}"
|
|
|
|
local stage_dir="${STAGE_ROOT}/${role}"
|
|
local tests_dir="${stage_dir}/tests"
|
|
mkdir -p "${tests_dir}/personas/utils"
|
|
|
|
cp -f "${PLAYWRIGHT_CONFIG_SRC}" "${stage_dir}/playwright.config.original.js"
|
|
config_stub >"${stage_dir}/playwright.config.js"
|
|
|
|
cp -f "${role_files_dir}"/*.js "${tests_dir}/"
|
|
find "${role_files_dir}" -mindepth 1 -maxdepth 1 -type d -exec cp -R {} "${tests_dir}/" \;
|
|
for helper_src in "${HELPERS_SRC}"/*.js; do
|
|
[[ -f "${helper_src}" ]] || continue
|
|
[[ "$(basename "${helper_src}")" == "playwright.config.js" ]] && continue
|
|
cp -f "${helper_src}" "${tests_dir}/$(basename "${helper_src}")"
|
|
done
|
|
cp -f "${PERSONAS_SRC}"/*.js "${tests_dir}/personas/"
|
|
cp -f "${PERSONAS_SRC}"/utils/*.js "${tests_dir}/personas/utils/"
|
|
|
|
ln -sfn "${REPO_ROOT}/node_modules" "${stage_dir}/node_modules"
|
|
|
|
local stub_env=("${CONFIG_TIMEOUT_ENV[@]}")
|
|
local env_template="roles/${role}/templates/playwright.env.j2"
|
|
if [[ -f "${env_template}" ]]; then
|
|
local key
|
|
while IFS= read -r key; do
|
|
[[ -z "${key}" ]] && continue
|
|
stub_env+=("${key}=$(stub_env_for_key "${key}")")
|
|
done < <(grep -oE '^[A-Z][A-Z0-9_]+=' "${env_template}" | sed 's/=$//' | sort -u)
|
|
fi
|
|
|
|
local rc=0
|
|
(cd "${stage_dir}" && env "${stub_env[@]+"${stub_env[@]}"}" npx --no-install playwright test --list) || rc=$?
|
|
echo "::endgroup::"
|
|
return "${rc}"
|
|
}
|
|
|
|
log_dir="${STAGE_ROOT}/logs"
|
|
mkdir -p "${log_dir}"
|
|
|
|
total_specs=${#SPEC_FILES[@]}
|
|
n_workers="${INFINITO_WORKER_CPU}"
|
|
if ((n_workers > total_specs)); then n_workers="${total_specs}"; fi
|
|
|
|
active=0
|
|
declare -A pid_to_role=()
|
|
declare -A role_rc=()
|
|
|
|
drain_completion() {
|
|
local pid=""
|
|
local rc=0
|
|
wait -n -p pid || rc=$?
|
|
local role="${pid_to_role[${pid}]}"
|
|
role_rc["${role}"]="${rc}"
|
|
active=$((active - 1))
|
|
}
|
|
|
|
for spec in "${SPEC_FILES[@]}"; do
|
|
while ((active >= n_workers)); do
|
|
drain_completion
|
|
done
|
|
role="${spec#roles/}"
|
|
role="${role%%/*}"
|
|
lint_one_role "${spec}" >"${log_dir}/${role}.log" 2>&1 &
|
|
pid_to_role[$!]="${role}"
|
|
active=$((active + 1))
|
|
done
|
|
|
|
while ((active > 0)); do
|
|
drain_completion
|
|
done
|
|
|
|
failed_roles=()
|
|
for spec in "${SPEC_FILES[@]}"; do
|
|
role="${spec#roles/}"
|
|
role="${role%%/*}"
|
|
cat "${log_dir}/${role}.log"
|
|
if [[ "${role_rc[${role}]:-0}" -ne 0 ]]; then
|
|
failed_roles+=("${role}")
|
|
echo "::error file=${spec}::lint-playwright: 'playwright test --list' failed for ${role}"
|
|
fi
|
|
done
|
|
|
|
if [[ ${#failed_roles[@]} -gt 0 ]]; then
|
|
echo "lint-playwright FAILED for ${#failed_roles[@]}/${total_specs} role(s): ${failed_roles[*]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "lint-playwright OK (${total_specs} spec(s) parsed, ${n_workers} parallel worker(s))"
|