Files
sanderling/.github/workflows/replay-ui.yml
T
pj 7c845ff498 ci: declare least-privilege permissions
none of the three declared any, so each got the repository default.
release.yml and docs.yml already do this. all three only check out,
build, test and upload artifacts.
2026-08-15 12:46:49 +05:30

138 lines
4.7 KiB
YAML

name: replay-ui
# Sanderling fuzzing sanderling's own replay UI. Dispatch-only: it takes minutes
# and it is a demo of the product loop, not a merge gate.
#
# The shape is: produce a real trace, serve it with `sanderling replay`, then run
# a spec against that UI. Any violation fails the job. Six of the seven
# properties in replay-ui/sanderling/spec.ts are cross-panel agreements that hold
# for any trace; the seventh is the stock noUncaughtExceptions. None of them
# needs recalibrating when the fixture changes.
on:
workflow_dispatch:
inputs:
seed:
description: seed for the dogfood run
default: "3"
max-steps:
description: step budget for the dogfood run
default: "80"
permissions:
contents: read
jobs:
dogfood:
timeout-minutes: 45
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Set up bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.13"
# Pinned stable plus the AppArmor sysctl: the same setup ci.yml's browser
# job needs to get headless Chrome up on ubuntu-latest.
- name: Set up Chrome
uses: browser-actions/setup-chrome@v1
with:
chrome-version: stable
- name: Allow Chrome under unprivileged user namespaces
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
- name: Verify headless Chrome starts
run: |
chrome --version
chrome --headless --no-sandbox --disable-gpu --disable-dev-shm-usage \
--dump-dom 'data:text/html,<title>ok</title>'
# The UI the spec drives is the one embedded in this binary, so the build
# has to come after any change to replay-ui/src.
- name: Build sanderling
run: make sanderling-web
# A trace with a violation and uncaught exceptions in it, so the UI has
# something to render in every panel the spec looks at. No
# --exit-on-violation here: the run is the fixture, and stopping it at the
# first violation would leave a four-step trace to fuzz.
- name: Record a fixture trace
run: |
python3 -m http.server 8792 --bind 127.0.0.1 \
--directory test/browser/testdata/throwing &
for _ in $(seq 1 30); do
curl -sf http://127.0.0.1:8792/ >/dev/null && break
sleep 1
done
./bin/sanderling test \
--platform web \
--spec test/browser/testdata/throwing/spec.ts \
--bundle-id http://127.0.0.1:8792/ \
--duration 5m --max-steps 25 --seed 7 \
--output runs/fixture
- name: Serve the trace with sanderling replay
run: |
# Flags before the positional argument: Go's flag package stops
# parsing at the first non-flag word.
./bin/sanderling replay --port 8793 --no-open runs/fixture &
for _ in $(seq 1 30); do
curl -sf http://127.0.0.1:8793/api/runs >/dev/null && break
sleep 1
done
run_id="$(ls runs/fixture | head -1)"
echo "RUN_URL=http://127.0.0.1:8793/runs/$run_id/steps/1" >> "$GITHUB_ENV"
curl -sf "http://127.0.0.1:8793/runs/$run_id/steps/1" >/dev/null
# Inputs go through env rather than into the script text: a `${{ }}` is
# substituted before bash ever sees the line, so a seed of `$(id)` would
# run as a command.
- name: Fuzz the replay UI
run: |
./bin/sanderling test \
--platform web \
--spec replay-ui/sanderling/spec.ts \
--bundle-id "$RUN_URL" \
--duration 10m \
--max-steps "$MAX_STEPS" \
--seed "$SEED" \
--exit-on-violation \
--output runs/dogfood
env:
SEED: ${{ inputs.seed }}
MAX_STEPS: ${{ inputs.max-steps }}
- name: Summarise
if: always()
run: |
{
echo "### replay-ui dogfood"
echo
echo "- seed \`$SEED\`, budget $MAX_STEPS steps"
for dir in runs/dogfood/*/; do
steps=$(wc -l < "$dir/trace.jsonl" | tr -d ' ')
violations=$(grep -c '"violations":\[' "$dir/trace.jsonl" || true)
echo "- $steps steps recorded, $violations step(s) with violations"
done
} >> "$GITHUB_STEP_SUMMARY"
env:
SEED: ${{ inputs.seed }}
MAX_STEPS: ${{ inputs.max-steps }}
- name: Upload runs
if: always()
uses: actions/upload-artifact@v4
with:
name: replay-ui-runs
path: runs/
retention-days: 14