Resource library

QA How-To

TypeScript config for tests: A QA Guide (2026)

Configure TypeScript for tests QA teams maintain, with strict checking, Node and browser types, Playwright and Vitest configs, aliases, CI, and debugging.

21 min read | 3,391 words

TL;DR

A strong TypeScript test config uses strict checking, noEmit, a module and moduleResolution pair that matches the runner, deliberate Node and DOM libraries, narrow include patterns, and a separate CI typecheck command. Extend a shared base, but keep test-only globals and aliases out of production compilation.

Key Takeaways

  • Treat tsconfig as an executable contract for the test project, not as editor-only decoration.
  • Choose NodeNext for code executed as native Node modules and Bundler for Vite-style transformed test code.
  • Enable strict checking and noEmit, then run tsc separately because test runners may transpile without type checking.
  • Control global type packages with types so Jest, Vitest, Node, and DOM declarations do not collide.
  • Keep application and test concerns separate with an extending test config or project references.
  • Verify aliases in TypeScript, the test runner, and the runtime because compiler resolution alone does not rewrite imports.

TypeScript config for tests qa teams can depend on is more than a file that removes editor warnings. It defines which test files belong to the project, which globals exist, how imports resolve, how strictly test data is checked, and whether CI can catch defects before the runner executes them.

The difficult part is alignment. TypeScript, Node.js, Playwright, Vitest, Vite, and the application bundler may each participate in module loading. A configuration can satisfy the editor while failing in CI, or let the runner execute code that the compiler would reject. This guide builds configurations around the actual execution model and shows how to diagnose the gaps.

TL;DR

Project style module moduleResolution Typical runner
Native modern Node ESM NodeNext NodeNext node:test or emitted Node code
Vite-transformed tests ESNext Bundler Vitest
Playwright test repository Often ESNext Often Bundler Playwright Test transform plus separate tsc
Existing CommonJS suite Match the established runtime Match the established runtime Migration should be deliberate

Start from strict: true and noEmit: true. Use explicit test imports when practical, narrow include, add only required global type packages, and run tsc -p tsconfig.tests.json --noEmit as an independent quality gate.

1. TypeScript config for tests QA fundamentals

A tsconfig.json marks the root of a TypeScript project and supplies compiler options plus file selection. The compiler builds a program from files, include, imported dependencies, and declarations. Every option affects that program, not merely the file currently open in an editor.

Test code deserves a clear program boundary because it often sees things application source should not: Node APIs, test-runner globals, browser DOM types, custom matcher augmentation, fixture declarations, and test-data utilities. If all visible types are accepted by one broad root config, accidental globals can compile even though the intended runner does not provide them.

Five questions shape the configuration:

  1. Who executes or transforms the test source?
  2. Is the package ESM or CommonJS at runtime?
  3. Does the source use Node, browser, or both sets of platform APIs?
  4. Are test functions imported explicitly or installed as globals?
  5. Is TypeScript expected to emit files, or only check them?

A browser automation file often uses Node to run and a browser object through library types. That does not automatically mean it needs every DOM global. Playwright's Page type comes from @playwright/test imports. Direct code evaluated in the test's Node process follows Node rules, while code passed to page.evaluate runs inside the page.

Pin TypeScript in devDependencies and invoke the project-local compiler with npx tsc or an npm script. A developer's globally installed compiler can interpret newer options or produce different diagnostics.

If the underlying runtime model is unfamiliar, read Node.js for testers QA fundamentals before tuning module settings.

2. Start with a strict reusable base config

A base config centralizes safety rules that both application and test projects agree on. TypeScript config files accept JSON with comments, commonly called JSONC.

// tsconfig.base.json
{
  "compilerOptions": {
    "target": "ES2022",
    "strict": true,
    "noImplicitOverride": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "useUnknownInCatchVariables": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true
  }
}

strict enables a family of checks, including strict null handling and stronger function checking. Keep it on for test code. A test suite manipulates optional response fields, partial setup, and external data constantly, so disabling null checks hides real false-pass and crash risks.

noUncheckedIndexedAccess adds undefined to undeclared indexed values. It catches code such as assuming rows[0] always exists after a query. exactOptionalPropertyTypes distinguishes an absent optional property from a property present with undefined unless undefined is explicitly allowed. Both can expose old shortcuts, but the resulting fixtures and assertions are more honest.

