Implementation:DevExpress Testcafe TestCafe Close
| Knowledge Sources | |
|---|---|
| Domains | Testing, Web_Automation |
| Last Updated | 2026-02-12 04:00 GMT |
Overview
Concrete method for gracefully shutting down the TestCafe runtime, ensuring all resources including browser connections, proxy servers, and runners are properly released provided by TestCafe.
Description
The close method orchestrates orderly shutdown of the TestCafe instance by stopping all active runners, disposing the browser provider pool, and closing the browser connection gateway. It implements idempotency protection to prevent errors from double-close calls and uses parallel promise execution to stop multiple runners simultaneously for faster shutdown.
The method is automatically called via an exit hook registered during TestCafe instance creation, ensuring cleanup even when the process terminates unexpectedly. Manual close calls in finally blocks provide explicit control over cleanup timing and enable proper error handling during shutdown.
Usage
Always call close on TestCafe instances when programmatically controlling test execution. Use try-finally blocks to guarantee cleanup even when tests fail or errors occur. The method is essential in long-running processes where TestCafe instances are created and destroyed multiple times, as it prevents resource leaks that can exhaust system limits.
Code Reference
Source Location
- Repository: testcafe
- File: src/testcafe.js
- Lines: 113-123
Signature
async close(): Promise<void>
Import
// close is a method on TestCafe instances
const createTestCafe = require('testcafe');
const testcafe = await createTestCafe();
// ... use testcafe ...
await testcafe.close();
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| None | - | - | close takes no parameters |
Outputs
| Name | Type | Description |
|---|---|---|
| None | Promise<void> | Promise that resolves when all resources are released |
Usage Examples
Basic Cleanup Pattern
const createTestCafe = require('testcafe');
(async () => {
const testcafe = await createTestCafe();
try {
const runner = testcafe.createRunner();
await runner
.src('tests/**/*.js')
.browsers('chrome')
.run();
}
finally {
await testcafe.close();
}
})();
Multiple Runners with Cleanup
const createTestCafe = require('testcafe');
(async () => {
const testcafe = await createTestCafe();
try {
const runner1 = testcafe.createRunner();
const runner2 = testcafe.createRunner();
await Promise.all([
runner1.src('tests/suite1/**/*.js').browsers('chrome').run(),
runner2.src('tests/suite2/**/*.js').browsers('firefox').run()
]);
}
finally {
// Stops both runners and cleans up all resources
await testcafe.close();
}
})();
Long-Running Process with Multiple Sessions
const createTestCafe = require('testcafe');
async function runTestSession(testFiles, browsers) {
const testcafe = await createTestCafe();
try {
const runner = testcafe.createRunner();
const failedCount = await runner
.src(testFiles)
.browsers(browsers)
.run();
return failedCount;
}
finally {
// Essential to prevent resource leaks
await testcafe.close();
}
}
// Can be called multiple times without leaking resources
(async () => {
await runTestSession('tests/smoke/**/*.js', 'chrome:headless');
await runTestSession('tests/integration/**/*.js', 'firefox:headless');
await runTestSession('tests/e2e/**/*.js', 'chrome');
})();
Error Handling During Cleanup
const createTestCafe = require('testcafe');
(async () => {
const testcafe = await createTestCafe();
let testError = null;
try {
const runner = testcafe.createRunner();
await runner
.src('tests/**/*.js')
.browsers('chrome')
.run();
}
catch (error) {
testError = error;
console.error('Test execution error:', error);
}
finally {
try {
await testcafe.close();
}
catch (closeError) {
console.error('Cleanup error:', closeError);
}
// Re-throw original test error if it exists
if (testError) {
throw testError;
}
}
})();
Server Integration
const createTestCafe = require('testcafe');
const express = require('express');
let testcafe = null;
const app = express();
app.post('/run-tests', async (req, res) => {
try {
testcafe = await createTestCafe();
const runner = testcafe.createRunner();
const failedCount = await runner
.src(req.body.testFiles)
.browsers(req.body.browsers)
.run();
res.json({ success: true, failedCount });
}
catch (error) {
res.status(500).json({ success: false, error: error.message });
}
finally {
if (testcafe) {
await testcafe.close();
testcafe = null;
}
}
});
// Cleanup on server shutdown
process.on('SIGTERM', async () => {
if (testcafe) {
await testcafe.close();
}
process.exit(0);
});
app.listen(3000);
Conditional Cleanup
const createTestCafe = require('testcafe');
(async () => {
let testcafe = null;
try {
testcafe = await createTestCafe();
const runner = testcafe.createRunner();
const failedCount = await runner
.src('tests/**/*.js')
.browsers('chrome')
.run();
console.log('Tests completed:', failedCount === 0 ? 'SUCCESS' : 'FAILURE');
}
catch (error) {
console.error('Fatal error:', error);
}
finally {
// Only close if instance was created
if (testcafe) {
await testcafe.close();
}
}
})();
Implementation Details
Cleanup Sequence
async close() {
// Step 1: Idempotency check
if (this.closed)
return;
// Step 2: Mark as closed
this.closed = true;
// Step 3: Stop all runners in parallel
await Promise.all(this.runners.map(runner => runner.stop()));
// Step 4: Dispose browser provider pool
await browserProviderPool.dispose();
// Step 5: Close browser connection gateway
await this.browserConnectionGateway.close();
}
Runner.stop()
The runner.stop() method (src/runner/index.js:806-818) cancels pending task promises:
async stop() {
// Iterate in reverse to handle array mutation during cancellation
const cancellationPromises = this.pendingTaskPromises.reduceRight((result, taskPromise) => {
result.push(taskPromise.cancel());
return result;
}, []);
await Promise.all(cancellationPromises);
}
Each task promise has a cancel method that:
- Stops the Task (aborts browser jobs)
- Disposes browser set (closes browsers)
- Disposes reporters (flushes output)
- Disposes tested app (kills application process)
BrowserProviderPool.dispose()
Disposes all browser provider instances, which:
- Closes browser processes
- Releases browser driver instances
- Cleans up temporary directories
- Releases file system locks
BrowserConnectionGateway.close()
Closes the browser connection gateway, which:
- Terminates WebSocket server
- Closes all active WebSocket connections
- Releases network ports (port1 and port2)
- Stops HTTP server
Idempotency Protection
The closed flag prevents double-close errors:
if (this.closed)
return;
this.closed = true;
Multiple close calls are safe and have no effect after the first call.
Automatic Cleanup Hook
During TestCafe instance creation, an exit hook is registered:
// In src/index.js:66
setupExitHook(cb => testcafe.close().then(cb));
This ensures cleanup even when:
- Process receives SIGTERM or SIGINT
- Unhandled exception occurs
- process.exit() is called
- Node.js event loop completes
Parallel Cleanup
Runners are stopped in parallel for faster shutdown:
await Promise.all(this.runners.map(runner => runner.stop()));
This is safe because runners are independent and don't share mutable state.