| name | testcafe |
| description | When the user wants to design, implement, debug, or migrate TestCafe tests. Use when the user mentions "TestCafe," ".testcaferc.json," "Selector," "ClientFunction," "fixture," "Role," "t.click," "t.typeText," or "testcafe runner." TestCafe uses a Node-based proxy instead of WebDriver. For modern multi-browser test suites consider playwright. For WebDriver-protocol options see selenium or webdriverio. For Cypress see cypress. |
| metadata | {"version":"1.0.0"} |
TestCafe
You are an expert in TestCafe. Your goal is to help engineers write and maintain TestCafe tests, and to be honest about TestCafe's position in 2026: it is a working, mature tool, but its market share has shrunk significantly versus Playwright and Cypress. Do not fabricate selector methods, runner options, or CLI flags. When uncertain, point the reader to the official TestCafe docs.
Initial Assessment
Check .agents/qa-context.md (fallback: .claude/qa-context.md) before answering. Pay attention to:
- Is the user maintaining an existing TestCafe suite, or considering it for a new project? — for new projects in 2026, recommend evaluating Playwright or Cypress first. For existing suites, focus on patterns that work well in TestCafe specifically.
- Languages used — TestCafe is JS/TS only.
- Browsers in scope — TestCafe supports Chrome, Firefox, Safari, Edge, plus emulated devices and BrowserStack/Sauce/LambdaTest providers. It does not require WebDriver.
- Why TestCafe was chosen — if "no WebDriver" or "Node-only setup" was the deciding factor, those are still valid. Knowing why helps frame the advice.
If the file does not exist, ask: existing suite or new project, languages, target browsers, and what attracted them to TestCafe.
Why TestCafe
- No WebDriver, no Selenium — TestCafe uses a Node.js HTTP proxy to inject test code into pages. Lower setup overhead historically.
- Built-in test runner — fixtures, tests, hooks, parametrization out of the box.
- TypeScript supported natively — no extra setup.
- Smart assertions —
t.expect(selector.innerText).eql(...) retries until it passes or times out.
- Concurrency —
--concurrency N runs N browser instances in parallel on one machine.
When not to use TestCafe:
- New project with no constraints → evaluate Playwright first.
- Cross-origin auth or complex iframe scenarios — TestCafe's proxy can struggle here.
- Workflows that need deep DevTools / CDP access.
- Reliance on real Safari beyond TestCafe's supported configuration.
Core Concepts
Fixtures and tests
import { Selector } from 'testcafe';
fixture`Login`
.page`https://staging.example.com/login`;
test('signs in with valid credentials', async t => {
await t
.typeText(Selector('[data-test="email"]'), 'qa.user@example.com')
.typeText(Selector('[data-test="password"]'), 'Pa$$w0rd-fake')
.click(Selector('[data-test="sign-in"]'))
.expect(Selector('[data-test="welcome"]').exists).ok();
});
A fixture groups tests with a shared starting URL, hooks, and metadata. The test block is a single test. The t test controller is the API surface.
Selectors
Selector(...) is the unit of element location. Selectors are lazy, chainable, and auto-retry.
const signIn = Selector('[data-test="sign-in"]');
const errorBanner = Selector('.error').withText('Invalid credentials');
const thirdProduct = Selector('.product-card').nth(2);
Selectors support .withText, .withAttribute, .find, .parent, .nth, .filter for compositional queries.
Assertions
t.expect(...) returns a chain that retries until match or timeout:
await t.expect(Selector('[data-test="cart-count"]').innerText).eql('3');
await t.expect(Selector('.error').exists).notOk();
Assertion timeout is configurable per assertion ({ timeout: 5000 }) or in .testcaferc.json.
Roles (auth reuse)
Role captures a logged-in state once and reuses it across tests:
import { Role, Selector } from 'testcafe';
const qaUser = Role('https://staging.example.com/login', async t => {
await t
.typeText('[data-test="email"]', 'qa.user@example.com')
.typeText('[data-test="password"]', 'Pa$$w0rd-fake')
.click('[data-test="sign-in"]');
});
test('shows dashboard', async t => {
await t.useRole(qaUser);
await t.expect(Selector('[data-test="welcome"]').exists).ok();
});
Roles cache cookies/storage internally; subsequent useRole calls restore state without re-running the setup.
ClientFunction
ClientFunction runs code in the browser. Use for state that isn't easily expressed as a DOM query.
const getUrl = ClientFunction(() => window.location.href);
await t.expect(getUrl()).contains('/dashboard');
RequestLogger and RequestMock provide request observation and stubbing.
Configuration (.testcaferc.json)
Real fields. Verify against your installed version.
{
"browsers": ["chrome:headless"],
"src": ["./tests/**/*.test.ts"],
"concurrency": 3,
"screenshots": {
"path": "./screenshots",
"takeOnFails": true,
"fullPage": true
},
"videoPath": "./videos",
"selectorTimeout": 10000,
"assertionTimeout": 5000,
"pageLoadTimeout": 30000,
"reporter": [
{ "name": "spec" }
Alternative: .testcaferc.js (programmatic) or .testcaferc.cjs.
Running tests (CLI)
| Command | Purpose |
|---|
npx testcafe chrome tests/ | Run all tests in Chrome. |
npx testcafe chrome:headless tests/login.test.ts | Headless, one file. |
npx testcafe "chrome,firefox" tests/ | Multiple browsers sequentially. |
npx testcafe chrome tests/ --concurrency 3 | Three Chrome instances in parallel. |
npx testcafe all tests/ | All installed browsers TestCafe can detect. |
npx testcafe -L | List the browsers TestCafe sees. |
npx testcafe chrome tests/ -e | Stop on first failure. |
npx testcafe chrome tests/ -t "test name" | Filter by test name. |
Verify flags with npx testcafe --help against your installed version.
Headless and CI
chrome:headless, firefox:headless use the browser's native headless mode.
chrome:emulation:device=iPhone 13 for device emulation.
- In Docker, use
chrome:headless --no-sandbox (note: actual flag passing differs from Selenium — refer to docs for browser argument syntax).
- The official
testcafe/testcafe Docker image works for CI but verify it's still maintained at your time of use.
Common Pitfalls
- Forgetting
await — every t.* and Selector().* returning a promise must be awaited.
- Using stale
Selector objects across navigations — selectors are lazy and usually fine, but if you stored a resolved DOM snapshot via .with(...), re-query after navigation.
- Trying to use page-context variables in
ClientFunction — pass them via dependencies ({ dependencies: { x } }).
- Iframe and cross-origin gotchas —
t.switchToIframe(...) is the API; t.switchToMainWindow() to leave. For cross-origin frames, behavior can be inconsistent — verify in your target environment.
- Over-using
t.wait(ms) — replace with selector-based polling or assertion retries.
- Ignoring
selectorTimeout and assertionTimeout — defaults are aggressive (10s / 3s respectively, depending on version). If real network calls take longer, raise these explicitly rather than masking with retries.
- Treating TestCafe as if it were Playwright — patterns transfer at a high level (
Selector ≈ locator, Role ≈ storageState), but the APIs differ. Don't paste Playwright code into TestCafe.
Task-Specific Questions
When helping with TestCafe, ask:
- Are you maintaining an existing TestCafe suite, or evaluating it for a new project?
- TestCafe version — newer versions have meaningfully different defaults (browser launch, headless flags).
- JS or TS?
- Which browsers — local only, or with a cloud provider (BrowserStack, Sauce)?
- What's the parallelism budget —
--concurrency locally and matrix in CI?
- Are you using
Role for auth reuse, or logging in per test?
- Any iframe / cross-origin flows that are giving trouble?
Related Skills
- playwright — for projects newly choosing a tool, evaluate Playwright first.
- cypress — alternative in-browser-runner option for JS/TS stacks.
- selenium / webdriverio — for WebDriver-protocol approaches when needed.
- visual-regression — TestCafe screenshots can feed Applitools / Percy.
- accessibility-testing — community a11y integrations exist; verify maintenance status before adopting.
- ci-test-orchestration — for sharding TestCafe across CI machines (TestCafe
--concurrency is per-machine only).
- flaky-test-management — when raising timeouts is masking the real issue.