Skip to content

test-vitest.yml

Comprehensive JavaScript/TypeScript test execution using Vitest with automatic runtime and package manager detection. Supports Node.js and Bun runtimes with npm, pnpm, yarn, and bun package managers. Skips the test job gracefully when Vitest is not listed as a dependency.

Inputs

InputTypeDescriptionRequiredDefault
RUNTIME_VERSIONstringRuntime version to use. Empty resolves to a per-runtime default (Node.js 24, Bun latest)No""
PACKAGE_MANAGERstringPackage manager (npm, pnpm, yarn, bun). Empty auto-detectsNo""
RUNTIMEstringJavaScript runtime (node, bun). Empty auto-detectsNo""
WORKING_DIRECTORYstringWorking directory for the projectNo"."
TEST_COMMANDstringCustom test command to run (defaults to vitest run)No""
TEST_PRECOMMANDstringCustom command to run before tests (e.g. build)No""
COVERAGEbooleanWhether to collect test coverageNofalse
COVERAGE_REPORTERstringCoverage reporter to use (text, lcov, html, json)No"text"
COVERAGE_ARTIFACT_NAMEstringName of the coverage artifactNounit-tests-coverage
COVERAGE_ARTIFACT_PATHstringPath where to download the coverage artifactNo./coverage
FAIL_ON_ERRORbooleanWhether to fail the workflow on test failuresNotrue
TIMEOUTstringTest timeout in millisecondsNo"60000"

Permissions

ScopeAccessDescription
contentsreadRead source files for tests

Notes

  • Automatic Detection: Intelligently detects package manager and runtime based on lock files and configuration files.
  • Multi-Runtime Support: Works with Node.js and Bun runtimes.
  • Multi-Package Manager Support: Supports npm, pnpm, yarn, and bun package managers.
  • Test File Detection: Automatically detects test files (.test., .spec.) and test directories (test/, tests/, tests/).
  • Vitest Requirement: Skips test execution gracefully when Vitest is not listed as a dependency in package.json.
  • Coverage Support: Optional test coverage collection with configurable reporters.
  • Flexible Test Commands: Uses package.json test script if available, falls back to direct Vitest execution.
  • Package Manager Detection (same logic as lint-js.yml, à la @antfu/ni): an explicit input wins; otherwise the workflow walks upward from WORKING_DIRECTORY to the checkout root, checking at each level for the corepack packageManager field, then lock files, falling back to npm. This means a job scoped to a monorepo sub-package still detects the package manager from the workspace root's lock file. Yarn Berry (.yarnrc.yml) installs with --immutable, classic with --frozen-lockfile.
  • Runtime Detection: explicit input wins, else Bun when the detected package manager is bun, else Node.js.
  • Coverage inputs apply to direct Vitest invocation only: The COVERAGE, COVERAGE_REPORTER, and TIMEOUT inputs only take effect when the workflow invokes Vitest directly (i.e. when package.json has no test script). If package.json defines a test script, the workflow runs that script instead and these inputs are ignored.

Examples

The following examples range from a minimal invocation to customised setups covering coverage upload, alternative runtimes, custom test commands, monorepo paths, and non-blocking mode.

Simple example

Auto-detects Vitest, the runtime, and the package manager from project files — a typical call needs no with: block at all. Pin RUNTIME/PACKAGE_MANAGER/RUNTIME_VERSION only to override detection or lock a specific version.

yaml
jobs:
  test:
    uses: this-is-tobi/github-workflows/.github/workflows/test-vitest.yml@v0
    permissions:
      contents: read

With coverage

COVERAGE: true runs Vitest with the --coverage flag and uploads the report as a GitHub Actions artifact. The default artifact name unit-tests-coverage matches the expected input of scan-sonarqube.yml, making the two workflows easy to chain.

yaml
jobs:
  test:
    uses: this-is-tobi/github-workflows/.github/workflows/test-vitest.yml@v0
    permissions:
      contents: read
    with:
      COVERAGE: true
      COVERAGE_REPORTER: lcov
      RUNTIME_VERSION: "18"

Bun runtime

Sets the runtime and package manager to Bun for faster installs and test runs. Coverage collection remains available via COVERAGE: true.

yaml
jobs:
  test:
    uses: this-is-tobi/github-workflows/.github/workflows/test-vitest.yml@v0
    permissions:
      contents: read
    with:
      RUNTIME: bun
      PACKAGE_MANAGER: bun
      COVERAGE: true

Custom test command

TEST_COMMAND replaces the default vitest run invocation entirely — any shell command is accepted. TIMEOUT controls the per-test timeout in milliseconds passed to Vitest.

yaml
jobs:
  test:
    uses: this-is-tobi/github-workflows/.github/workflows/test-vitest.yml@v0
    permissions:
      contents: read
    with:
      TEST_COMMAND: "npm run test:unit"
      TIMEOUT: "120000"

Monorepo testing

One job per package in parallel, each scoped to its own WORKING_DIRECTORY with its own timeout or coverage settings. Neither job sets PACKAGE_MANAGER: detection walks up to the repository root and finds the single lock file the monorepo's workspace keeps there.

yaml
jobs:
  test-frontend:
    uses: this-is-tobi/github-workflows/.github/workflows/test-vitest.yml@v0
    permissions:
      contents: read
    with:
      WORKING_DIRECTORY: packages/frontend
      COVERAGE: true
      COVERAGE_REPORTER: text

  test-backend:
    uses: this-is-tobi/github-workflows/.github/workflows/test-vitest.yml@v0
    permissions:
      contents: read
    with:
      WORKING_DIRECTORY: packages/backend
      TIMEOUT: "180000"

Pin PACKAGE_MANAGER explicitly only if a sub-package genuinely uses a different package manager than the rest of the repository, or ships its own lock file that should take precedence. A single job at the root (WORKING_DIRECTORY: .) that tests every package is an even simpler alternative for most monorepos — see the monorepo CI example.

Non-blocking tests

The job always succeeds even when tests fail, but coverage is still collected and uploaded. Useful when introducing a test suite into an existing project with known failures.

yaml
jobs:
  test:
    uses: this-is-tobi/github-workflows/.github/workflows/test-vitest.yml@v0
    permissions:
      contents: read
    with:
      FAIL_ON_ERROR: false
      COVERAGE: true
      COVERAGE_REPORTER: lcov