useUnknownInCatchVariables prevents unguarded access to error.message because JavaScript can throw any value. Narrow with error instanceof Error or a domain type guard.

skipLibCheck skips checking declaration file internals, which can reduce noise and compile time in dependency-heavy repositories. It does not skip checking your source. The tradeoff is that incompatible declarations can remain hidden, so temporarily disable it when investigating type package conflicts or during dependency upgrades.

Do not copy a maximal strictness list without team ownership. Start with strict, add high-value options, fix findings intentionally, and document any exception. A setting ignored through widespread as any casts is not providing safety.

3. Select module and moduleResolution as a pair

module describes module output and interpretation. moduleResolution describes how TypeScript maps import specifiers to files and package declarations. They must match the execution or transform environment.

Resolution mode Use when Important behavior
NodeNext Native Node ESM and CommonJS rules matter Honors package type, exports, and Node file-extension rules
Bundler Vite or another bundler-like transform resolves imports Supports package exports without requiring Node-style relative extensions
Legacy modes Existing project requires them Avoid choosing them for a new suite without a compatibility reason

For native Node ESM:

{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "types": ["node"],
    "noEmit": true
  },
  "include": ["src/**/*.ts", "test/**/*.ts"]
}

With NodeNext and an ESM package, TypeScript may require runtime-compatible relative specifiers such as import { total } from '../src/total.js' even though the source file is total.ts. The specifier describes the emitted runtime file.

For a Vite-transformed project:

{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "noEmit": true
  },
  "include": ["src/**/*.ts", "tests/**/*.ts"]
}

Do not fix a module error by cycling through options until the red line disappears. Check the nearest package.json type field, source extensions, runner transform, config file format, and production build tool. The editor and CI must load the same config.

TypeScript configuration validates imports but does not guarantee the runtime can execute them. The next layer must share the same package and alias assumptions.

4. Configure Node, DOM, and runner types deliberately

The lib option selects built-in declaration libraries such as ECMAScript and DOM APIs. The types option limits which installed @types packages add declarations to the global scope. They solve different problems.

A Node-focused test project can use:

{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "types": ["node"]
  }
}

A Vitest unit project that uses global describe, it, expect, and DOM APIs may use:

{
  "compilerOptions": {
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "types": ["node", "vitest/globals"]
  }
}

Only include vitest/globals if Vitest is configured with globals enabled. Otherwise, import describe, it, and expect from vitest. Explicit imports make dependencies visible and reduce collisions, so they are a strong default.

Jest and Vitest both define familiar global names. Including both type packages can make editor suggestions ambiguous even when only one runner executes the file. Separate projects or use explicit imports.

Playwright test files typically import test, expect, and types from @playwright/test. Package imports carry their declarations, so adding a Playwright global type entry is generally unnecessary. Include Node types when tests use process, Buffer, paths, or file APIs.

Be cautious with DOM libraries in server-only tests. A compile-time global document can exist because DOM declarations were included even though Node will not provide it. For a jsdom or happy-dom Vitest project, the configured environment supplies a simulated DOM. Type availability and runtime availability should say the same thing.

The types list does not prevent imported packages from bringing their own exported types. It controls global inclusion and auto-import visibility from the selected type packages.

5. Create a dedicated tsconfig for tests

Applications and tests have different boundaries. Keep a shared base and add an extending test configuration rather than adding every test declaration to production compilation.

// tsconfig.tests.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "types": ["node"],
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "@app/*": ["src/*"],
      "@test/*": ["tests/support/*"]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "tests/**/*.ts",
    "tests/**/*.tsx",
    "playwright.config.ts",
    "vitest.config.ts"
  ],
  "exclude": ["node_modules", "dist", "test-results", "playwright-report"]
}

The exact lib and runner config files should match the repository. An API-only project may omit DOM. A Vitest project may not include Playwright configuration at all.

include patterns are resolved relative to the config file. Excluding generated report directories avoids accidental type checking of copied artifacts. node_modules is excluded by default in normal situations, but an explicit list documents intent.

The base application config can exclude tests from production output. If both configurations compile shared source with different module assumptions, verify that imports remain valid in both. Do not create two contradictory identities for the same module merely to satisfy tooling.

