Skip to content

svelte-test

Frontend tests for a project's Svelte/JS UI:

  • vitestnpx vitest run (unit/component). Always-on (MR-gated + change-detected). Runs on node-tools.
  • playwright:preflight — launches Chromium through the consumer's own @playwright/test. Hard-gated, no allow_failure.
  • playwright — the e2e suite itself. allow_failure: true, and needs: [playwright:preflight].

Both e2e jobs are opt-in (heavy) and run on playwright-tools. They stay dormant behind enable_e2e: true + $RUN_E2E == "true", mirroring rust-test's cross-OS jobs, so they don't sit stuck-pending without an e2e setup.

All three use the project-local tools (npm ci then npx), so the consumer carries vitest (+ test config) and, for e2e, @playwright/test. paths is required (no default).

The e2e job no longer installs browsers

It used to run npx playwright install --with-deps. That can never work on the Wolfi-based images. Playwright has no apk path: it detects an unsupported OS, falls back to ubuntu24.04-x64 and shells out to apt-get.

The first error it prints is su: must be suid to work properly, which reads as "the image is non-root" and is a red herring — running as root only moves the failure to sh: apt-get: not found.

The browser is now baked into playwright-tools instead, at a pinned version.

Why the e2e job is split in two

allow_failure: true was written to tolerate a flaky browser test. What it actually absorbed was the browsers never installing — a 12-second failure, before a single test ran, reported as a green pipeline three times, twice on main, while both consumers with e2e enabled shipped with no coverage.

So the two concerns are now separate jobs, and only the suite is soft:

allow_failure catches
playwright:preflight no browser absent, unlaunchable, or version-mismatched against the consumer's @playwright/test
playwright yes a flaky test

The preflight deliberately launches the browser through the consumer's own @playwright/test rather than the image's, because that is the pairing that has to hold: Playwright refuses to drive a browser build it does not recognise. A consumer whose dependency has drifted past the image's pin fails in seconds, with Playwright naming the executable it wanted.

Jobs

Job What it runs
vitest Per path: npm ci && $[[ inputs.test_command ]] (default npx vitest run).
playwright:preflight Per path: npm ci && npx playwright --version, then a launch-and-close of Chromium. Only when enable_e2e: true and e2e_if matches.
playwright Per path: npm ci && $[[ inputs.e2e_command ]] (default npx playwright test). Nothing installs a browser first.

Inputs

Input Type Default Description
image string node-tools:v0.1.1 Image for vitest. Node + npm.
playwright_image string playwright-tools:v0.1.0 Image for both e2e jobs. node-tools plus Chromium's runtime libraries and a baked chromium-headless-shell. A separate image on purpose — see below.
stage string test GitLab CI stage.
paths string (required) Space-separated frontend root(s).
if string '$CI_PIPELINE_SOURCE == "merge_request_event"' Gating rules:if: for vitest.
ignore_install_scripts string "true" Blocks npm lifecycle scripts during dependency installation, via the npm_config_ignore_scripts job variable so every install in the job inherits it. Set "false" only if a dependency needs its install hook (a native module that compiles on install); the project's own prebuild/postbuild are unaffected. See spec 0069.
changes array ["**/*.svelte", "**/*.ts", "**/*.js", "**/*.css", "**/*.html", "**/package.json", "**/package-lock.json", "**/vite.config.*", "**/vitest.config.*", "**/svelte.config.*", "**/tsconfig*.json", ".gitlab-ci.yml"] Change-detection paths.
test_command string "npx vitest run" Command run in each root for unit/component tests.
enable_e2e boolean false Adds both e2e jobs.
e2e_if string '$RUN_E2E == "true" && ($CI_PIPELINE_SOURCE == "merge_request_event" \|\| $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH)' Gating rules:if: for both e2e jobs. An input rather than a hardcoded rule so the e2e path can be self-tested — when it was hardcoded, a child pipeline could never schedule the job, so the component's own self-test only ever covered the disabled path.
e2e_command string "npx playwright test" Command run in each root for the e2e suite.

Why the browser gets its own image

Baking Chromium into node-tools takes it from ~117 MB to roughly 780 MB — a 6.5x inflation of the image that serves svelte-build, svelte-lint and vitest across the estate, to serve one opt-in job that two projects enable. node-tools at 112 MB was the largest proportional saving of the image split at −89%; baking the browser in spends all of it.

playwright-tools bakes chromium-headless-shell (267 MB) rather than full Chromium (655 MB). Playwright selects the headless shell automatically when running headless on the default chromium project, so a consumer's playwright.config.js needs no change.

Version agreement

The browser version is pinned by playwright_image, and the consumer's @playwright/test must agree with it. Upgrading the dependency past the image means bumping playwright_image in the same change. playwright:preflight is what turns that into a fast, clear failure instead of a confusing one.

Usage

include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      paths: "pkg/studio/web"
      test_command: "npx vitest run --passWithNoTests"
      enable_e2e: true

See also