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 moduleimport)- 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 configcypress/tsconfig.json— Cypress-specific confignpm run type-checkscript 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 commandscypress/types/env.d.ts— typedprocess.envfor the CI environment variables
Migrated page objects:
LoginPage.ts,CheckoutPage.ts,ProductPage.ts- Each method typed with explicit parameter types and return types
Locatorproperties declared asprivate readonlyin the constructor
Migrated support files:
commands.ts— all 6 commands typede2e.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
anytypes
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:
| Phase | noImplicitAny | strictNullChecks | strict |
|---|---|---|---|
| Day 1 (setup) | false | false | false |
| After page objects | true | false | false |
| After test files | true | true | false |
| Stretch goal | true | true | true |
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>orChainable<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.
- – 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 openCreate 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.