Skip to content

CI integration ​

Nuxt Spec offers ready-made templates for GitHub Actions and GitLab CI/CD that:

  • verify - run the test suite on every change of the default branch (and on pull/merge requests targeting it)
  • update - regenerate test baselines on demand and commit them back into the repository

Both run inside a pinned Playwright Docker image. Screenshots are sensitive to the operating system, installed fonts and browser builds, so baselines captured on a developer machine will rarely match the ones captured elsewhere. Running both phases in the very same container makes visual comparisons reproducible. As a rule of thumb, baselines should be generated in CI, not locally.

Scaffolding via CLI ​

Use the ci command of the Nuxt Spec CLI tool and pass the target host (github or gitlab):

sh
pnpx nuxt-spec ci github
pnpx nuxt-spec ci gitlab
sh
npx nuxt-spec ci github
npx nuxt-spec ci gitlab
sh
yarn dlx nuxt-spec ci github
yarn dlx nuxt-spec ci gitlab
sh
bunx nuxt-spec ci github
bunx nuxt-spec ci gitlab
sh
deno run -A npm:nuxt-spec ci github
deno run -A npm:nuxt-spec ci gitlab

Like setup and update, it first asks whether to run automatically. Passing true as the next parameter (e.g. pnpx nuxt-spec ci github true) skips all prompts.

The templates are downloaded from the project repository in the version matching your Nuxt Spec. Following placeholders are filled in afterwards:

  • __PLAYWRIGHT_VERSION__ - Playwright version used by Nuxt Spec (currently 1.64.0), used as the container image tag
  • __DEFAULT_BRANCH__ - (GitHub only) default branch of the Git repository in the current folder

The default branch is resolved from the local Git repository in this order:

  1. HEAD of the origin remote (or the first available remote) - it is set when cloning, or via git remote set-head origin --auto
  2. existing local or remote-tracking main or master branch
  3. the only local branch, or the current branch if the repository has no commits yet
  4. init.defaultBranch value from Git config
  5. main as the last resort

