Testing Tool
TestCafe
Overview
TestCafe is a zero-configuration E2E testing framework developed by DevExpress that provides innovative web application automation without requiring browser driver installation or setup. Written in JavaScript/TypeScript, it supports all modern browsers including Chrome, Firefox, Safari, Edge, and Internet Explorer. With live mode, parallel execution, automatic element waiting, smart assertions, and API testing capabilities, it delivers user-friendly and high-performance End-to-End and integration testing for developers. Running on Node.js and utilizing proprietary proxy server technology, it provides stable cross-browser testing and fast test execution as a next-generation web test automation solution.
Details
TestCafe 2025 edition maximizes developer productivity by maintaining zero-configuration philosophy, eliminating complex setup requirements for E2E testing frameworks. No installation or configuration of WebDriver or browser drivers is needed - tests can be executed immediately after npm installation. Its proprietary proxy server technology controls communication between browsers and test code, fundamentally solving flaky tests commonly encountered in traditional Selenium-based tools. Live mode functionality automatically detects test file changes and re-executes tests in real-time, significantly accelerating development cycles. Parallel execution and concurrency control enable efficient execution of large test suites. Smart assertion functionality automatically waits for element state changes, reducing explicit wait code. Integration of API testing capabilities allows execution of both UI and API tests within a single framework, achieving comprehensive application testing.
Key Features
- Zero Configuration Architecture: Immediate test execution without browser driver or WebDriver setup
- Proprietary Proxy Technology: Stable browser control and flaky test reduction through proxy server
- Live Mode: Automatic detection of test file changes and real-time re-execution
- Parallel Execution and Performance: Simultaneous test execution across multiple browser instances
- Smart Assertions: Automatic element state waiting and dynamic assertions
- API Testing Integration: Unified execution environment for UI and API testing
Pros and Cons
Pros
- Simple setup with zero configuration and no browser driver management required
- High stability and significant flaky test reduction through proprietary proxy technology
- Fast development and debugging cycles achieved through live mode functionality
- Consistent development experience in JavaScript/TypeScript environment leveraging existing skills
- Large-scale test efficiency through parallel execution and concurrency control
- Concise test code writing through smart assertions and automatic waiting
- Support for Chrome Device Emulation, headless mode, and custom browser arguments
- Comprehensive application quality assurance through API testing functionality
Cons
- Limited to JavaScript ecosystem with no support for other languages (Java, Python, C#, etc.)
- Smaller community and ecosystem compared to Playwright or Selenium
- Some operational limitations and need for custom configuration in complex SPA applications
- No support for native mobile app testing or specialized browser environments
- Commercial product considerations for enterprise license management due to DevExpress ownership
- Limited third-party extensions and integration tools compared to Selenium
Reference Links
- TestCafe Official Site
- TestCafe GitHub Repository
- TestCafe Official Documentation
- TestCafe API Reference
- TestCafe Example Repository
- TestCafe Best Practices
Code Examples
Installation and Setup
# Global TestCafe installation
npm install -g testcafe
# Project-local installation
npm install --save-dev testcafe
# Test execution (Chrome browser)
testcafe chrome test1.js
# Headless mode test execution
testcafe chrome:headless tests/sample-fixture.js
# Parallel test execution across multiple browsers
testcafe chrome,firefox tests/
# Live mode for development (automatic file change detection)
testcafe chrome tests/test.js -L
# Specify parallel execution count
testcafe -c 3 chrome tests/test.js
# Debug mode execution
testcafe chrome my-tests/**/*.js --debug-mode
Basic E2E Testing
// test/basic-e2e.js
import { Selector } from 'testcafe';
fixture `Basic E2E Testing`
.page `https://devexpress.github.io/testcafe/example`;
test('Form input and assertions', async t => {
const nameInput = Selector('#developer-name');
const submitButton = Selector('#submit-button');
const articleHeader = Selector('#article-header');
await t
.typeText(nameInput, 'John Smith')
.click(submitButton)
.expect(articleHeader.innerText).eql('Thank you, John Smith!');
});
test('Element state verification', async t => {
const nameInput = Selector('#developer-name');
const triedTestCafeCheckbox = Selector('#tried-test-cafe');
const featureSelect = Selector('#preferred-interface');
await t
// Text input
.typeText(nameInput, 'TestCafe User')
.expect(nameInput.value).eql('TestCafe User')
// Checkbox operations
.click(triedTestCafeCheckbox)
.expect(triedTestCafeCheckbox.checked).ok()
// Select box operations
.click(featureSelect)
.click(featureSelect.find('option').withText('Both'))
.expect(featureSelect.value).eql('Both');
});
test('Conditional testing and error handling', async t => {
const errorMessage = Selector('.error-message');
const submitButton = Selector('#submit-button');
// Submit empty form
await t.click(submitButton);
// Error message verification
if (await errorMessage.exists) {
await t.expect(errorMessage.visible).ok();
await t.expect(errorMessage.innerText).contains('required field');
}
});
Form Operations and Validation
// test/form-validation.js
import { Selector } from 'testcafe';
fixture `Form Validation Testing`
.page `https://example.com/contact`;
test('Complex form operations', async t => {
const form = Selector('#contact-form');
const nameField = Selector('#name');
const emailField = Selector('#email');
const messageField = Selector('#message');
const categorySelect = Selector('#category');
const agreeCheckbox = Selector('#agree-terms');
const submitButton = Selector('#submit');
const successMessage = Selector('.success-message');
await t
// Form element existence verification
.expect(form.exists).ok()
// Input field operations
.typeText(nameField, 'John Doe')
.typeText(emailField, '[email protected]')
.typeText(messageField, 'This is a TestCafe contact test.')
// Select box selection
.click(categorySelect)
.click(categorySelect.find('option').withText('Technical Support'))
// Checkbox operations
.click(agreeCheckbox)
.expect(agreeCheckbox.checked).ok()
// Form submission
.click(submitButton)
// Success message verification
.expect(successMessage.exists).ok()
.expect(successMessage.innerText).contains('Message sent');
});
test('Validation error testing', async t => {
const emailField = Selector('#email');
const submitButton = Selector('#submit');
const emailError = Selector('#email-error');
await t
// Invalid email address input
.typeText(emailField, 'invalid-email')
.click(submitButton)
// Validation error verification
.expect(emailError.exists).ok()
.expect(emailError.innerText).contains('valid email address');
// Correct email address correction
await t
.selectText(emailField)
.typeText(emailField, '[email protected]')
.expect(emailError.exists).notOk();
});
test('Drag and drop operations', async t => {
const fileIcon = Selector('.file-icon');
const dropZone = Selector('.drop-zone');
await t
.dragToElement(fileIcon, dropZone, {
offsetX: 10,
offsetY: 10,
destinationOffsetX: 100,
destinationOffsetY: 50
})
.expect(dropZone.hasClass('file-dropped')).ok();
});
API Testing and UI Testing Integration
// test/api-ui-integration.js
import { Selector, RequestHook } from 'testcafe';
// API request mocking
class MockAPIHook extends RequestHook {
constructor() {
super('https://api.example.com', {
includeHeaders: true,
includeBody: true
});
}
async onRequest(event) {
console.log(`API Request: ${event.request.method} ${event.request.url}`);
}
async onResponse(event) {
console.log(`API Response: ${event.response.statusCode}`);
}
}
const mockAPI = new MockAPIHook();
fixture `API and UI Integration Testing`
.page `https://example.com/dashboard`
.requestHooks(mockAPI);
test('UI API data retrieval test', async t => {
const loadButton = Selector('#load-data');
const dataContainer = Selector('.data-container');
const loadingSpinner = Selector('.loading');
await t
// Click data load button
.click(loadButton)
// Loading state verification
.expect(loadingSpinner.exists).ok()
// Data display verification (API response waiting)
.expect(dataContainer.exists).ok()
.expect(dataContainer.find('.data-item').count).gte(1);
});
test('Direct API request execution', async t => {
// TestCafe API request functionality (v1.20.0 and later)
const responseBody = await t.request('http://localhost:3000/api/users').body;
await t
.expect(responseBody).contains('users')
.expect(JSON.parse(responseBody).length).gte(1);
});
test('UI and API state synchronization test', async t => {
const createButton = Selector('#create-item');
const itemsList = Selector('.items-list');
const newItemTitle = 'Test Item';
// Item creation via UI
await t
.click(createButton)
.typeText('#item-title', newItemTitle)
.click('#save-item');
// UI display verification
await t.expect(itemsList.find('.item').withText(newItemTitle).exists).ok();
// Actual data verification at API endpoint
const apiResponse = await t.request('http://localhost:3000/api/items').body;
const items = JSON.parse(apiResponse);
const createdItem = items.find(item => item.title === newItemTitle);
await t.expect(createdItem).ok();
});
Custom Selectors and Smart Assertions
// test/custom-selectors.js
import { Selector, ClientFunction } from 'testcafe';
// Custom selector definitions
const byDataTestId = (id) => Selector(`[data-testid="${id}"]`);
const byRole = (role) => Selector(`[role="${role}"]`);
// Custom client functions
const getWindowLocation = ClientFunction(() => window.location.toString());
const getBrowserConsoleMessages = ClientFunction(() =>
window.console._logs || []
);
fixture `Custom Selectors and Assertions`
.page `https://example.com/app`;
test('Custom selector utilization', async t => {
const loginButton = byDataTestId('login-button');
const userMenu = byRole('menu');
const headerSection = Selector('header').find('.user-info');
await t
// Element selection using data-testid attribute
.expect(loginButton.exists).ok()
.click(loginButton)
// Element selection using role attribute
.expect(userMenu.visible).ok()
// Nested selectors
.expect(headerSection.find('.username').innerText).eql('testuser');
});
test('Smart assertions and dynamic elements', async t => {
const dynamicContent = Selector('#dynamic-content');
const loadingIndicator = Selector('.loading');
await t
// Wait for loading indicator appearance
.expect(loadingIndicator.exists).ok()
// Automatic wait for dynamic content display
.expect(dynamicContent.innerText).contains('Data loading complete')
// Verify loading indicator disappearance
.expect(loadingIndicator.exists).notOk();
});
test('Browser state verification using ClientFunction', async t => {
const currentLocation = await getWindowLocation();
await t
.expect(currentLocation).eql('https://example.com/app')
// Page navigation
.click('#about-link');
const newLocation = await getWindowLocation();
await t.expect(newLocation).contains('/about');
});
test('Conditional test execution', async t => {
const advancedFeature = Selector('#advanced-feature');
// Conditional branching after element existence verification
if (await advancedFeature.exists) {
await t
.click(advancedFeature)
.expect(Selector('.advanced-panel').visible).ok();
} else {
console.log('Advanced feature not available in this environment');
}
});
Live Mode and Debug Features
// test/debug-features.js
import { Selector } from 'testcafe';
fixture `Debug Feature Utilization`
.page `https://example.com/debug`;
test('Debug feature usage example', async t => {
const debugButton = Selector('#debug-button');
// Debug point setting (execution pause)
await t.debug();
await t
.click(debugButton)
.expect(Selector('#result').innerText).eql('Debug Success');
});
test('Console message verification', async t => {
// Browser console message retrieval
const consoleMessages = await t.getBrowserConsoleMessages();
await t
.click('#generate-error')
.expect(consoleMessages.error).contains('Test error message');
});
test('Screenshot capture', async t => {
await t
.click('#action-button')
.takeScreenshot('action-result.png')
.expect(Selector('#success-message').visible).ok();
});
test('Browser information retrieval', async t => {
// Browser information verification
const isNativeAutomation = t.browser.nativeAutomation;
const engineName = t.browser.engine.name;
console.log(`Browser Engine: ${engineName}`);
console.log(`Native Automation: ${isNativeAutomation}`);
await t.expect(isNativeAutomation).ok();
});
Configuration Files and Advanced Features
// .testcaferc.json
{
"browsers": ["chrome", "firefox"],
"src": ["tests/**/*.js"],
"reporter": [
{
"name": "spec"
},
{
"name": "json",
"output": "reports/report.json"
}
],
"screenshots": {
"path": "screenshots/",
"takeOnFails": true,
"pathPattern": "${DATE}_${TIME}/test-${TEST_INDEX}/${USERAGENT}/${FILE_INDEX}.png"
},
"concurrency": 3,
"selectorTimeout": 10000,
"assertionTimeout": 10000,
"pageLoadTimeout": 8000,
"speed": 0.1,
"debugMode": false,
"skipJsErrors": false,
"quarantineMode": false,
"disablePageReloads": false,
"disablePageCaching": true,
"clientScripts": [
"helpers/mock-date.js",
"helpers/react-helpers.js"
]
}
// testcafe-runner.js - Programmatic execution
const createTestCafe = require('testcafe');
let testcafe = null;
createTestCafe('localhost', 1337, 1338)
.then(tc => {
testcafe = tc;
const runner = testcafe.createRunner();
return runner
.src(['tests/fixture1.js', 'tests/fixture2.js'])
.browsers(['chrome:headless', 'firefox:headless'])
.concurrency(2)
.reporter(['spec', {
name: 'json',
output: 'reports/report.json'
}])
.screenshots({
path: 'screenshots/',
takeOnFails: true
})
.run({
skipJsErrors: true,
debugMode: false,
selectorTimeout: 10000,
assertionTimeout: 10000
});
})
.then(failedCount => {
console.log('Tests failed: ' + failedCount);
testcafe.close();
})
.catch(error => {
console.error('Error during test execution:', error);
if (testcafe) {
testcafe.close();
}
});
CI/CD Integration and Docker Utilization
# .github/workflows/testcafe.yml
name: TestCafe Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
browser: [chrome, firefox]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Start application
run: |
npm start &
npx wait-on http://localhost:3000
- name: Run TestCafe tests
run: testcafe ${{ matrix.browser }}:headless tests/ --reporter spec,json:reports/report-${{ matrix.browser }}.json
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.browser }}
path: |
reports/
screenshots/
# Dockerfile.testcafe
FROM testcafe/testcafe:latest
# Copy custom helper scripts
COPY helpers/ /tests/helpers/
COPY tests/ /tests/
# Execute TestCafe
CMD ["firefox", "/tests/**/*.js", "--reporter", "spec,json:/reports/report.json"]
# Docker Compose execution example
# docker-compose.yml
version: '3.8'
services:
testcafe:
build: .
volumes:
- ./tests:/tests
- ./reports:/reports
depends_on:
- app
environment:
- BASE_URL=http://app:3000
app:
build: ./app
ports:
- "3000:3000"
# Execution command
docker-compose up --abort-on-container-exit