Browser tests
Offload a Playwright e2e suite to the shipped `playwright-e2e` run — sharded across the Browser Rendering pool, results posted back as a Check Run.
Source
This recipe ships both triggers — a typed Effect-TS run
(*.run.ts) and a GitHub Actions workflow
(ci.yml). Use whichever fits;
Action mode is the recommended default.
# Recipe: browser tests (Playwright e2e) on Cloudflare
#
# Use case: a Playwright suite that is slow and burns GHA minutes. Offload it
# to the shipped `playwright-e2e` run, which shards the suite across the
# Cloudflare Browser Rendering pool. GHA only fires the dispatch (~10 s) and
# the result comes back as a check-run.
#
# Mode: Action mode, fire-and-forget. See specs/04-gha-integration.md.
# Run: playwright-e2e — see specs/02-runs.md#3-playwright-e2e.
name: e2e
on:
pull_request:
paths: ["apps/**", "packages/**"]
jobs:
browser-tests:
runs-on: ubuntu-latest
steps:
- uses: openhackersclub/flare-dispatch-action@v1
with:
run: playwright-e2e
endpoint: ${{ vars.FLAREDISPATCH_ENDPOINT }}
hmac-secret: ${{ secrets.FLAREDISPATCH_HMAC }}
inputs: |
{
"repo": "${{ github.repository }}",
"sha": "${{ github.sha }}",
"baseURL": "https://staging.example.com",
"shards": 4,
"browserMode": "cf-browser-rendering",
"uploadReport": true
}
# Branch protection: require the check-run `flare-dispatch/playwright-e2e`,
# not this GHA job — the job succeeds the moment dispatch is accepted. // Recipe: browser tests — the `playwright-e2e` Run
//
// The typed Run that ./ci.yml dispatches. Shards a Playwright suite with
// Playwright's native --shard flag — one container per shard, all in
// parallel — and uploads a report per shard.
//
// This recipe rides on two primitives — `sharded` (count-and-index fan-out)
// and `workspace` (acquire + clone + cached install) — so the body is just
// "run Playwright on this shard". See specs/03-dsl.md § Primitives.
//
// This is the shipped `playwright-e2e` run, reproduced here so the recipe is
// self-contained. Spec: specs/02-runs.md § 3. DSL: specs/03-dsl.md.
import { Effect, Schema } from "effect";
import { defineRun, step, sandbox, artifact } from "@flare-dispatch/core";
import { sharded, workspace } from "@flare-dispatch/core/primitives";
const Input = Schema.Struct({
repo: Schema.String,
sha: Schema.String,
baseURL: Schema.String,
shards: Schema.optional(Schema.Number), // default 4
project: Schema.optional(Schema.String), // Playwright project name
});
const Output = Schema.Struct({
shards: Schema.Number,
passed: Schema.Number,
failed: Schema.Number,
shardResults: Schema.Array(
Schema.Struct({
index: Schema.Number,
exitCode: Schema.Number,
reportUri: Schema.String,
}),
),
});
export const playwrightE2E = defineRun({
name: "playwright-e2e",
version: "1.0.0",
image: "registry.cloudflare.com/openhackersclub/flare-dispatch-playwright:latest",
inputs: Input,
outputs: Output,
limits: { maxDurationSec: 2400, maxConcurrency: 8, requiresBrowser: true },
run: (input) =>
Effect.gen(function* () {
const shards = input.shards ?? 4;
const projectArg = input.project ? ["--project", input.project] : [];
// One container per shard, all in parallel. `sharded` hands each shard
// its { index, total }; `workspace` does the per-shard checkout +
// cached install. The suite's Playwright config points at CF Browser
// Rendering — `requiresBrowser` declares the binding.
const shardResults = yield* step("run-shards", () =>
sharded({
count: shards,
body: ({ index, total }) =>
Effect.gen(function* () {
const { container, dir } = yield* workspace({
repo: input.repo,
sha: input.sha,
install: true,
});
const exec = yield* sandbox.exec({
cwd: dir,
container,
env: { BASE_URL: input.baseURL },
command: [
"pnpm", "exec", "playwright", "test",
"--shard", `${index}/${total}`,
...projectArg,
],
});
const reportUri = yield* artifact.upload({
name: `playwright-report-${index}`,
path: `${dir}/playwright-report/`,
container,
signedUrlTTL: "30 days",
});
return { index, exitCode: exec.exitCode, reportUri };
}),
}),
);
const failed = shardResults.filter((r) => r.exitCode !== 0).length;
return { shards, passed: shards - failed, failed, shardResults };
}),
});