codeceptjs
Version:
Supercharged End 2 End Testing Framework for NodeJS
160 lines (117 loc) • 4.39 kB
Markdown
---
permalink: /typescript
title: TypeScript
---
# TypeScript
CodeceptJS ships [type declarations](https://github.com/codeceptjs/CodeceptJS/tree/master/typings), so you can write tests, page objects, and custom helpers in TypeScript and get autocomplete and type checking in your editor.
## Getting started
`npx codeceptjs init` scaffolds a TypeScript project when you answer **Yes** to:
```
? Do you plan to write tests in TypeScript? Yes
```
It writes `codecept.conf.ts` and `*_test.ts` files. The **config file** and helpers are transpiled automatically. **Test files** need a loader — CodeceptJS 4.x is ESM, and Mocha loads test files through CommonJS hooks, so use [`tsx`](https://tsx.is) (fast, esbuild-based, no `tsconfig.json` required):
```sh
npm i tsx --save-dev
```
```ts
// codecept.conf.ts
export const config = {
tests: './**/*_test.ts',
require: ['tsx/cjs'], // loads the *_test.ts files
helpers: {
Playwright: { url: 'http://localhost', browser: 'chromium' },
},
}
```
Run the tests with `npx codeceptjs run`.
> Adding TypeScript to an existing project: set `"type": "module"` in `package.json`, rename the config to `codecept.conf.ts` with `export const config = {}`, install `tsx`, and add `require: ['tsx/cjs']`.
## Writing tests
Test files use the full TypeScript syntax — imports, enums, interfaces, types:
```ts
// fixtures.ts
export interface User { email: string; password: string }
export const admin: User = { email: 'admin@example.com', password: 's3cret' }
// login_test.ts
import { admin } from './fixtures'
Feature('Login')
Scenario('admin signs in', ({ I }) => {
I.amOnPage('/login')
I.fillField('email', admin.email)
I.fillField('password', admin.password)
I.click('Login')
I.see('Welcome')
})
```
> **Cannot find module** or **Unexpected token** while running tests means the loader isn't wired up — check that `tsx` is installed and `require: ['tsx/cjs']` is in the config.
## Promise-based typings
CodeceptJS tests read synchronously even though every `I.*` call returns a promise:
```ts
I.amOnPage('/')
I.click('Login')
I.see('Hello')
```
The default typings declare these methods as returning `void`, so a linter won't demand `await` on every line. To follow TypeScript conventions and `await` each command instead — some teams find explicit flow control improves stability — enable promise-based typings in `codecept.conf.ts`:
```ts
export const config = {
fullPromiseBased: true,
// ...
}
```
Rebuild the type definitions:
```sh
npx codeceptjs def
```
Now the typings return promises:
```ts
await I.amOnPage('/')
await I.click('Login')
await I.see('Hello')
```
## Types for page objects and custom helpers
`npx codeceptjs def` regenerates `steps.d.ts` from your config — run it after adding a page object or a custom helper so autocomplete picks them up.
For a custom helper:
```ts
// CustomHelper.ts
export class CustomHelper extends Helper {
printMessage(msg: string) {
console.log(msg)
}
}
```
Register it in `codecept.conf.ts` ([helper configuration](/helpers#configuration)), run `npx codeceptjs def`, and `steps.d.ts` becomes:
```ts
/// <reference types='codeceptjs' />
type CustomHelper = import('./CustomHelper')
declare namespace CodeceptJS {
interface SupportObject { I: I }
interface Methods extends Playwright, CustomHelper {}
interface I extends WithTranslation<Methods> {}
}
```
Page objects appear the same way — `def` adds a `type` for each and lists them in `SupportObject`:
```ts
type loginPage = typeof import('./loginPage')
type homePage = typeof import('./homePage')
declare namespace CodeceptJS {
interface SupportObject { I: I, loginPage: loginPage, homePage: homePage }
// ...
}
```
## Types for custom locators
If you use [custom locators](/locators#custom-locators) — for example `I.click({ data: 'user-login' })` — declare their shape in the `CustomLocators` interface in `steps.d.ts` so they're accepted wherever a locator is expected:
```ts
/// <reference types='codeceptjs' />
declare namespace CodeceptJS {
interface CustomLocators {
data: { data: string }
}
}
```
Only the property *types* matter, not the keys. Locators with several (optional) properties work too:
```ts
declare namespace CodeceptJS {
interface CustomLocators {
data: { data: string; value?: number; flag?: boolean }
}
}
```