Testing
Chef runs tests in a real browser via Playwright. Two types of tests are supported: unit tests (Mocha + Chai) and E2E tests (Playwright Test API). E2E tests can be written for a single extension or at the module level — for scenarios that span several extensions.
Setup
Initialize the test environment:
chef init testsThis creates two files in the project root:
| File | Description |
|---|---|
playwright.config.ts | Playwright config for running unit and E2E tests in browser |
.env.test | Credentials for automatic authentication during tests |
Fill in your local Bitrix installation credentials:
BASE_URL=http://localhost
LOGIN=admin
PASSWORD=your_password| Variable | Description |
|---|---|
BASE_URL | URL of your local Bitrix installation |
LOGIN | Test user login |
PASSWORD | Test user password |
WARNING
Do not commit .env.test to version control — it contains sensitive credentials.
Install Playwright browsers:
npx playwright installIDE Types
mocha, chai and their types are included in Chef and used when running chef test. For IDE autocompletion, install the types locally:
npm install --save-dev @types/mocha @types/chai @playwright/testRunning Tests
# All tests for an extension
chef test vendor.my-extension
# Unit tests only
chef test unit vendor.my-extension
# E2E tests only
chef test e2e vendor.my-extension
# Module-level scenario tests (several extensions)
chef test module crm
# Specific file
chef test unit vendor.my-extension ./utils.test.ts
# Tests matching pattern
chef test vendor.* --grep "should render"
# List tests without running them
chef test vendor.my-extension --list
# Watch mode — rerun on changes
chef test vendor.my-extension -wLive output
During a run chef shows a single per-browser status bar — one look from start to finish. While an engine warms up you see its stage; once tests start, a counter and the test currently running:
○ Chromium starting · ○ Firefox · ○ WebKit · 0.7s
○ Chromium 12/132 · ○ Firefox preparing · ○ WebKit 8/132 · 5.3s
✓ Chromium 132/132 · ○ Firefox 90/132 · ○ WebKit building · 12.1sResults are grouped by describe block: the suite path is a heading printed once, tests indented beneath it. A test that ran in several browsers is one line tagged with every engine (◌ — still running):
ui.notification > Position
✓ has TOP_LEFT [Chromium ✓ · Firefox ✓ · WebKit ◌]
✗ has BOTTOM_RIGHT [Chromium ✗ · Firefox ✓ · WebKit ✓]Tests that passed only after a retry (Playwright's retry) are flagged as flaky:
✓ opens the dialog (passed on attempt 2) [Chromium ✓]And the final summary counts flaky tests separately (Tests 130 passed · 2 flaky).
Debugging
# Open browser with DevTools
chef test vendor.my-extension --debug
# With visible browser window
chef test vendor.my-extension --headed
# In a specific browser
chef test vendor.my-extension --project chromiumIn --debug mode, source maps are enabled and DevTools are opened — you can set breakpoints directly in your TypeScript source code.
Listing without running
--list enumerates the tests that would run, without running them — handy for seeing what a suite contains or picking a pattern for --grep:
chef test vendor.my-extension --listTests are grouped by describe block, with deferred ones (skip/fixme) marked. A Summary at the end breaks the counts down per kind — unit and e2e separately (a module has e2e only) — with the number of tests and how many run / are skipped:
Summary
Unit 132 tests · 132 runnable
E2E 25 tests · 25 runnableWorks for extensions (unit + e2e), modules (chef test module) and every reporter (--reporter default|json|teamcity). It can't be combined with --watch.
Bulk Runs
chef test without arguments or with a glob pattern (im.v2.**) walks through every matching extension. Extensions without tests are skipped silently — only those with tests or problems show up in the output.
Browser console output is hidden by default to keep bulk reports clean. Add --console if you need it:
chef test im.v2.** --consoleTask Statuses
Chef shows the failure reason inline next to each extension:
| Status | Meaning |
|---|---|
✓ Unit tests | All tests passed |
✗ Unit tests (3 failed) | Some tests failed, the number is how many |
✗ Unit tests (build failed) | The test bundle did not compile (Rollup) |
✗ Unit tests (crashed before any tests ran) | Crashed before the first it — usually a setup error |
⚠ Unit tests (no tests collected) | Files exist but Mocha found no it (empty describe, .skip) |
— Unit tests (no test files) | No *.test.{ts,js} in the test/unit/ (or test/) directory |
— E2E tests (no test files) | Same for e2e |
An extension or module with no tests is marked skipped (not passed) — counted separately in the summary.
Final Summary
After the run chef prints an aggregated report:
- Failed Tests (N) — all failed tests with stack traces and code frames, grouped by extension. For e2e, paths to Playwright artifacts (screenshot, video, trace) are printed alongside, grouped by browser — ready to open in your editor.
- Errors (N) — build errors and runtime crashes, one line per cause.
- Issues — list of extensions with error/warning counts.
- Extensions / Tests / Time — totals: how many extensions and tests passed, failed or were skipped, how long it took. The Tests line flags flaky tests (passed after a retry) as a separate count. For
chef test modulethe line is labeled Modules.
Tips
Test Isolation
Each test should be independent. Use beforeEach/afterEach for setup and cleanup:
describe('TodoList', () => {
let list: TodoList;
beforeEach(() => {
list = new TodoList();
});
afterEach(() => {
list.destroy();
});
it('should add item', () => {
list.add('Buy milk');
assert.equal(list.getCount(), 1);
});
it('should start empty', () => {
assert.equal(list.getCount(), 0);
});
});Test Organization
Group tests by functionality:
describe('UserService', () => {
describe('create', () => {
it('should create user with valid data', () => { /* ... */ });
it('should throw on duplicate email', () => { /* ... */ });
});
describe('update', () => {
it('should update user name', () => { /* ... */ });
it('should not allow empty name', () => { /* ... */ });
});
describe('delete', () => {
it('should soft delete user', () => { /* ... */ });
});
});