Project references are useful in large monorepos. A test project can reference a composite application package and use its public declarations. They add build orchestration and should solve a real scaling problem, not become the first configuration for a five-file suite.

Name npm scripts by purpose:

{
  "scripts": {
    "typecheck:tests": "tsc -p tsconfig.tests.json --noEmit",
    "test": "vitest run"
  }
}

The separate script is central. The runner executes behavior, while tsc validates the complete test program.

6. Configure TypeScript for Playwright tests

Playwright Test can load and transform TypeScript directly, so no precompile step is required for normal execution. That convenience does not mean Playwright performs a full TypeScript type check. Run the compiler separately.

A practical Playwright-focused config is:

// tsconfig.playwright.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "types": ["node"],
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "@pages/*": ["tests/pages/*"],
      "@fixtures/*": ["tests/fixtures/*"]
    }
  },
  "include": [
    "playwright.config.ts",
    "tests/**/*.ts"
  ]
}

Then point Playwright to the config if the repository needs an explicit choice:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  tsconfig: './tsconfig.playwright.json',
  use: {
    baseURL: 'http://127.0.0.1:3000',
    trace: 'on-first-retry'
  }
});

Playwright's runtime transform honors a limited subset of TypeScript configuration concerns related to loading, such as path mapping. The compiler remains the authority for strict semantic checking. A CI sequence should therefore run both:

npx tsc -p tsconfig.playwright.json --noEmit
npx playwright test

Keep fixture declarations and page-object types inside the included test tree. Import Playwright types with import type when no runtime value is needed:

import type { Locator, Page } from '@playwright/test';

export class LoginPage {
  readonly email: Locator;

  constructor(private readonly page: Page) {
    this.email = page.getByLabel('Email');
  }
}

The typing Playwright fixtures QA guide shows how the config supports custom test and worker fixtures without any.

7. Configure TypeScript for Vitest

Vitest uses Vite's transform and resolution pipeline, so module: "ESNext" with moduleResolution: "Bundler" is a natural fit for many projects. Keep runtime configuration in vitest.config.ts and compiler configuration in tsconfig. They cooperate but are not interchangeable.

// tsconfig.vitest.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2022"],
    "types": ["node"],
    "noEmit": true
  },
  "include": [
    "src/**/*.ts",
    "tests/**/*.ts",
    "tests/setup.ts",
    "vitest.config.ts"
  ]
}

Use explicit imports in tests:

import { describe, expect, it } from 'vitest';
import { calculateRisk } from '../src/calculate-risk';

describe('calculateRisk', () => {
  it('rejects a negative score', () => {
    expect(() => calculateRisk(-1)).toThrow(RangeError);
  });
});

If the project intentionally uses global APIs, set globals: true in Vitest and add vitest/globals to types. Both halves are required for runtime and compiler agreement.

A DOM-oriented unit suite also needs a DOM test environment and type libraries. Adding "DOM" to lib only makes names type-check. It does not create document at runtime. Configure environment: "jsdom" or another supported environment and install its required package.

Read Vitest setup for QA teams for setup files, environment selection, cleanup, mocks, and coverage. The TypeScript config should describe what exists; Vitest config determines how the tests run.

As with Playwright, run tsc -p tsconfig.vitest.json --noEmit separately. Fast transforms are valuable, but a green test run is not proof that the entire TypeScript program is valid.

8. Path aliases and runtime resolution

