@gitlab/frontend-tests
v0.1.3
Published
Reusable test infrastructure for GitLab frontend packages
Maintainers
Keywords
Readme
@gitlab/frontend-tests
Reusable test infrastructure for GitLab-owned frontend packages. It includes a shared Jest configuration for unit tests and a frontend integration test setup factory. Each package can set up its test infrastructure without copying files or following many manual steps.
Installation
yarn add --dev @gitlab/frontend-testsDevelopment
The package is written in TypeScript and built with rollup. Tests use vitest. Linting uses ESLint 9 with a flat config and typescript-eslint.
tsconfig.json is the editor and yarn typecheck config and covers the tests. tsconfig.build.json extends it for rollup and excludes the tests so they stay out of dist/.
Relative imports in src/ carry a .js extension (./types.js), which TypeScript maps to the .ts source. The emitted .d.ts files keep those specifiers, which node16 module resolution requires. After rollup, scripts/emit_cjs_types.mjs copies every .d.ts to a .d.cts twin for the require export condition, so CommonJS TypeScript consumers resolve types too. lodash-es is bundled into dist/ because it ships ESM only; the only runtime dependency is @whatwg-node/fetch.
yarn install # install dependencies
yarn test # run tests
yarn build # build dist/ (ESM, CJS and .d.ts)
yarn lint # run ESLint
yarn typecheck # run tsc --noEmitAdding a module
- Add the module under
src/. - Add tests next to it as
*.test.ts. - Re-export the public API from
src/index.ts. Put data shapes that several modules share, or that consumers write themselves, insrc/integration/types.ts. Keep a type that describes one function's parameters or return value next to that function. Do not export implementation details. Name generic parameters with a single letter (T,K,V) or descriptively with aTypesuffix (DataType), never with aTprefix; ESLint enforces this. - Add the new entry to the
inputmap inrollup.config.js, toexportsandtypesVersionsinpackage.json, and add a root resolver stub (see Gotchas). This publishes it as the subpath@gitlab/frontend-tests/<name>.
Integration test core
This package holds the framework core of GitLab's frontend integration tests. These tests mount a real Vue app in jsdom. They intercept GraphQL and REST requests at the network layer with Mock Service Worker (MSW). The core provides fixture loading, fixture variants, request capture and the MSW server wiring. It also provides a Jest config factory and the polyfills MSW needs.
Modules
@gitlab/frontend-tests/integration is the core. It exports:
- Fixture utilities:
loadFixturesMap(basePath),matchFixture,isValidGraphqlFixture,setFixtureData,setFixtureErrors,setFixtureItemsCount,createFixturePaginator.loadFixturesMapreads every*.jsonfile in a directory and keys the results by camelCased operation name. - Fixture variants:
defineFixtureVariants,defineRestFixtureVariants,setQueryVariant,activateVariant,getActiveVariant,getVariant,resetAllVariants,dumpRegistry. - Request capture:
captureRequest,capturedRequests,resetCapturedRequests,snapshotRequests,expectGraphQLCalls,lastRequestVariables,captureMissingOperation,missingOperations,clearMissingOperations. - MSW wiring:
buildHandlers(featureHandlers, restEndpoints)andcreateServer(featureHandlers, restEndpoints).buildHandlerscreates onegraphql.operationhandler that tries each feature resolver in order. It also adds the REST endpoint handlers and a catch-all that answers 400.createServerbuilds anmsw/nodeserver from those handlers. baseMetadata.- Types for consumers:
GraphqlFixture,GraphqlFixtureInput,GraphqlVariables,FeatureHandler,RestEndpoint,RestFixture,FixtureVariants,CapturedRequestandRequestCounts.setQueryVariantderives its activator names from the variant keys, sosetQueryVariant(constant).withError()is checked against the keys passed todefineFixtureVariants.
@gitlab/frontend-tests/integration/polyfills/text-encoding and @gitlab/frontend-tests/integration/polyfills are Jest setupFiles entries, in that order. The first puts TextEncoder and TextDecoder on global. The second adds the web streams, BroadcastChannel, and fetch, Request, Response and Headers from @whatwg-node/fetch.
@gitlab/frontend-tests/integration/jest-config exports createFrontendIntegrationConfig(config) and MSW_ESM_DEPENDENCIES.
Jest configuration
Pass an existing Jest config to the factory. It returns a copy with the integration overrides applied.
const { createFrontendIntegrationConfig } = require('@gitlab/frontend-tests/integration/jest-config');
const baseConfig = require('./jest.config.base');
module.exports = createFrontendIntegrationConfig({
...baseConfig,
setupFilesAfterEnv: ['<rootDir>/spec/integration/test_setup.js'],
});The factory changes these things:
- Adds the ESM-only dependencies of msw (
rettime,until-async,@open-draft/*) to thetransformIgnorePatternsallowlist. - Appends the two polyfills entries to
setupFiles, text encoding first. - Clears
testPathIgnorePatterns. - Disables global fake timers.
- Sets
testTimeoutto 20000 in CI and 7000 locally.
It does not touch setupFilesAfterEnv, module aliases or the test environment. Those stay with the consumer.
msw v2 is a peer dependency, and @jest/types is an optional peer that only TypeScript consumers of jest-config need. @whatwg-node/fetch is the only regular dependency; lodash-es is bundled.
Gotchas
- Load the polyfills through
setupFiles, notsetupFilesAfterEnv.msw/nodereads these globals while its module body evaluates, so they must exist before it loads. Keep the text-encoding entry before the main one:@whatwg-node/fetchneedsTextDecoderat load time, and bundlers hoist imports, so a single file cannot guarantee the order. - The root-level
integration.jsandintegration/*.jsfiles are stubs for resolvers that ignore theexportsfield, such as webpack 4 and the ESLint import resolver built on it. Node and Jest never load them. Add one for every new subpath. - Make sure every import resolves to the same copy of this package. The variant registry and the captured-requests store are module-level singletons. With two copies, a variant activated in a spec is invisible to the handler.
Publishing
The package is published to the public npm registry (registry.npmjs.org), not the GitLab package registry. Only dist/ is published.
Publishing uses npm trusted publishing with OIDC. The publish job gets a short-lived ID token from GitLab CI (id_tokens, audience npm:registry.npmjs.org). No npm token is stored in CI variables. Packages are published with provenance attestations.
The publish job runs only on git tags that match v<digits>, for example v0.1.0. It runs after lint, typecheck, test and build pass.
To release:
- Bump
versioninpackage.jsonin a merge request. - Merge the merge request.
- Create and push a tag
vX.Y.Zonmain. The tag must match the version.
The tag pipeline publishes the package.
One-time setup
This is required before the first CI publish. Someone with publish rights on the @gitlab npm organization must do it.
- Publish the first version by hand from a logged-in machine with
npm publish --access public. The trusted publisher is set on an existing package's settings on npmjs.com, so the package must exist first. - On npmjs.com, open the package Settings and find the Trusted Publisher section. Choose GitLab CI/CD and enter:
- Namespace:
gitlab-org/frontend - Project name:
tests - Top-level CI file path:
.gitlab-ci.yml - Allow
npm publish
- Namespace:
- Optional: under Publishing access, choose "Require two-factor authentication and disallow tokens".
After this, tag pipelines publish on their own.
