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):
pnpx nuxt-spec ci github
pnpx nuxt-spec ci gitlabnpx nuxt-spec ci github
npx nuxt-spec ci gitlabyarn dlx nuxt-spec ci github
yarn dlx nuxt-spec ci gitlabbunx nuxt-spec ci github
bunx nuxt-spec ci gitlabdeno run -A npm:nuxt-spec ci github
deno run -A npm:nuxt-spec ci gitlabLike 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 (currently1.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:
HEADof theoriginremote (or the first available remote) - it is set when cloning, or viagit remote set-head origin --auto- existing local or remote-tracking
mainormasterbranch - the only local branch, or the current branch if the repository has no commits yet
init.defaultBranchvalue from Git configmainas 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.yamlmust be committed, because dependencies are installed with--frozen-lockfile- it is advised to define the
packageManagerfield in yourpackage.json, so corepack picks the same pnpm version you use locally - the
testandtest-uscripts must exist in yourpackage.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:
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.
| Folder | Content | Handling |
|---|---|---|
**/__baseline__ | baseline images of compareScreenshot | committed by update |
**/__screenshots__ | reference images of Vitest browser mode toMatchScreenshot | committed by update |
**/__snapshots__ | Vitest snapshot files | committed by update |
**/__current__ | actual images of compareScreenshot from the last run | uploaded on failure by verify |
test/__reports__ | HTML test report | uploaded on failure by verify |
.vitest/attachments | Vitest 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.yamlhash) - 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-uand commits the changed baselines asgithub-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:
include:
- local: .gitlab/ci/ci-test-verify.yml
- local: .gitlab/ci/ci-test-update.ymlIf 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_BASELINESleft asfalse) - caches the pnpm store (in
.pnpm-storefolder, keyed bypnpm-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_BASELINESvariable set totrue - runs
pnpm test-uand 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
- You change the UI and push the changes to a feature branch.
- The verify phase fails on the pull/merge request. Download the artifacts and check the HTML report to see the differences.
- If the changes are intended, run the update phase on your feature branch.
- Baselines are committed into your branch (or into a new pull/merge request against it) and the verify phase passes again.