Path aliases improve imports when they reflect stable architectural boundaries:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@app/*": ["src/*"],
      "@test/*": ["tests/support/*"]
    }
  }
}

A test can then import:

import { OrderBuilder } from '@test/builders/order-builder';
import { calculateTotal } from '@app/domain/calculate-total';

The compiler uses paths for resolution, but it does not generally rewrite the emitted import specifier. The runtime or transform tool must understand the same alias. Vite-based tools can use top-level resolve.alias configuration or aligned plugins. Playwright can use supported tsconfig path mapping when it loads test source. Native emitted Node code needs a Node-compatible package or import-map strategy rather than assuming TypeScript changed the path.

Before adding aliases, try relative imports and package boundaries. An alias for every folder can hide coupling and make code harder to move. Prefer a few semantic roots.

Test aliases in three places:

  • tsc resolves the imported source and types.
  • The runner can load the module during a real test.
  • Debugging and coverage tools map results back to the source.

If only the editor succeeds, inspect which config it selected. In VS Code, the TypeScript language service may infer a project for files excluded from the intended config. Add the file to the correct include, restart the TypeScript server if needed, and compare the command-line compiler output.

Case sensitivity is another CI trap. macOS development can tolerate an import whose case differs from the filename, while a Linux runner fails. forceConsistentCasingInFileNames and disciplined review reduce the risk.

9. Type test data, fixtures, and environment values

TypeScript improves tests only when external data is validated. A type assertion does not parse or verify runtime JSON:

type User = {
  id: string;
  active: boolean;
};

const user = await response.json() as User;

The cast tells the compiler to trust the author. It does not prove id exists. Validate at the boundary with a type guard or schema library, then let the rest of the test use the narrowed type.

A small guard is runnable without dependencies:

type User = {
  id: string;
  active: boolean;
};

export function isUser(value: unknown): value is User {
  if (typeof value !== 'object' || value === null) {
    return false;
  }

  const record = value as Record<string, unknown>;
  return typeof record.id === 'string' &&
    typeof record.active === 'boolean';
}

export function parseUser(value: unknown): User {
  if (!isUser(value)) {
    throw new TypeError('Response does not match User');
  }
  return value;
}

Use unknown for untrusted fixture and response values. Avoid any because it disables checks across every operation that follows.

Environment variables are string | undefined under Node types. Validate them once:

export function requiredEnv(name: string): string {
  const value = process.env[name];
  if (value === undefined || value.length === 0) {
    throw new Error('Missing environment variable: ' + name);
  }
  return value;
}

Do not silence the error with process.env.API_URL! unless startup has already guaranteed the value. Non-null assertions are claims, not runtime checks.

Builders should make required properties explicit and optional variation deliberate. Partial<T> is useful for overrides, but merging arbitrary partial data can produce an invalid domain object. Validate the final result or model the builder input separately from the server response.

10. Debug tsconfig selection and compiler errors

When editor behavior and CI disagree, first identify the actual config and file set. TypeScript provides diagnostic commands:

npx tsc -p tsconfig.tests.json --showConfig
npx tsc -p tsconfig.tests.json --listFiles
npx tsc -p tsconfig.tests.json --traceResolution

--showConfig prints the resolved configuration after extends processing. --listFiles reveals whether an unexpected global declaration entered the program. --traceResolution is verbose but shows how an import or type reference was resolved.

Common error categories point to different layers:

Symptom Likely check
Cannot find name process Add Node types to the intended test project
Cannot find name describe Import it or align runner globals and types
Cannot find module alias Compare paths, config selection, and runner alias rules
ESM relative import asks for extension Review NodeNext runtime semantics
Duplicate matcher declarations Remove overlapping global runner types
Runner works, tsc fails Fix real type errors or ensure both use the intended project
tsc works, runner cannot load Configure runtime or transform resolution

Do not respond to every missing name by adding another types entry. That can make the editor quiet while broadening globals incorrectly.

Use tsc --noEmit rather than relying on files emitted to a source tree. If build output is required for a native Node test project, use a separate build config with outDir and clear output before compiling. Never let stale JavaScript sit beside TypeScript and be discovered by the runner unexpectedly.

The promises and error handling in tests QA guide is useful when strict catch variables and async return types expose existing test defects.

11. TypeScript config for tests QA in CI and monorepos

CI should install from the lockfile, run the local compiler, and execute tests as separate visible steps. This makes a type failure distinct from a behavioral failure.

name: test-quality

on:
  pull_request:

jobs:
  checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm run typecheck:tests
      - run: npm test

Pin the Node and TypeScript versions through project files and the lockfile. The displayed Node version is an illustrative repository choice. Select a supported runtime compatible with the actual runner.

In a monorepo, each package should own a config that extends an agreed base. Avoid one root include that pulls every application, test suite, generated client, and tool into a single program. Package-level boundaries improve incremental checks and prevent one runner's globals from leaking into another.

Project references can express dependencies:

// tests/tsconfig.json
{
  "extends": "../tsconfig.base.json",
  "compilerOptions": {
    "composite": true,
    "noEmit": true,
    "types": ["node"]
  },
  "references": [
    { "path": "../packages/domain" }
  ],
  "include": ["**/*.ts"]
}

