TypeScript is installed and your tsconfig.json is ready. The last setup step before migrating files is configuring your test runner to process TypeScript. Cypress, Playwright, and Jest each handle this differently. This lesson covers all three, plus ESLint and path aliases — and explains the relationship between your tsconfig.json and the test runner's own compilation pipeline.
How test runners use TypeScript
Most test runners do not use tsc directly to run your tests. They use their own transpiler under the hood:
- Cypress uses esbuild (Cypress 10+) — fast transpilation, no type checking.
- Playwright uses esbuild (via
@playwright/test) — fast transpilation, no type checking. - Jest uses either Babel (
@babel/preset-typescript) orts-jest— Babel transpiles without checking,ts-jestchecks.
This means: running your tests does not guarantee type correctness. Type errors in test files will not fail a cypress run or npx playwright test unless you run npm run type-check separately as a CI step.
The CI pipeline you're building should run both:
npm run type-check # TypeScript checks — catches type errors
npx playwright test # Test runner — runs the actual testsCypress
Cypress 10+ supports TypeScript natively. If you rename a .cy.js file to .cy.ts, Cypress picks it up automatically — no additional configuration for basic usage.
For correct IDE support and custom type declarations (like the custom commands from TypeScript for QA — Cypress), add a cypress/tsconfig.json:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["cypress", "node"],
"lib": ["ES2022", "DOM"]
},
"include": ["**/*.ts", "**/*.js"]
}"types": ["cypress", "node"] restricts auto-loaded type packages for the Cypress directory. Without it, TypeScript loads all installed @types packages — which can cause conflicts between Cypress's DOM types and Node types.
The custom commands file (cypress/support/commands.ts) needs the interface augmentation you saw in the TypeScript for QA course:
// cypress/support/commands.ts
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable<void>;
seedUser(role: "admin" | "member"): Chainable<string>;
}
}
}
Cypress.Commands.add("login", (email, password) => {
cy.request("POST", "/api/auth/login", { email, password })
.its("body.token")
.then((token) => cy.setCookie("auth_token", token));
});Playwright
Playwright is TypeScript-first. Its generated projects use TypeScript by default. Migrating a JavaScript Playwright project is straightforward:
- Rename
playwright.config.js→playwright.config.ts - Update imports in the config to use ES module syntax
- Rename test files from
.spec.jsto.spec.tsone at a time
The Playwright config in TypeScript:
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
timeout: 30_000,
use: {
baseURL: process.env.BASE_URL ?? "http://localhost:3000",
trace: "on-first-retry",
},
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
],
});For custom fixtures, TypeScript unlocks the full fixture typing pattern from the TypeScript with Playwright lesson:
// fixtures.ts
import { test as base } from "@playwright/test";
import { LoginPage } from "./pages/LoginPage";
type Fixtures = { loginPage: LoginPage };
export const test = base.extend<Fixtures>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
});Jest with ts-jest
Jest's default transformer doesn't understand TypeScript. You have two options:
Option A — ts-jest (type-checks during test runs):
npm install --save-dev ts-jest @types/jest// jest.config.js
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
testMatch: ["**/*.test.ts"],
};Option B — Babel preset (transpiles without type checking, faster):
npm install --save-dev @babel/preset-typescript @babel/preset-env @babel/core babel-jest// babel.config.json
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript"
]
}For migration purposes, ts-jest is preferable: it uses your tsconfig.json and gives you real type errors in Jest test files. The Babel option is faster but provides no type safety — you still need to run npm run type-check separately.
ESLint with TypeScript
TypeScript catches type errors; ESLint enforces code style and catches logical issues that TypeScript cannot. Add both:
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint// .eslintrc.js
module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: { project: "./tsconfig.json" },
plugins: ["@typescript-eslint"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
],
rules: {
"@typescript-eslint/no-explicit-any": "warn", // encourage removing any
"@typescript-eslint/no-unused-vars": "error", // catch dead code
"@typescript-eslint/no-floating-promises": "error", // catches missing await
},
};no-floating-promises is particularly valuable in Playwright and Cypress tests — it catches page.click(selector) without await, which causes tests to pass for the wrong reasons.
Path aliases
As your project grows, relative imports become hard to read:
import { LoginPage } from "../../../pages/LoginPage";Path aliases map shorthand names to directories:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@pages/*": ["./cypress/pages/*"],
"@fixtures/*": ["./cypress/fixtures/*"],
"@support/*": ["./cypress/support/*"]
}
}
}import { LoginPage } from "@pages/LoginPage"; // much cleanerPath aliases in tsconfig.json are honoured by VS Code and tsc, but not automatically by Jest or Node.js. For Jest, add moduleNameMapper to jest.config.js:
module.exports = {
moduleNameMapper: {
"^@pages/(.*)$": "<rootDir>/cypress/pages/$1",
"^@fixtures/(.*)$": "<rootDir>/cypress/fixtures/$1",
},
};⚠️ Common mistakes
- Assuming
cypress runcatches type errors. It doesn't. Cypress uses esbuild, which strips types without checking them. A test file with a type error will run and may pass — the error is invisible until you runtsc --noEmit. Always includetype-checkas a separate CI step before your test run. - Configuring Mocha without
ts-node/register. If you're using Mocha (common in API test projects), it doesn't understand TypeScript by default. Add"require": ["ts-node/register"]to.mocharc.jsonand"extensions": ["ts"]to pick up.tstest files. - Setting
isolatedModules: truewithout reading the implications. Some tools (esbuild, swc) require this flag because they transpile each file independently without global type information. It prohibits certain TypeScript features (const enum, namespace imports). If your setup requires it, be aware of the constraints before you write code that relies on those features.
🎯 Practice task
Configure TypeScript end-to-end for your test runner.
- Pick the test runner you use — Cypress, Playwright, or Jest.
- Follow the setup for that runner from this lesson. Install any required packages.
- Rename one test file from
.jsto.ts— the simplest test file in the project. - Run the test suite (
cypress run,npx playwright test, ornpx jest). The renamed test should still pass. - Now run
npm run type-check. If there are errors in the renamed file, fix them. - Confirm both commands exit successfully: the type check and the test run.
- Stretch: add the
no-floating-promisesESLint rule and runnpx eslinton your converted test file. If the rule flags anything, it's a real bug — a missingawaitthat was silently being ignored.
This is the end of Chapter 2. Your project now has TypeScript installed, a migration-friendly tsconfig.json, a type-check script, and a configured test runner that can process TypeScript files. You're ready to start migrating files in earnest — Chapter 3 covers how.