Skip to content

fix(sw): do not let a busy worker's silence cancel the reload that re… #602

fix(sw): do not let a busy worker's silence cancel the reload that re…

fix(sw): do not let a busy worker's silence cancel the reload that re… #602

Workflow file for this run

name: CI
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
# A pull request that gets a new push has nothing to learn from the run for the
# superseded commit, and every one of those runs holds a Playwright job for
# minutes. Pushes to main are never cancelled: each one is the commit a deploy
# and the production smoke are about to be judged on.
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
# The three E2E suites run the same ~95 specs against three different servers
# (vite preview, the production container, wrangler pages dev), which is the
# whole cost of this workflow -- install, vite build and docker build together
# are under two minutes. Each suite is therefore split across three runners,
# and a summary job carries the name the branch protection requires.
#
# Sharding, not more workers: the runner has 4 cores, the editor is a WASM
# process per worker, and the Pages suite deliberately runs single-worker
# (playwright.pages.config.ts explains why workerd dies under concurrent
# aborts). Shards keep every suite's own concurrency semantics untouched.
#
# Changing the shard count means editing both the matrix and the /N in the
# --shard argument of that job.
jobs:
lint:
name: Lint and Validate
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up the toolchain
uses: ./.github/actions/setup
- name: Check formatting
run: pnpm run format:check
- name: Run oxlint and TypeScript checks
run: pnpm run lint:ts
- name: Run unit tests with coverage
run: pnpm run test:coverage
- name: Validate Docker Compose file
run: docker compose -f docker-compose.yaml config --quiet
- name: Lint Dockerfile with Hadolint
uses: hadolint/hadolint-action@v3.4.0
with:
dockerfile: Dockerfile
failure-threshold: error
# The E2E jobs deliberately do NOT wait for lint. Lint is a one-minute job,
# but waiting for it put ~90 seconds of pure latency on the critical path of
# every PR. On a public repository the runners are free, so the only cost of
# starting E2E early is a wasted run on the commits where lint is red -- and
# those commits get their E2E answer for free instead of after a second push.
e2e-shard:
name: E2E shard ${{ matrix.shard }}
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
# One shard failing must not hide the verdict of the other two: a red
# suite should report every failure it has in a single run.
fail-fast: false
matrix:
shard: [1, 2, 3]
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up the toolchain
uses: ./.github/actions/setup
with:
browsers: chromium
- name: Run E2E tests
# `@serial` is excluded here and measured in the next step: those cases
# assert wall-clock budgets, and four WASM editors sharing four cores
# push them past their bound (the font-system wait measured 3.4 s
# against a 2 s budget). See the next step and CLAUDE.md.
run: pnpm exec playwright test --shard=${{ matrix.shard }}/3 --grep-invert @serial
- name: Run the timing-budget E2E cases with the runner to themselves
# Run in every shard rather than one: each shard is its own VM, so this
# pass really is alone, and every shard stays a copy of the others (an
# asymmetric shard is how a step gets dropped unnoticed). Deliberately
# not sharded -- it is a single case.
#
# The case itself takes seconds, but the step does not: this is a second
# `playwright test` process, and `reuseExistingServer` is false in CI, so
# each shard pays another full `vite build` + preview startup (~1 min)
# for it. That is the price of testing the budget at all -- measured
# against three WASM editors it is the machine that fails, not the code
# -- but it is a minute per shard, not seconds.
run: pnpm exec playwright test --grep @serial --workers=1
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v7
with:
name: playwright-report-${{ matrix.shard }}
path: playwright-report/
retention-days: 7
# Summary jobs. Branch protection matches required checks by name, so the
# three names below must stay exactly as they are -- the sharded jobs are
# named "... shard N" precisely so these can keep the original names.
e2e:
name: E2E
runs-on: ubuntu-latest
needs: e2e-shard
if: always()
timeout-minutes: 5
steps:
- name: Report the shard verdict
env:
RESULT: ${{ needs.e2e-shard.result }}
run: |
echo "shards: $RESULT"
[ "$RESULT" = "success" ] || exit 1
# Deploy artifact + host semantics: bin/build.sh output served by
# `wrangler pages dev`, which reproduces Cloudflare Pages' index.html -> 308
# redirects, _headers and _redirects. Catches "works on vite preview, broken
# on the live site" (PDF loader stuck behind the 308; font cache headers).
e2e-pages-shard:
name: E2E (Cloudflare Pages semantics) shard ${{ matrix.shard }}
runs-on: ubuntu-latest
# Five shards rather than three: this suite still serves wrangler with a
# single worker on purpose (see playwright.pages.config.ts), so it is the
# slowest of the three and sets the critical path. Every shard also pays a
# fixed ~2 minutes for its own bin/build.sh and wrangler start, which is
# what stops the split from paying off much beyond this.
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up the toolchain
uses: ./.github/actions/setup
with:
browsers: chromium
- name: Run E2E against wrangler pages dev
# `@serial` is excluded by playwright.pages.config.ts itself, so a local
# run of that config measures exactly what this job does.
run: pnpm exec playwright test -c playwright.pages.config.ts --shard=${{ matrix.shard }}/5
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v7
with:
name: playwright-report-pages-${{ matrix.shard }}
path: playwright-report-pages/
retention-days: 7
e2e-pages:
name: E2E (Cloudflare Pages semantics)
runs-on: ubuntu-latest
needs: e2e-pages-shard
if: always()
timeout-minutes: 5
steps:
- name: Report the shard verdict
env:
RESULT: ${{ needs.e2e-pages-shard.result }}
run: |
echo "shards: $RESULT"
[ "$RESULT" = "success" ] || exit 1
# The lint job only validates compose config and Dockerfile style; this job
# actually builds the image and runs the full e2e suite against the running
# container, proving the production image serves the editor correctly (this
# is what caught the workspace-manifest install breakage). Every shard builds
# the image itself -- that build is 45 seconds, far cheaper than shipping the
# image between jobs as an artifact.
e2e-docker-shard:
name: E2E (Docker image) shard ${{ matrix.shard }}
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up the toolchain
uses: ./.github/actions/setup
with:
browsers: chromium
- name: Run E2E tests against the Docker image
# Called directly, not through `pnpm run`: pnpm swallows the extra
# arguments, which would silently run the whole suite in every shard.
# `@serial` is excluded by playwright.docker.config.ts itself, so a
# local `pnpm run test:e2e:docker` measures the same set.
run: sh ./bin/test-e2e-docker.sh --shard=${{ matrix.shard }}/3
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v7
with:
name: playwright-report-docker-${{ matrix.shard }}
path: playwright-report-docker/
retention-days: 7
e2e-docker:
name: E2E (Docker image)
runs-on: ubuntu-latest
needs: e2e-docker-shard
if: always()
timeout-minutes: 5
steps:
- name: Report the shard verdict
env:
RESULT: ${{ needs.e2e-docker-shard.result }}
run: |
echo "shards: $RESULT"
[ "$RESULT" = "success" ] || exit 1