Verify the chosen combination of composite, noEmit, declarations, and build orchestration against the repository's build design. References are not a copy-paste requirement.

Review config changes like code. A new exclude can silently remove tests from type checking. Adding any-heavy declaration shims can make a migration appear complete. A changed module mode can affect every import. Capture the resolved config in debugging, but keep source configuration concise enough for humans to understand.

A dependable pipeline fails if either type checking or behavior fails. One does not substitute for the other.

12. A practical configuration review checklist

Use this checklist before calling a test project complete:

  • The project-local TypeScript version is pinned in the lockfile.
  • extends points to a base with rules the test project actually wants.
  • module and moduleResolution match the runtime or transform tool.
  • target and lib represent supported runtime capabilities.
  • types contains only intentional global type packages.
  • strict and noEmit are enabled for the test type-check command.
  • Test, support, fixture, and runner config files are included.
  • Generated reports and output are excluded.
  • Aliases resolve in TypeScript and in the actual runner.
  • Runtime response and fixture data are validated rather than cast blindly.
  • Playwright or Vitest execution is not mistaken for semantic type checking.
  • CI runs the explicit test config and reports type and test failures separately.

Also run one negative configuration check. Remove Node types temporarily and confirm Node globals fail. Break an alias and confirm both tsc and the runner detect it. Add a wrong fixture field and confirm strict mode rejects it. These checks prove the quality gate is active rather than ceremonial.

Keep the configuration boring. Clever inheritance across many files makes it hard to know which rule applies. A small base plus one runner-specific config is sufficient for many repositories.

When a team must relax a strict option, record the scope and reason. Prefer a localized type guard or adapter to weakening the entire project. Configuration should make correct test code easier to write and incorrect assumptions visible early.

Interview Questions and Answers

Q: What is the purpose of tsconfig.json in a test project?

It defines the TypeScript program, including file membership, language target, module resolution, platform and global declarations, strictness, and emit behavior. In tests it prevents accidental runner globals, invalid fixture shapes, and unresolved imports from reaching runtime. The config should be checked in CI with the project-local compiler.

Q: What is the difference between module and moduleResolution?

module controls how TypeScript interprets or emits modules. moduleResolution controls how an import specifier maps to source and declaration files. They should be selected as a compatible pair that reflects Node or the bundler-like runner.

Q: Why can Playwright run a TypeScript test that tsc rejects?

Playwright transforms TypeScript so it can execute the file, but transformation is not the same as full semantic type checking. The runner can remove type syntax and run valid JavaScript despite noncritical type errors. Teams should run tsc --noEmit separately before Playwright tests.

Q: What does the types option control?

When specified, it limits which visible @types packages add names to the global scope and auto-import suggestions. It does not stop normal imported packages from exposing their exported types. A narrow list prevents Jest, Vitest, Node, and other global declarations from colliding.

Q: When would you choose NodeNext versus Bundler resolution?

Choose NodeNext when native Node module and package rules are the execution contract. Choose Bundler when Vite or a similar transform resolves imports and supports bundler conventions. The package type, source extensions, and runtime must agree with the decision.

Q: Why use unknown instead of any for API JSON?

unknown forces code to validate or narrow the value before reading properties. any disables checking and lets an assumed response shape flow through the suite. A schema or type guard turns runtime evidence into a safe TypeScript type.

Q: How do path aliases fail even when TypeScript accepts them?

paths teaches the compiler how to resolve a specifier, but it does not automatically rewrite that import for every runtime. The test runner, bundler, or Node package design must resolve the same alias. Validate aliases with both tsc and an executed test.

Q: What belongs in a separate test tsconfig?

Test file includes, Node or DOM libraries, runner global types, test-only path aliases, strict no-emit checking, fixture declarations, and runner config files belong there. Production build output and test global declarations can remain isolated from the application project.

