svelte-test¶
Frontend tests for a project's Svelte/JS UI:
vitest—npx vitest run(unit/component). Always-on (MR-gated + change-detected). Runs onnode-tools.playwright:preflight— launches Chromium through the consumer's own@playwright/test. Hard-gated, noallow_failure.playwright— the e2e suite itself.allow_failure: true, andneeds: [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¶
svelte-build,svelte-lint,svelte-security- spec 0076 — the browser image decision