◐ Shell
clean mode source ↗

TypeScript

CodeceptJS ships type declarations, so you can write tests, page objects, and custom helpers in TypeScript and get autocomplete and type checking in your editor.

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 (fast, esbuild-based, no tsconfig.json required):

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'].

Test files use the full TypeScript syntax — imports, enums, interfaces, types:

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.

CodeceptJS tests read synchronously even though every I.* call returns a promise:

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:

export const config = {

fullPromiseBased: true,

// ...

}

Rebuild the type definitions:

Now the typings return promises:

await I.amOnPage('/')

await I.click('Login')

await I.see('Hello')

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:

export class CustomHelper extends Helper {

printMessage(msg: string) {

console.log(msg)

}

}

Register it in codecept.conf.ts (helper configuration), run npx codeceptjs def, and steps.d.ts becomes:

/// <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:

type loginPage = typeof import('./loginPage')

type homePage = typeof import('./homePage')

declare namespace CodeceptJS {

interface SupportObject { I: I, loginPage: loginPage, homePage: homePage }

// ...

}

If you use 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:

/// <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:

declare namespace CodeceptJS {

interface CustomLocators {

data: { data: string; value?: number; flag?: boolean }

}

}