Snapvisor Docs

GitHub Actions recipes

Copy-paste GitHub Actions workflows for Snapvisor with Playwright, plus how builds behave in merge queues, shallow checkouts, fork pull requests and re-runs.

These recipes use @snapvisor/playwright. The CI detection they describe is in @snapvisor/core, checked against 6.6.2 (used by @snapvisor/playwright 7.3.7) and 7.0.0, which behave the same here. The SDK keeps the ARGOS_* environment variable names. For other triggers see Run on a preview deployment.

Baseline workflow

Upload on pull requests, and on pushes to your base branch so the baseline keeps up with main. GitHub sets CI=true, which turns on the upload in the reporter from SDKs.

.github/workflows/visual-tests.yml
name: Visual tests

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

jobs:
  visual-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}

ARGOS_TOKEN is the project token from your project settings. In the connected repository you can drop it and use tokenless authentication or OIDC.

Merge queues

Add the merge_group trigger, and pass GITHUB_TOKEN to the test step so the SDK can look up the pull request.

.github/workflows/visual-tests.yml
on:
  push:
    branches: [main]
  pull_request:
  merge_group:

permissions:
  contents: read
  pull-requests: read

# ...same job as above, with this env on the test step:
#   ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
#   GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

What the SDK does on a merge_group event (the checks_requested action):

  • Commit: GITHUB_SHA, the merge group commit.
  • Branch: the SDK reads the pull request number from the queue branch name (gh-readonly-queue/<base>/pr-<number>-<sha>) and fetches that pull request with GITHUB_TOKEN. The build uploads under the pull request's head branch. Without the token it is not linked to a pull request and uploads under the queue branch name.
  • Flag: the build is marked as a merge queue build and carries the pull request number.

What Snapvisor does with it:

  • Baseline: it resolves the usual baseline from the pull request's base branch, or your project's default base branch when no pull request is linked. Then it applies the changes from the latest approved or unchanged build of the pull request the SDK reports, so changes you approved on the pull request are part of the baseline. The SDK reports one pull request number, taken from the pull request it fetched or from the queue branch name. The build is matched on its branch, so this needs GITHUB_TOKEN: without it the build uploads under the queue branch name and the pull request's approved build is not found.
  • Status check: posted on the merge group commit, not on the pull request head commit. No pull request comment is posted.

Use a project token here. Tokenless authentication compares the commit and branch the SDK sends with the workflow run. When the SDK finds the pull request, it sends that pull request's head commit and branch, not the queue's. That comparison may be rejected.

Full git history

With the main Snapvisor GitHub App connected, Snapvisor finds the merge base on its side and the SDK runs no git commands, so the default actions/checkout depth of 1 is enough. Setting ARGOS_REFERENCE_COMMIT is the exception: the SDK then lists that commit's ancestors with git.

Without the main app (the light install, GitLab or no connection) the SDK finds the merge base itself. It runs git fetch --depth <n> origin for your branch and the base branch at depth 200, then 400, 600 and 800 until the histories meet, and computes the merge base from those fetched copies. The depth of your actions/checkout does not matter here, so the default depth of 1 works when the merge base is within 800 commits of both branch tips, and a full checkout (fetch-depth: 0) does not extend that limit.

When the merge base is further back and the build has no baseline although one exists, set ARGOS_REFERENCE_COMMIT to the commit you want to compare against, or connect the main app. If the log says failed to fetch "<branch>" from origin, the fetch itself failed. Keep the default persist-credentials, because the SDK fetches from origin. To pick the baseline yourself, see Troubleshooting.

Fork pull requests

For a pull_request run from a fork, GitHub withholds your secrets and gives the job a read-only GITHUB_TOKEN. secrets.ARGOS_TOKEN is empty there.

Snapvisor documents tokenless authentication for runs in the connected repository, not forks. The code behind it does not look at forks. It checks that the project allows tokenless authentication, that the workflow run is in progress, and that the run's commit and branch match the upload. Do not plan around tokenless for forks.

Do not switch to pull_request_target and check out the pull request head to get the secret back. That runs code written by the fork author with your secrets and the base repository's token. GitHub calls this a "pwn request"; see Securely using pull_request_target.

The safe option is to skip the visual job for forks. A skipped job reports success, so it does not block the pull request. No build is created, so Snapvisor posts no status on it; do not mark the Snapvisor status as a required check if you skip forks.

jobs:
  visual-tests:
    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
    # ...

Re-running a build

GitHub re-runs keep the same commit and ref and raise GITHUB_RUN_ATTEMPT. The SDK sends the run id and attempt. A workflow_dispatch run is a new run on the ref you pick; the SDK does not link it to a pull request, so set ARGOS_PR_NUMBER if you need that.

  • Single builds: every upload creates a new build, even for the same commit and build name. The earlier build stays in the list.
  • Parallel builds: the nonce is <run id>-<attempt>, so each attempt gets its own build. Uploading to a finalized build fails with Build already finalized.
  • Re-run failed jobs: on attempt 2 or later with the main GitHub App, Snapvisor checks which jobs already succeeded in the previous attempt and were not re-run. It marks the new build partial and copies the missing shards of a parallel build from the previous attempt's build of the same name, so the result is complete.

On this page