📚 4 min read
TestCafe Guide
TestCafe is a Node.js tool to automate end-to-end web testing. It's easy to set up, works on all popular environments, and provides powerful features for testing modern web apps.
Key Features
- No WebDriver or other testing software required
- Cross-browser support
- Concurrent test execution
- Built-in waiting mechanism
- Page object model support
- Mobile device testing
- Smart assertion query mechanism
- Stable tests
Getting Started
bash
# Install TestCafe
npm install --save-dev testcafe
Core Concepts
Test Structure
typescript
import { Selector, t } from 'testcafe';
fixture('Example Tests')
.page('https://example.com')
.beforeEach(async (t) => {
await t.maximizeWindow();
});
test('Basic test', async (t) => {
await t
.typeText('#email', 'user@example.com')
.typeText('#password', 'password')
.click('#submit')
.expect(Selector('.welcome').exists)
.ok();
});
Selectors
typescript
// Basic selectors
const button = Selector('button');
const input = Selector('#email');
const link = Selector('a').withText('Click me');
// Chaining selectors
const listItem = Selector('ul')
.child('li')
.nth(2)
.withAttribute('data-test', 'item');
// Custom selectors
const customSelector = Selector(() => {
return document.querySelector('.dynamic-class');
});
// React selectors
const reactComponent = Selector('MyComponent').withProps({ active: true });
Actions
typescript
// Mouse actions
await t
.click(button)
.doubleClick(button)
.rightClick(button)
.hover(button)
.drag(element, 200, 0)
.dragToElement(element, target);
// Keyboard actions
await t
.pressKey('tab space')
.pressKey('ctrl+a delete')
.typeText(input, 'Hello')
.selectText(input)
.selectTextAreaContent(textarea);
Advanced Features
Page Model Pattern
typescript
class LoginPage {
private email = Selector('#email');
private password = Selector('#password');
private submitButton = Selector('button[type="submit"]');
private errorMessage = Selector('.error-message');
constructor() {
this.email = Selector('#email');
}
async login(email: string, password: string) {
await t
.typeText(this.email, email)
.typeText(this.password, password)
.click(this.submitButton);
}
async getErrorMessage() {
return this.errorMessage.innerText;
}
}
// Usage
const loginPage = new LoginPage();
test('Login test', async (t) => {
await loginPage.login('user@example.com', 'password');
await t.expect(Selector('.dashboard').exists).ok();
});
Request Hooks
typescript
import { RequestHook, RequestLogger, RequestMock } from 'testcafe';
// Logger
const logger = RequestLogger(/api\/users/, {
logRequestBody: true,
logResponseBody: true,
});
// Mock
const mock = RequestMock()
.onRequestTo(/api\/data/)
.respond({ data: 'mocked' }, 200, {
'Content-Type': 'application/json',
});
// Custom hook
class AuthHook extends RequestHook {
constructor() {
super(/api/);
}
async onRequest(e) {
e.requestOptions.headers['Authorization'] = 'Bearer token';
}
async onResponse(e) {
// Handle response
}
}
fixture('API Tests')
.page('https://example.com')
.requestHooks(logger, mock, new AuthHook());
Client Scripts
typescript
// Inject script
fixture('Client Scripts').page('https://example.com').clientScripts({
path: './scripts/helper.js',
module: true,
});
// Inline script
test('with client script', async (t) => {
await t.eval(() => {
window.localStorage.setItem('key', 'value');
});
const result = await t.eval(() => {
return document.title;
});
});
Testing Patterns
Visual Testing
typescript
import { takeSnapshot } from 'testcafe-blink-diff';
fixture('Visual Tests').page('https://example.com');
test('visual regression', async (t) => {
// Full page snapshot
await takeSnapshot(t, {
name: 'homepage',
fullPage: true,
});
// Element snapshot
const element = Selector('.card');
await takeSnapshot(t, {
name: 'card',
element,
});
});
Mobile Testing
typescript
fixture('Mobile Tests')
.page('https://example.com')
.beforeEach(async (t) => {
await t.resizeWindow(375, 812); // iPhone X
});
test('mobile layout', async (t) => {
// Touch actions
await t
.tap(Selector('.button'))
.doubleTap(Selector('.zoom'))
.hover(Selector('.menu'));
// Orientation
await t.resizeWindowToFitDevice('iphonex', {
portraitOrientation: true,
});
});
API Testing
typescript
import { RequestLogger } from 'testcafe';
const logger = RequestLogger();
fixture('API Integration').page('https://example.com').requestHooks(logger);
test('API calls', async (t) => {
// Trigger API call
await t.click('.load-data');
});
Best Practices
1. Selector Best Practices
typescript
// ❌ Avoid
const button = Selector('button').nth(2);
const div = Selector('div.btn');
// ✅ Prefer
const button = Selector('[data-testid="submit"]');
const input = Selector('input').withAttribute('name', 'email');
const heading = Selector('h1').withText('Welcome');
2. Waiting Strategies
typescript
// ❌ Avoid
await t.wait(5000);
// ✅ Prefer
await t
.expect(Selector('.loader').exists)
.notOk()
.expect(Selector('.content').exists)
.ok()
.expect(Selector('.data').innerText)
.contains('Loaded');
3. Error Handling
typescript
test('with error handling', async (t) => {
try {
await t.click('.non-existent');
} catch (error) {
// Take screenshot
await t.takeScreenshot({
path: `error-${Date.now()}.png`,
fullPage: true,
});
throw error;
}
});
4. Test Organization
typescript
// roles.ts
import { Role } from 'testcafe';
export const adminRole = Role('https://example.com/login', async (t) => {
await t
.typeText('#email', 'admin@example.com')
.typeText('#password', 'admin123')
.click('#submit');
});
// hooks.ts
export const globalHooks = {
beforeEach: async (t) => {
await t.maximizeWindow().setTestSpeed(0.8).setPageLoadTimeout(30000);
},
afterEach: async (t) => {
if (await Selector('.error').exists) {
await t.takeScreenshot();
}
},
};
// test.ts
import { adminRole } from './roles';
import { globalHooks } from './hooks';
fixture('Admin Tests')
.page('https://example.com')
.beforeEach(globalHooks.beforeEach)
.afterEach(globalHooks.afterEach);
test('admin functionality', async (t) => {
await t.useRole(adminRole).expect(Selector('.admin-panel').exists).ok();
});
5. Configuration
typescript
// .testcaferc.json
{
"browsers": ["chrome:headless", "firefox"],
"src": "tests/**/*.ts",
"screenshots": {
"path": "screenshots/",
"takeOnFails": true,
"fullPage": true
},
"reporter": {
"name": "spec",
"output": "reports/report.html"
},
"concurrency": 3,
"selectorTimeout": 10000,
"assertionTimeout": 5000,
"pageLoadTimeout": 30000
}