Skip to content

Commit d9b5eac

Browse files
committed
chore(ci): compare frozen WebKit provisioning paths
1 parent 9e1f122 commit d9b5eac

7 files changed

Lines changed: 649 additions & 6 deletions

File tree

‎.github/scripts/http-e2e.sh‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,19 @@ JS
318318
export INTERNAL_API_SECRET=mobile-http-ci-local-secret-at-least-32-characters
319319
export NEXT_PUBLIC_SOCKET_URL=http://127.0.0.1:3025 SOCKET_SERVER_URL=http://127.0.0.1:3025
320320
read -ra browsers <<< "${MOBILE_E2E_BROWSER:-chromium webkit}"
321-
start_browser_install mobile --only-shell "${browsers[@]}"
321+
if [ "${PLAYWRIGHT_WEBKIT_WS_ENDPOINT+x}" = x ]; then
322+
if [ "${MOBILE_E2E_BROWSER:-}" != webkit ]; then
323+
echo "::error::A remote endpoint requires explicit WebKit selection." >&2
324+
exit 1
325+
fi
326+
else
327+
start_browser_install mobile --only-shell "${browsers[@]}"
328+
fi
322329
start_app mobile 3024 'mobile browser' record-http-status
323330
start_realtime mobile 3025
324-
wait_for_browsers
331+
if [ "${PLAYWRIGHT_WEBKIT_WS_ENDPOINT+x}" != x ]; then
332+
wait_for_browsers
333+
fi
325334
MOBILE_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
326335
MOBILE_E2E_DATABASE_URL="$DATABASE_URL" \
327336
MOBILE_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env bash
2+
# Owns one pinned WebKit server; start prints its loopback endpoint, stop preserves its logs.
3+
set -euo pipefail
4+
umask 077
5+
6+
action=${1:-}
7+
state_argument=${2:-}
8+
if [ "$#" -ne 2 ] || { [ "$action" != start ] && [ "$action" != stop ]; }; then
9+
echo 'Usage: playwright-server.sh <start|stop> <state-directory-under-RUNNER_TEMP>' >&2
10+
exit 2
11+
fi
12+
: "${RUNNER_TEMP:?RUNNER_TEMP must be set}"
13+
: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY must be set}"
14+
: "${GITHUB_RUN_ID:?GITHUB_RUN_ID must be set}"
15+
: "${GITHUB_RUN_ATTEMPT:?GITHUB_RUN_ATTEMPT must be set}"
16+
: "${GITHUB_JOB:?GITHUB_JOB must be set}"
17+
if ! [[ "$GITHUB_RUN_ID" =~ ^[1-9][0-9]*$ && "$GITHUB_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ && "$GITHUB_JOB" =~ ^[a-zA-Z0-9_-]+$ ]]; then
18+
echo 'Invalid GitHub job ownership metadata.' >&2
19+
exit 2
20+
fi
21+
state=$(node - "$RUNNER_TEMP" "$state_argument" <<'JS'
22+
const fs = require('node:fs')
23+
const path = require('node:path')
24+
const root = fs.realpathSync(process.argv[2])
25+
const state = path.resolve(process.argv[3])
26+
if (path.dirname(state) !== root || (fs.existsSync(state) && fs.realpathSync(state) !== state))
27+
throw new Error('State must be a direct, non-symlink child of RUNNER_TEMP')
28+
process.stdout.write(state)
29+
JS
30+
)
31+
state_key=$(node -e 'process.stdout.write(require("node:crypto").createHash("sha256").update(process.argv[1]).digest("hex"))' "$state")
32+
image='mcr.microsoft.com/playwright@sha256:2f05f8a734509a7b3f26a912639488ee08f58a7b292d14d62271bcff9038768f'
33+
starting=false
34+
35+
stop_container() {
36+
local cid inspection remaining status=0
37+
[ -f "$state/cid" ] || return 0
38+
cid=$(< "$state/cid")
39+
if ! [[ "$cid" =~ ^[0-9a-f]{64}$ ]]; then
40+
echo 'Refusing to stop an invalid container ID.' >&2
41+
return 1
42+
fi
43+
if ! inspection=$(docker container inspect "$cid" 2> "$state/inspect-error.log"); then
44+
remaining=$(docker container ls --all --no-trunc --filter "id=$cid" --format '{{.ID}}') || return 1
45+
[ -z "$remaining" ] && return 0
46+
cat "$state/inspect-error.log" >&2
47+
return 1
48+
fi
49+
if ! jq -e --arg repository "$GITHUB_REPOSITORY" --arg run "$GITHUB_RUN_ID" \
50+
--arg attempt "$GITHUB_RUN_ATTEMPT" --arg job "$GITHUB_JOB" --arg state "$state_key" \
51+
'.[0].Config.Labels | .["com.sim.ci.playwright-server"] == "true"
52+
and .["com.sim.ci.repository"] == $repository and .["com.sim.ci.run"] == $run
53+
and .["com.sim.ci.attempt"] == $attempt and .["com.sim.ci.job"] == $job
54+
and .["com.sim.ci.state"] == $state' <<< "$inspection" > /dev/null; then
55+
echo 'Refusing to stop a container owned by another job or state directory.' >&2
56+
return 1
57+
fi
58+
docker stop --time 10 "$cid" > "$state/stop.log" 2>&1 || status=1
59+
docker logs "$cid" > "$state/server.log" 2>&1 || status=1
60+
docker rm "$cid" > "$state/remove.log" 2>&1 || status=1
61+
return "$status"
62+
}
63+
64+
trap '
65+
status=$?
66+
trap - EXIT
67+
if [ "$status" -ne 0 ] && [ "$starting" = true ]; then
68+
stop_container || echo "Owned Playwright server cleanup failed; preserve its state and logs." >&2
69+
fi
70+
exit "$status"
71+
' EXIT
72+
trap 'exit 130' INT
73+
trap 'exit 143' TERM
74+
75+
if [ "$action" = stop ]; then
76+
stop_container
77+
exit 0
78+
fi
79+
if [ -e "$state/cid" ]; then
80+
echo 'Refusing to reuse an existing Playwright server state.' >&2
81+
exit 1
82+
fi
83+
mkdir -p -- "$state"
84+
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
85+
driver=$(node - "$repo_root/apps/sim" <<'JS'
86+
const fs = require('node:fs')
87+
const path = require('node:path')
88+
const packagePath = require.resolve('playwright-core/package.json', { paths: [process.argv[2]] })
89+
const root = fs.realpathSync(path.dirname(packagePath))
90+
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'browsers.json'), 'utf8'))
91+
if (JSON.parse(fs.readFileSync(packagePath, 'utf8')).version !== '1.64.1' ||
92+
manifest.browsers.find(browser => browser.name === 'webkit')?.revision !== '2370')
93+
throw new Error('The pinned WebKit image requires Playwright 1.64.1, revision 2370')
94+
process.stdout.write(root)
95+
JS
96+
)
97+
docker pull "$image" > "$state/pull.log" 2>&1
98+
starting=true
99+
docker run --detach --init --cidfile "$state/cid" \
100+
--name "sim-ci-playwright-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$GITHUB_JOB-$$" \
101+
--label com.sim.ci.playwright-server=true --label "com.sim.ci.repository=$GITHUB_REPOSITORY" \
102+
--label "com.sim.ci.run=$GITHUB_RUN_ID" --label "com.sim.ci.attempt=$GITHUB_RUN_ATTEMPT" \
103+
--label "com.sim.ci.job=$GITHUB_JOB" --label "com.sim.ci.state=$state_key" \
104+
--network host --cpus 8 --memory 4g --memory-swap 4g --pids-limit 512 --shm-size 256m \
105+
--mount "type=bind,src=$driver,dst=/opt/playwright-core,readonly" \
106+
"$image" node /opt/playwright-core/cli.js run-server --host 127.0.0.1 --port 0 \
107+
> "$state/start.log" 2>&1
108+
cid=$(< "$state/cid")
109+
if ! [[ "$cid" =~ ^[0-9a-f]{64}$ ]]; then
110+
echo 'Docker did not return a complete container ID.' >&2
111+
exit 1
112+
fi
113+
docker container inspect "$cid" > "$state/metadata.json"
114+
deadline=$((SECONDS + 30))
115+
while [ "$SECONDS" -lt "$deadline" ]; do
116+
live=$(docker container inspect --format '{{.State.Running}} {{.State.Pid}}' "$cid")
117+
if ! [[ "$live" =~ ^true\ [1-9][0-9]*$ ]]; then
118+
docker logs "$cid" > "$state/server.log" 2>&1
119+
echo 'Playwright server exited before readiness.' >&2
120+
exit 1
121+
fi
122+
docker logs "$cid" > "$state/server.log" 2>&1
123+
endpoint=$(sed -n 's@^Listening on \(ws://127\.0\.0\.1:[1-9][0-9]*/\)$@\1@p' "$state/server.log")
124+
if [ -n "$endpoint" ]; then
125+
node - "$endpoint" <<'JS'
126+
const endpoint = process.argv[2]
127+
if (!/^ws:\/\/127\.0\.0\.1:[1-9]\d*\/$/.test(endpoint)) throw new Error('Invalid server endpoint')
128+
new URL(endpoint)
129+
process.stdout.write(`${endpoint}\n`)
130+
JS
131+
exit 0
132+
fi
133+
sleep 0.1
134+
done
135+
echo 'Playwright server did not report a live loopback endpoint within 30 seconds.' >&2
136+
exit 1

0 commit comments

Comments
 (0)