End-to-end tests for Verdaccio across all popular package managers and the web UI.
| Package | Description |
|---|---|
@verdaccio/e2e-cli |
CLI e2e tests (publish, install, audit, etc.) |
@verdaccio/e2e-ui |
Cypress UI e2e tests (home, signin, publish) |
pnpm install
pnpm build
# CLI tests — run against any Verdaccio
./scripts/run-e2e.sh 6 npm
# UI tests — run Cypress against any Verdaccio
./scripts/run-e2e-ui.sh 6
# Full matrix (all PMs x Verdaccio 6)
./scripts/run-e2e-matrix.shA standalone CLI tool that runs the full Verdaccio e2e test suite against any running registry. No test framework dependency — just plain assert.
verdaccio-e2e --registry http://localhost:4873
verdaccio-e2e -r http://localhost:4873 --pm npm --pm pnpm
verdaccio-e2e -r http://localhost:4873 --pm bun --pm deno
verdaccio-e2e -r http://localhost:4873 --test publish --test install
verdaccio-e2e -r http://localhost:4873 --pm yarn-modern=/path/to/yarn.js
verdaccio-e2e -r http://localhost:4873 -v # verbose — shows each command| Option | Description | Default |
|---|---|---|
-r, --registry <url> |
Verdaccio registry URL (required) | — |
--pm <name[=path]> |
Package manager to test (repeatable) | npm |
-t, --test <name> |
Filter tests by name (repeatable) | all supported |
--token <token> |
Auth token (skips user creation) | auto-created |
--timeout <ms> |
Per-test timeout | 50000 |
--uplink-port <port> |
Port for the mock uplink used by scenario:uplink-failure |
$E2E_UPLINK_PORT |
--print-config |
Print the recommended registry config for the full battery and exit | — |
-v, --verbose |
Show each command executed | false |
The suite runs fully offline: every test publishes the packages it consumes, the generated registry config has no npmjs uplink, and the prepared projects disable npm's implicit audit/fund requests. Nothing in the suite depends on npmjs.org being reachable.
The HTTP-protocol scenarios need a registry started with a specific config (max_body_size for the large-tarball tests, a mock uplink for the failure tests). The CLI prints that config so every consumer starts Verdaccio from the same source of truth:
verdaccio-e2e --print-config --uplink-port 4874 > /tmp/verdaccio-e2e/config.yaml
verdaccio --config /tmp/verdaccio-e2e/config.yaml --listen 4873 &
verdaccio-e2e --registry http://localhost:4873 --uplink-port 4874Without --uplink-port, scenario:uplink-failure is skipped and the rest of the suite runs against any plain registry.
| Adapter | --pm value |
Notes |
|---|---|---|
| npm (10-12) | npm |
Uses --registry flag |
| pnpm (10+) | pnpm |
Uses --registry flag |
| Yarn Modern (v3+) | yarn-modern=/path/to/yarn.js |
Uses .yarnrc.yml for registry config |
| Bun | bun |
Uses --registry flag (except info which reads .npmrc) |
| Deno | deno |
Reads registry from .npmrc, install and info only |
Dropped: npm 8/9, pnpm 8/9 and Yarn Classic (v1) are end-of-life and no longer supported by the suite.
| Test | npm | pnpm 10 | pnpm ≥11 | yarn-modern | bun | deno |
|---|---|---|---|---|---|---|
| publish | yes | yes | yes | yes | yes | skip |
| install | yes | yes | yes | yes | yes | yes |
| ci | yes | yes | yes | yes | yes | skip |
| info | yes | yes | yes | yes | yes | yes |
| audit | yes | yes | yes | skip | yes | skip |
| deprecate | yes | yes | yes | yes | skip | skip |
| dist-tags | yes | yes | skip | skip | skip | skip |
| login | skip | skip | skip | yes | skip | skip |
| ping | yes | yes | skip | yes | skip | skip |
| search | yes | yes | skip | skip | skip | skip |
| unpublish | yes | yes | yes | skip | skip | skip |
pnpm ≥11 notes: pnpm v11 reimplemented many commands natively and removed
ping,search, anddist-tag. Un-deprecate uses the newpnpm undeprecatecommand (other package managers usedeprecate pkg ""with an empty message).Bun notes:
bun inforeads the registry from.npmrc(does not accept--registry). All other commands use--registry.Deno notes: Deno reads the registry entirely from
.npmrc. Onlyinstallandinfoare supported.deno infousesnpm:<pkg>specifiers with--node-modules-dir=auto.
Scenarios are complex, multi-step tests that simulate real-world workflows beyond single-command operations. They exercise the registry under realistic conditions — many parallel requests, transitive dependency resolution, version updates, etc.
| Scenario | Description | Requires |
|---|---|---|
scenario:install-multiple-deps |
Publishes a tree of packages (leaf, shared, intermediate with transitive deps), installs them all in a consumer project, verifies metadata, then publishes updated versions and re-installs with semver ranges | publish, install, info |
scenario:minimum-release-age |
Exercises pnpm's minimumReleaseAge cooldown + minimumReleaseAgeExclude globs: excluded fresh packages install, non-excluded ones are blocked |
pnpm 11.1+ |
scenario:tarballs |
HTTP-level tarball battery over a large (~30 MB) package: full download integrity, client aborts mid-download, concurrent downloads, 404s, scoped %2f URLs, end-to-end install |
npm adapter |
scenario:metadata |
HTTP-level packument battery: full and abbreviated (install-v1) metadata shape, ETag/304 revalidation, dist.tarball URL rewriting, 404 error body, coherence after publish/unpublish |
npm adapter |
scenario:search |
Contract battery for GET /-/v1/search: result shape, real total, from/size pagination (local and merged with an uplink), 400 without text, ISO time, size clamp, plus a real npm search on top |
npm adapter (uplink checks need --uplink-port) |
scenario:uplink-failure |
Starts a controllable mock uplink and verifies registry behavior when the upstream is healthy, cuts the connection mid-tarball, is slower than the timeout, or is down | npm adapter, --uplink-port + battery config |
Run a specific scenario:
verdaccio-e2e -r http://localhost:4873 -t scenario:install-multiple-depsSimulates a realistic npm install that triggers many parallel registry requests. The test has four phases:
- Publish seed packages — publishes 5 leaf packages, a shared package, and an intermediate package that depends on some leaves + shared (8 packages total)
- Install all dependencies — creates a consumer project that depends on all packages (including transitive overlap via the intermediate), runs
install - Verify installed packages — asserts every package is in the registry with correct name, version, and dependency metadata
- Update and re-install — publishes v2.0.0 of selected packages, creates a new consumer using
^ranges, installs, and verifiesdist-tags.latestreflects the update
Publishes a large (~30 MB, incompressible) package and exercises the tarball endpoints directly over HTTP:
- full download with
Content-Length+ shasum/integrity verification - repeated client aborts mid-download — the response must terminate, never leave the client hanging, and the registry must survive
- concurrent downloads of the same tarball
- correct 404s for missing package, missing version, and mismatched filename
- scoped packages through
%2f-encoded URLs - a final end-to-end install through the package manager
Packument battery over HTTP:
- full packument shape (
versions,dist-tags,readme,time) - abbreviated metadata via
Accept: application/vnd.npm.install-v1+json— asserts internal fields (_id,_rev,readme) are not leaked, matching the npm registry contract ETag/If-None-Match→304revalidationdist.tarballURLs rewritten to the serving registry- scoped
%2fURLs, 404 error body shape, and metadata coherence after publish/unpublish
Pins the registry.npmjs.org GET /-/v1/search contract (spec + what npm CLI 12 actually consumes). Publishes a set of uniquely-prefixed packages and asserts over raw HTTP:
- result shape npm CLI depends on:
package.name/versionandmaintainersas an array (npm CLI maps it without a guard — a non-array crashesnpm search) totalis the real number of matches, not the size of the returned pagefrom/sizepagination walks the local result set without gaps or overlaps- merged local + uplink pagination happens exactly once — the mock uplink implements
/-/v1/searchthe way npmjs does (it appliesfromitself), so a registry that slices again produces empty or shifted pages - missing
text→ 400ERR_TEXT_MISSING(spec), not an empty 200 timeis an ISO 8601 date-time- oversized
sizerequests are clamped (npmjs caps at 250), not an error - a real
npm search <prefix> --jsonfinds every published package
The contract checks run independently and are all reported before the scenario fails, so a single run lists every divergence at once. The two uplink sub-tests are gated on --uplink-port / E2E_UPLINK_PORT.
Disabled by default: the pending contract checks (search: real
total, 400 withouttext, ISOtime, merged local+uplink pagination,package.versionon results; tarballs:Content-Lengthpresent, no gzip re-compression). They pin the correct npmjs contract but are red against every current Verdaccio. Enable the full battery withE2E_PENDING_CONTRACT_CHECKS=true; make it the default once the registry-side fixes land.
Starts a controllable mock uplink (on --uplink-port) and verifies how the registry behaves when its upstream misbehaves:
- healthy uplink: packages proxy normally
- uplink drops the connection mid-tarball: the client must not hang and the cache must not be poisoned
- uplink slower than the configured timeout: the request fails fast instead of multiplying retries
- uplink down: cached packages are still served, everything else fails cleanly
Requires the registry to be started with the config from --print-config (it wires the mock uplink in). Gated on --uplink-port / E2E_UPLINK_PORT — skipped otherwise.
See docs/cli-tests.md for detailed descriptions of what each test asserts.
import {
allTests,
createBunAdapter,
createDenoAdapter,
createNpmAdapter,
createPnpmAdapter,
runAll,
} from '@verdaccio/e2e-cli';
const adapters = [createNpmAdapter(), createPnpmAdapter(), createBunAdapter(), createDenoAdapter()];
const { results, exitCode } = await runAll(adapters, allTests, 'http://localhost:4873', token, {
timeout: 50000,
concurrency: 1,
});Run only scenarios:
import { allScenarios, createNpmAdapter, runAll } from '@verdaccio/e2e-cli';
const { results, exitCode } = await runAll(
[createNpmAdapter()],
allScenarios,
'http://localhost:4873',
token,
{
timeout: 120000,
concurrency: 1,
}
);A Cypress plugin that provides reusable Verdaccio UI test suites. Run the same tests against any Verdaccio version without copying test files.
npm install @verdaccio/e2e-ui cypresscypress.config.ts
import { defineConfig } from 'cypress';
import { setupVerdaccioTasks } from '@verdaccio/e2e-ui';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:4873',
setupNodeEvents(on) {
setupVerdaccioTasks(on, { registryUrl: 'http://localhost:4873' });
},
},
});cypress/support/e2e.ts
import '@verdaccio/e2e-ui/commands';cypress/e2e/verdaccio.cy.ts
import { createRegistryConfig, registerAllTests } from '@verdaccio/e2e-ui';
const config = createRegistryConfig({ registryUrl: 'http://localhost:4873' });
registerAllTests(config);Or pick individual suites:
import { createRegistryConfig, homeTests, signinTests } from '@verdaccio/e2e-ui';
const config = createRegistryConfig({
registryUrl: 'http://localhost:4873',
title: 'My Verdaccio', // optional, default: 'Verdaccio'
credentials: { user: 'admin', password: 'admin' }, // optional
});
homeTests(config);
signinTests(config);| Suite | Tests |
|---|---|
homeTests |
Page title, help card (empty registry), 404 page |
signinTests |
Login, logout |
publishTests |
Publish package, navigate detail, readme, dependencies, versions, uplinks |
Importing @verdaccio/e2e-ui/commands adds:
| Command | Description |
|---|---|
cy.getByTestId(id) |
Find element by data-testid attribute |
cy.login(user, password) |
Login to Verdaccio UI |
| Export | Description |
|---|---|
setupVerdaccioTasks(on, options) |
Register Cypress tasks |
createRegistryConfig(options) |
Build config with defaults |
registerAllTests(config) |
Register all test suites |
homeTests(config) |
Home page tests |
signinTests(config) |
Login/logout tests |
publishTests(config) |
Package publish + detail tests |
| Script | Description |
|---|---|
./scripts/run-e2e.sh [version] [pm] |
Run CLI tests against a registry (verdaccio or pnpr) |
./scripts/run-e2e-ui.sh [version] |
Run Cypress UI tests against a Verdaccio version |
./scripts/run-e2e-matrix.sh |
Run CLI tests for all detected PMs x verdaccio 6, next-7 and pnpr |
All scripts accept --docker to use Docker images instead of local npm install.
./scripts/run-e2e.sh 6 npm # CLI: verdaccio@6, npm
./scripts/run-e2e.sh next-7 npm # CLI: verdaccio@next-7, npm
./scripts/run-e2e.sh pnpr npm # CLI: @pnpm/pnpr@next, npm
./scripts/run-e2e.sh --docker 6 npm # CLI: Docker verdaccio@6
./scripts/run-e2e-ui.sh 6 # UI: verdaccio@6
./scripts/run-e2e-ui.sh --docker 6 # UI: Docker verdaccio@6
./scripts/run-e2e-ui.sh --open # UI: interactive Cypress
./scripts/run-e2e-matrix.sh # Full CLI matrix
./scripts/run-e2e-matrix.sh --docker # Full CLI matrix via DockerAll packages built with Vite 8 in library mode. Pure ESM, no Babel.
pnpm build # build all tools
pnpm clean # clean build output