Project Brief — Migrate a Real-World Cypress Project

10 min read

This capstone puts everything from the course together. You will migrate a complete JavaScript Cypress project to TypeScript — from the first tsconfig.json through to a fully typed suite with strict mode enabled. The project is realistic: it has multiple test files, page objects, custom commands, JSON fixtures, and a configuration file. You will make real decisions about migration strategy, encounter real errors, and produce a result that reflects genuine professional migration work.

The starting project

The project you're migrating is a Cypress test suite for a fictional e-commerce application. Its structure:

cypress/
├── e2e/
│   ├── login.cy.js         (50 lines — happy path + error cases)
│   ├── checkout.cy.js      (120 lines — cart, shipping, payment)
│   ├── search.cy.js        (80 lines — filters, pagination, sorting)
│   ├── profile.cy.js       (60 lines — update profile, change password)
│   └── admin.cy.js         (90 lines — user management, reports)
├── pages/
│   ├── LoginPage.js        (page object, 5 methods)
│   ├── CheckoutPage.js     (page object, 12 methods)
│   └── ProductPage.js      (page object, 8 methods)
├── support/
│   ├── commands.js         (6 custom commands)
│   └── e2e.js              (imports and global hooks)
└── fixtures/
    ├── users.json
    ├── products.json
    └── orders.json
cypress.config.js
package.json

The JavaScript codebase has real characteristics of legacy code:

  • Custom commands without type annotations
  • require() throughout (not ES module import)
  • Page objects with implicit any parameters
  • Fixtures used without any type checking
  • Several places where properties are accessed without null checks

What you're building

By the end of the capstone, you'll have produced:

Infrastructure:

  • tsconfig.json — migration-friendly root config
  • cypress/tsconfig.json — Cypress-specific config
  • npm run type-check script that passes in CI

Types layer:

  • cypress/types/index.ts — all shared interfaces (User, Product, Order, CartItem)
  • cypress/types/cypress.d.ts — type augmentation for all 6 custom commands
  • cypress/types/env.d.ts — typed process.env for the CI environment variables

Migrated page objects:

  • LoginPage.ts, CheckoutPage.ts, ProductPage.ts
  • Each method typed with explicit parameter types and return types
  • Locator properties declared as private readonly in the constructor

Migrated support files:

  • commands.ts — all 6 commands typed
  • e2e.ts — global hooks with typed setup

Migrated test files:

  • All 5 test files converted to .cy.ts
  • Typed fixture usage with cy.fixture<Interface>(...)
  • No remaining any types

Documentation:

  • MIGRATION.md — explains what was changed, what TypeScript settings are active, and how to add new TypeScript tests

Decision 1: Migration strategy

Before writing any code, make a deliberate choice. The project has 13 JavaScript files. Refer to the criteria from Lesson 3 of Chapter 1:

  • File count: 13 files is medium-small — either strategy is viable
  • Team: assume you're the sole QA engineer on this migration, with Playwright/Cypress experience and moderate TypeScript experience
  • CI: the CI pipeline is stable and runs on every PR
  • Timeline: you have two weeks of focused migration time

Based on these factors, which strategy do you choose, and why? Write one paragraph justifying the choice before opening your editor. The next lesson uses the incremental approach — but your reasoning matters more than matching the walkthrough exactly.

Decision 2: Starting strictness

Your tsconfig.json will start permissive and tighten as you go. Map out the sequence you'll follow:

PhasenoImplicitAnystrictNullChecksstrict
Day 1 (setup)falsefalsefalse
After page objectstruefalsefalse
After test filestruetruefalse
Stretch goaltruetruetrue

Write this table somewhere accessible — it's your migration contract with yourself.

What makes this different from earlier exercises

The exercises in earlier lessons asked you to migrate one file at a time with a specific goal. The capstone asks you to make judgment calls across the whole project:

  • Which file do you migrate first, and why?
  • When a type error appears in a file you haven't reached yet, do you fix it immediately or suppress it?
  • When a custom command's return type is ambiguous, do you use Chainable<void> or Chainable<unknown>?
  • When a page object method could return null, do you widen the return type or throw?

These decisions compound. A choice made on Day 2 affects the errors you see on Day 6. The walkthrough in the next lesson shows one path — but the principles, not the exact code, are what matter.

Migration journey
  • – tsconfig.json
  • – cypress/tsconfig.json
  • – type-check script
  • – Domain interfaces
  • – Cypress.Chainable augmentation
  • – ProcessEnv typing
  • – Locator properties typed
  • – Methods: explicit return types
  • – Migrated before test files
  • One .cy.ts at a time –
  • Typed cy.fixture<>() –
  • Run after each rename –

Setting up a sample project

If you don't have an existing Cypress project, set one up now:

mkdir cypress-migration-capstone && cd cypress-migration-capstone
npm init -y
npm install --save-dev cypress
npx cypress open

Create the five test files in cypress/e2e/ and the three page objects in cypress/pages/. Populate them with realistic test code based on what you've written throughout this course — login tests, API calls, form interactions. The quality of the migration practice is proportional to the realism of the starting codebase.

If you've been following the exercises in this course, you already have a test project to use. Import it here.

Deliverables checklist

Use this list to track your progress through the capstone:

Infrastructure
[ ] tsconfig.json created with allowJs: true, strict: false
[ ] cypress/tsconfig.json created with types: ['cypress', 'node']
[ ] npm run type-check added to package.json
[ ] npm run type-check passes with zero errors

Types layer
[ ] interfaces defined: User, Product, Order, CartItem
[ ] custom commands typed in cypress.d.ts (all 6 commands)
[ ] process.env variables typed in env.d.ts

Page objects
[ ] LoginPage.ts — all methods typed, returns explicit
[ ] CheckoutPage.ts — all methods typed, returns explicit
[ ] ProductPage.ts — all methods typed, returns explicit

Support files
[ ] commands.ts — all 6 commands implemented and typed
[ ] e2e.ts — migrated and importing commands.ts

Test files
[ ] login.cy.ts — zero type errors, tests pass
[ ] search.cy.ts — zero type errors, tests pass
[ ] profile.cy.ts — zero type errors, tests pass
[ ] admin.cy.ts — zero type errors, tests pass
[ ] checkout.cy.ts — zero type errors, tests pass

Cleanup
[ ] cypress.config.ts — replaces cypress.config.js
[ ] noImplicitAny: true — zero errors
[ ] strictNullChecks: true — zero errors
[ ] MIGRATION.md written

Stretch goals
[ ] strict: true — zero errors
[ ] Zero explicit any annotations remaining
[ ] ESLint with @typescript-eslint rules configured

Before you start

Read through Chapters 3, 4, and 5 of this course one more time if anything feels unclear — particularly the chapter on function signatures (Chapter 3, Lesson 2) and the Cypress migration lesson (Chapter 5, Lesson 1). The walkthrough in the next lesson assumes you're comfortable with the patterns; it focuses on the sequencing decisions rather than reexplaining the basics.

Then open your editor, start a new git branch (git checkout -b typescript-migration), and begin with Step 1 of the walkthrough.

// tip to track lessons you complete and pick up where you left off across devices.