Q18 of 38 · TypeScript

What is module augmentation in TypeScript, and when do you use it in a test project?

TypeScriptMidtypescriptmodule-augmentationdeclaration-mergingplaywrightjestcustom-matchers

Short answer

Short answer: Module augmentation extends the types of an existing module without modifying its source. Use it in test projects to add types to Playwright's `test` object for custom fixtures, extend `expect` matchers, add properties to `Window`, or add types for third-party libraries that ship without `.d.ts` files.

Detail

Module augmentation is a TypeScript feature that allows you to add to or modify the type declarations of an existing module via a declare module '...' { ... } block.

How it works: In a .d.ts file or a file with no imports/exports (ambient module), you write declare module 'some-module' { export interface Foo { newProp: string } } and TypeScript merges it with the module's existing types.

Common use cases in test automation:

  1. Custom Playwright fixtures: The recommended pattern — extend test with test.extend<MyFixtures>() and the TypeScript types automatically reflect your fixtures through declaration merging.

  2. Custom Jest matchers: expect.extend({ myMatcher() {} }) at runtime, plus a declaration: declare global { namespace jest { interface Matchers<R> { myMatcher(): R; } } }.

  3. Extending Window: Add properties injected by the browser under test: declare global { interface Window { analytics: Analytics; } }.

  4. Stub types for untyped packages: When a third-party package has no @types, create a .d.ts stub: declare module 'legacy-sdk' { export function init(key: string): void; }.

// EXAMPLE

// 1. Extending Playwright test fixtures (via test.extend)
import { test as base } from "@playwright/test";
import { ApiClient } from "./ApiClient";

export const test = base.extend<{ apiClient: ApiClient }>({
  apiClient: async ({}, use) => {
    await use(new ApiClient());
  },
});
// TypeScript automatically types test({ apiClient }) correctly

// 2. Custom Jest matcher augmentation
// matchers.d.ts
declare global {
  namespace jest {
    interface Matchers<R> {
      toBeWithinRange(floor: number, ceiling: number): R;
    }
  }
}

// 3. Window augmentation for browser globals under test
// window.d.ts
declare global {
  interface Window {
    _analytics: { track(event: string): void };
  }
}
// Now page.evaluate(() => window._analytics.track("login")) is typed

// WHAT INTERVIEWERS LOOK FOR

Understanding the declaration merging mechanism. Practical examples in test context — Playwright fixture extension and Jest custom matchers are the most common. Knowing when to use a `.d.ts` stub for untyped packages.

// COMMON PITFALL

Forgetting that module augmentation files must be modules (have at least one import/export) OR ambient files (no imports/exports). Mixing them produces unexpected behaviour.