The resolved value is printed out, so you can verify it. If the target files already exist, you are asked whether to overwrite them (they are overwritten in auto mode). The only exception is the root .gitlab-ci.yml file, which is never overwritten, only extended (see GitLab). If any file cannot be created (or you decline creating a file that doesn't exist yet), the command stops with an error.

Requirements and assumptions ​

pnpm ​

To keep things simple, the templates always use pnpm (enabled via corepack), regardless of the package manager used for running the CLI tool:

  • pnpm-lock.yaml must be committed, because dependencies are installed with --frozen-lockfile
  • it is advised to define the packageManager field in your package.json, so corepack picks the same pnpm version you use locally
  • the test and test-u scripts must exist in your package.json (they are added by the setup command)

If you use a different package manager, you have to adjust the install and caching steps manually.

Playwright version ​

The Playwright version inside the container image must match the playwright dependency version used by Nuxt Spec. Otherwise, the browser executables won't be found.

The update command of the CLI tool bumps the image tag in the scaffolded files automatically. If you skip it (or if you override the playwright dependency in your project), the image tag must be adjusted manually.

External Playwright server ​

The templates are designed to work without an external Playwright server. Browsers bundled in the container image are used.

If you want to use an external server instead, the NUXT_SPEC_EXTERNAL_PLAYWRIGHT env variable must be available for the test run:

  • GitHub - define a repository variable (or secret) and map it in both workflows on the job level:
.github/workflows/ci-test-verify.yml
yaml
jobs:
  verify:
    env:
      NUXT_SPEC_EXTERNAL_PLAYWRIGHT: ${{ vars.NUXT_SPEC_EXTERNAL_PLAYWRIGHT }}
  • GitLab - define a CI/CD variable named NUXT_SPEC_EXTERNAL_PLAYWRIGHT. GitLab exposes it to all jobs automatically, so no template changes are needed.

The server must be reachable from CI runners and its version must still match the Playwright version used by Nuxt Spec. Since screenshots are then rendered by the remote server, baselines must be regenerated once you switch.

NOTE: If spec.externalPlaywright is set in your nuxt.config.ts, it applies in CI as well. An empty env variable cannot unset it.

Test outputs ​

The templates rely on following conventions instead of fixed paths. Glob patterns are used, so custom locations (e.g. targetDir of compareScreenshot) are supported out of the box. node_modules are always excluded.

FolderContentHandling
**/__baseline__baseline images of compareScreenshotcommitted by update
**/__screenshots__reference images of Vitest browser mode toMatchScreenshotcommitted by update
**/__snapshots__Vitest snapshot filescommitted by update
**/__current__actual images of compareScreenshot from the last runuploaded on failure by verify
test/__reports__HTML test reportuploaded on failure by verify
.vitest/attachmentsVitest attachments (e.g. actual and diff images of toMatchScreenshot)uploaded on failure by verify

The update phase runs the test-u script (vitest run -u), which updates snapshots and screenshot baselines alike. Only files in the folders listed above are committed. Obsolete snapshot files removed by Vitest are deleted from the repository as well.

GitHub ​

Scaffolded files:

  • .github/workflows/ci-test-verify.yml
  • .github/workflows/ci-test-update.yml

Third-party actions are pinned to commit SHAs. Consider enabling Dependabot to keep them up to date.

Verify workflow ​

  • triggered on push and pull request to the default branch, or manually from the Actions tab
  • caches the pnpm store (keyed by pnpm-lock.yaml hash)
  • runs pnpm test
  • if tests fail, uploads the ci-test-report-<run_id>-<attempt> artifact (kept for 14 days)
  • newer runs for the same ref cancel the older ones

Update workflow ​

  • triggered manually from the Actions tab (Run workflow on the selected branch)
  • runs pnpm test-u and commits the changed baselines as github-actions[bot]
  • tries to push directly into the selected branch
  • if the push is rejected (e.g. due to a branch protection or ruleset), it pushes into a new ci-test/update-<timestamp> branch and opens a pull request against the selected branch instead

Prerequisites: For the pull request fallback, enable Settings > Actions > General > Allow GitHub Actions to create and approve pull requests. Without it, creating the pull request fails with HTTP 403.

Limitations: Commits and pull requests created with the default GITHUB_TOKEN don't trigger other workflows. Run the verify workflow manually after the baselines are updated.

GitLab ​

Scaffolded files:

  • .gitlab/ci/ci-test-verify.yml
  • .gitlab/ci/ci-test-update.yml

Both files are then registered in the include section of your root .gitlab-ci.yml:

.gitlab-ci.yml
yaml
include:
  - local: .gitlab/ci/ci-test-verify.yml
  - local: .gitlab/ci/ci-test-update.yml

If the file doesn't exist, it is scaffolded from a template. Otherwise, the entries are appended to your existing include section (a single-value include is converted into a list). The rest of the file, including comments, is kept intact, and entries already present are not duplicated. Note that only the local: form is recognized as a duplicate - if you already include the files differently (e.g. as plain strings), remove one of the entries manually.

Both jobs use the test stage. If you define custom stages, make sure test is among them.

Unlike GitHub, GitLab provides the default branch via the predefined $CI_DEFAULT_BRANCH variable, so no placeholder is needed here.

Verify job ​

  • triggered on push to the default branch and on merge requests targeting it
  • can be started manually via Build > Pipelines > Run pipeline (with NUXT_SPEC_UPDATE_BASELINES left as false)
  • caches the pnpm store (in .pnpm-store folder, keyed by pnpm-lock.yaml)
  • runs pnpm test
  • if tests fail, keeps the job artifacts for 14 days
  • marked as interruptible, so it can be auto-canceled by newer pipelines

Update job ​

  • triggered manually via Build > Pipelines > Run pipeline on the selected branch with the NUXT_SPEC_UPDATE_BASELINES variable set to true
  • runs pnpm test-u and commits the changed baselines on behalf of the user who started the pipeline
  • tries to push directly into the selected branch
  • if the push is rejected (e.g. protected branch), it pushes into a new ci-test/update-<timestamp> branch and creates a merge request against the selected branch using push options
  • only one update job per branch runs at a time (via resource_group)

Prerequisites: The default CI_JOB_TOKEN cannot push into the repository. Create a project access token with the write_repository scope and store it as a masked CI/CD variable named NUXT_SPEC_CI_TOKEN. The role of the token decides whether it may push into protected branches directly. If the variable is marked as protected, it is only available on protected branches.

Unlike on GitHub, pushes made with the project access token trigger new pipelines, so the verify job runs automatically for the updated default branch and for the created merge request.

Typical workflow ​

  1. You change the UI and push the changes to a feature branch.
  2. The verify phase fails on the pull/merge request. Download the artifacts and check the HTML report to see the differences.
  3. If the changes are intended, run the update phase on your feature branch.
  4. Baselines are committed into your branch (or into a new pull/merge request against it) and the verify phase passes again.