Q40 of 40 · JavaScript

How do you lead a JavaScript-to-TypeScript migration for a large automation suite?

JavaScriptLeadjavascripttypescriptmigrationleadershipstrategyci-cd

Short answer

Short answer: Migrate incrementally: enable `allowJs` + `checkJs` first to catch errors without converting. Prioritise shared utilities (Page Objects, fixtures, helpers) over individual tests. Set a per-sprint conversion quota, enforce `noImplicitAny` in converted files, and block new JS files via lint rules once the team reaches proficiency.

Detail

A large-scale JS→TS migration is an engineering programme, not a weekend task. The goal is to capture TypeScript's benefits without stalling feature work or burning the team.

Phase 1 — Enable without converting (Week 1–2):

  • Add tsconfig.json with allowJs: true, checkJs: true, strict: false
  • Add // @ts-check to the highest-churn files first
  • Fix type errors surfaced without renaming files — this gives immediate value

Phase 2 — Convert shared code first (Weeks 3–8):

  • Rename high-value shared modules: Page Objects, fixture factories, API helpers, custom matchers
  • These deliver the most refactoring safety and intellisense benefit per file converted
  • Add // @ts-nocheck as a temporary escape hatch on complex files; remove it per sprint

Phase 3 — Tighten and enforce:

  • Ratchet strict: true file-by-file as each file is cleaned
  • Add an ESLint rule (no-explicit-any, or custom): block regressions on converted files
  • Add a CI gate: tsc --noEmit must pass; new .js files in /tests trigger a warning
  • Training: pair-program TypeScript-heavy refactors with the team

Metrics: Track % files converted, % type errors remaining, and test suite runtime (TypeScript compilation should not meaningfully slow CI).

// EXAMPLE

// tsconfig.json — phased migration settings
// Phase 1: check without converting
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "strict": false,       // loosened to reduce initial noise
    "noImplicitAny": false // will tighten per file
  }
}

// Phase 2: per-file strictness
// Add to each converted file's jsconfig or use override:
// /* @ts-check */
// // @ts-strict  ← per-file strict opt-in

// Phase 3: CI gate
// package.json
{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "lint": "eslint . --rule '@typescript-eslint/no-explicit-any: warn'"
  }
}

// ESLint rule to block new .js test files after migration threshold
// .eslintrc: "no-restricted-syntax": warn on new .js in /tests

// WHAT INTERVIEWERS LOOK FOR

A phased plan with specific milestones — not 'rename all files'. Prioritising shared utilities. Per-file strictness ratcheting. CI gates. Team enablement alongside tooling. The ability to articulate what 'done' looks like for each phase.

// COMMON PITFALL

Setting `strict: true` in tsconfig at the start of migration — it produces hundreds of errors across every file simultaneously, demoralises the team, and often leads to sprinkling `any` everywhere to make it compile, defeating the purpose.