Common Mistakes

  • Reusing a broad application config that accidentally includes every test runner's globals.
  • Choosing module and moduleResolution independently.
  • Assuming a green Playwright or Vitest run means TypeScript found no errors.
  • Adding DOM types without configuring a DOM runtime environment.
  • Adding global matcher types while tests use a different runner.
  • Casting API responses with as Type instead of validating unknown data.
  • Using any to silence fixture and configuration problems.
  • Defining paths but not configuring runtime resolution.
  • Excluding a test folder and unknowingly removing it from CI type checking.
  • Running a globally installed tsc instead of the locked project version.
  • Emitting JavaScript beside source and letting the runner discover stale files.
  • Disabling strict mode for the whole suite to avoid fixing a localized adapter.

Most fixes begin with --showConfig, --listFiles, or --traceResolution. Confirm the program TypeScript actually sees, then align the runner rather than guessing at options.

Conclusion

TypeScript config for tests qa teams can trust aligns the compiler with the real runner. Use strict checking, a deliberate module-resolution pair, controlled global types, narrow file selection, and runtime validation for external data.

Create one runner-specific config, add an explicit typecheck:tests command, and place it before behavioral tests in CI. When both gates agree, TypeScript becomes a practical defect detector rather than an editor decoration.

Interview Questions and Answers

Why create a dedicated TypeScript configuration for tests?

Tests often need runner APIs, Node types, DOM declarations, fixture types, and aliases that production source should not inherit. A dedicated config isolates those concerns while extending common strict rules. It also gives CI one explicit type-check target.

Explain module versus moduleResolution.

The module option controls module interpretation and emitted form. moduleResolution controls how TypeScript finds files and declarations for imports. The two must match the native Node or bundler-like execution environment.

Why run tsc when the test runner already handles TypeScript?

Many runners transform TypeScript by stripping types and producing executable JavaScript without performing complete semantic analysis. tsc checks relationships across the whole selected program. Running both catches type defects and behavioral defects.

What problem does the types compiler option solve?

It limits global declarations contributed by visible @types packages. This prevents unrelated runners or frameworks from silently adding names such as expect or describe. Exported types from explicitly imported packages remain available.

How would you type untrusted response JSON?

I would receive it as unknown and validate it with a schema or focused type guard. Only after runtime validation would I expose the domain type to the rest of the test. A direct as-cast provides no runtime evidence.

How do you troubleshoot an alias that works in the editor but not CI?

I would run tsc with showConfig and traceResolution to confirm the selected project and compiler mapping. Then I would verify the CI runner's runtime alias or transform configuration. I would also check case sensitivity and the config file's relative paths.

When is NodeNext the right choice?

NodeNext is appropriate when native Node ESM and CommonJS package semantics define execution. It honors the nearest package type, exports, and runtime-compatible extension rules. I would not use it solely to remove an editor diagnostic in a Vite-transformed project.

What strict options are valuable for test automation?

strict is the foundation. noUncheckedIndexedAccess catches unsafe array and map lookups, exactOptionalPropertyTypes clarifies absent values, and useUnknownInCatchVariables forces safe error narrowing. The team should fix findings rather than bypass them with any.

Frequently Asked Questions

Should tests have a separate tsconfig.json?

Usually yes when tests need Node APIs, DOM libraries, runner globals, or aliases that production code should not see. A small test config can extend a shared strict base while keeping its own include, types, and module settings.

Does Playwright type-check TypeScript tests?

Playwright transforms and runs TypeScript, but its execution is not a replacement for complete semantic type checking. Run the project-local TypeScript compiler with noEmit as a separate local and CI step.

Which moduleResolution should Vitest use?

Bundler is a natural choice for many Vitest projects because Vitest uses Vite's transform and resolution model. The correct choice still depends on the repository, especially if code is also executed directly by Node.

Why is describe not found in my TypeScript test?

Either import describe from the runner package or enable the runner's global mode and include its global type entry. Runtime configuration and TypeScript declarations must match.

Do TypeScript path aliases work automatically at runtime?

No. The paths option controls compiler resolution and does not universally rewrite imports. Configure the runner, bundler, or Node package boundary to resolve the same specifier and verify it with an executed test.

Should test projects enable strict mode?

Yes. Tests handle optional fields, external data, and asynchronous helpers where weak typing can create false passes. Fix localized integration boundaries with validation rather than disabling strictness across the suite.

What does noEmit do in a test tsconfig?

It tells TypeScript to check the program without writing JavaScript or declaration output. This is ideal when Playwright, Vitest, or another transform executes the source directly.

Related Guides