Web quickstart
Put Greenroom on a web app in four files and one PR.
By the end of this page, every pull request in your repository gets walked by Greenroom and receives a QA handoff. Four steps: install the GitHub App, add the workflow, add two config files, open a PR.
1. Install the GitHub App
Install the Greenroom GitHub App on the repository you want covered. The app requests read access to contents and pull requests, and permission to post the handoff as a check and a comment. It never gets write access to your code, and it never will.
2. Add the workflow
Create .github/workflows/greenroom.yml. For a web app the job deploys or serves a preview of the PR, then hands Greenroom the URL:
name: Greenroom
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
greenroom:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with: { fetch-depth: 0, persist-credentials: false }
- name: Materialize Greenroom policy from the trusted base revision
shell: bash
env:
GREENROOM_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
git show "${GREENROOM_BASE_SHA}:.greenroom/environment.json" > "${RUNNER_TEMP}/greenroom-environment.json"
git show "${GREENROOM_BASE_SHA}:.greenroom/state-contract.json" > "${RUNNER_TEMP}/greenroom-state-contract.json"
- name: Build and serve the preview
run: |
npm ci && npm run build
npx serve -l 4173 dist &
# Pin this action to a reviewed commit SHA.
- uses: justindc100/greenroom@REPLACE_WITH_REVIEWED_COMMIT_SHA
with:
greenroom-api-url: https://app.getgreenroom.io
platform: web
target-url: http://127.0.0.1:4173
# Never trust isolation policy or coverage authority from PR code.
environment-manifest: ${{ runner.temp }}/greenroom-environment.json
state-contract: ${{ runner.temp }}/greenroom-state-contract.json
base-sha: ${{ github.event.pull_request.base.sha }}
head-sha: ${{ github.event.pull_request.head.sha }}
workflow-ref: ${{ github.workflow_ref }}
workflow-sha: ${{ github.workflow_sha }}
pull-request-number: ${{ github.event.pull_request.number }}Two details in that file are deliberate and worth keeping:
- Policy comes from the base revision. The
git show "${GREENROOM_BASE_SHA}:..."step reads your Greenroom config from the PR's base commit, not from the PR itself. A pull request cannot loosen its own allowlist or rewrite its own contract to hide a defect. - Actions are pinned to commit SHAs and
persist-credentials: falsekeeps the checkout token out of later steps.
3. Add the environment manifest
Create .greenroom/environment.json. This is your attestation of what environment Greenroom is allowed to touch:
{
"schemaVersion": "1.0",
"classification": "test",
"isolated": true,
"resetStrategy": "ephemeral_deployment",
"networkControl": "playwright",
"allowedHosts": ["127.0.0.1", "api.example.test", "fonts.googleapis.com", "fonts.gstatic.com"],
"productionHosts": ["api.example.com"],
"sandboxPurchases": false
}Web runs require networkControl: "playwright": the browser itself enforces the allowlist, and any request to a host you did not list ends the walk with a contained violation.
List every host your app actually touches, including font and analytics CDNs. This is the number one first-run failure. A missing fonts.googleapis.com ends the pass after one action with "off-allowlist network request was contained". That behavior is correct, and the fix is to complete your allowlist, not to widen policy.
A host may not appear in both allowedHosts and productionHosts. The manifest is rejected outright if it does, because an allowlisted production host would mean the virtual user can reach real customer data.
The manifest reference documents every field.
4. Add the state contract
Create .greenroom/state-contract.json. The contract tells Greenroom what screens exist and how your router maps source to screens, which lets it scope a diff to exactly the screens it changes:
{
"schemaVersion": "1.0",
"routerKind": "hash-spa",
"graphPaths": ["src/views.js"],
"entryState": "home",
"states": [
{ "id": "home", "route": "#/", "goal": "Reach the home screen" },
{ "id": "checkout", "route": "#/checkout", "goal": "Complete a purchase with a saved cart" }
],
"transitions": [
{ "id": "home-to-checkout", "from": "home", "to": "checkout" }
]
}Ship this file at onboarding, not later. Without it, Greenroom can still walk whatever goals a run requests, but coverage authority stays requested-goal-only and a clean run can never conclude better than inconclusive. The contract is what upgrades "nothing went wrong" to "the changed screens were covered and passed". See the state contract reference.
5. Open a pull request
The first run on a new app begins with a screening pass: Greenroom evaluates its model roster against your actual screens (about a dollar of model spend) and fixes the per-app routing. Every run after that walks the screens your diff touched and posts the handoff. The report anatomy page shows what comes back.