Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Implementation:DevExpress Testcafe TestCafe Close

From Leeroopedia
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:

  1. Stops the Task (aborts browser jobs)
  2. Disposes browser set (closes browsers)
  3. Disposes reporters (flushes output)
  4. Disposes tested app (kills application process)

BrowserProviderPool.dispose()

Disposes all browser provider instances, which:

  1. Closes browser processes
  2. Releases browser driver instances
  3. Cleans up temporary directories
  4. Releases file system locks

BrowserConnectionGateway.close()

Closes the browser connection gateway, which:

  1. Terminates WebSocket server
  2. Closes all active WebSocket connections
  3. Releases network ports (port1 and port2)
  4. 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.

Related Pages

Implements Principle

Related Implementations

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment