Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Principle:DevExpress Testcafe Tested Application Management

From Leeroopedia
Knowledge Sources
Domains Testing, CI_CD, Web_Automation
Last Updated 2026-02-12 04:00 GMT

Overview

Tested Application Management is the automatic starting and stopping of the application under test as part of the test lifecycle, ensuring the tested server is available before tests begin and cleaned up after tests complete.

Description

End-to-end tests require a running application to test against. Manually starting the application server before tests and remembering to stop it afterward is error-prone and incompatible with CI automation. Tested Application Management solves this by treating the application lifecycle as an integral part of the test run: the test framework automatically spawns the application process, waits for initialization, runs tests, and terminates the application process tree.

Key aspects include:

  • Process Spawning: Starting application via shell command with environment inheritance
  • Initialization Delay: Configurable wait time for application to become ready
  • PATH Augmentation: Adding node_modules/.bin to PATH for local tool resolution
  • Output Capture: Logging application stdout/stderr for debugging
  • Error Detection: Detecting premature application crashes before tests begin
  • Graceful Termination: Killing entire process tree (parent and children) with SIGTERM
  • Crash Suppression: Ignoring application termination errors during explicit shutdown
  • CI Integration: Enabling headless test runs without manual server management

Usage

Use Tested Application Management when:

  • Running end-to-end tests against local development servers
  • Automating test execution in CI/CD pipelines
  • Testing Node.js applications that need to be started before tests
  • Running tests against applications with initialization requirements (database seeding, cache warming)
  • Ensuring clean test environments by starting fresh application instances
  • Debugging application startup issues in test contexts
  • Managing application lifecycle for parallel test runs with different configurations

Theoretical Basis

Core Concept: The application under test is a subprocess with a lifecycle managed by the test framework. The framework acts as a process supervisor, monitoring application health and terminating it regardless of test outcome.

Application Lifecycle States:

NOT_STARTED -> STARTING -> RUNNING -> TERMINATING -> TERMINATED
               |           |          |
               |           |          +-> ERROR (crashed)
               |           +-> READY (after initDelay)
               +-> ERROR (failed to start)

Process Management Algorithm:

CLASS TestedApp:
    process = null
    killed = false
    errorPromise = null

    FUNCTION start(command, initDelay):
        // 1. Spawn application process
        this.process = SPAWN_COMMAND(command, {
            shell: true,
            env: AUGMENT_PATH(process.env),
            stdio: 'pipe'
        })

        // 2. Setup output logging
        this.process.stdout.on('data', data => LOG_STDOUT(data))
        this.process.stderr.on('data', data => LOG_STDERR(data))

        // 3. Setup error detection
        this.errorPromise = WAIT_FOR_PROCESS_EXIT(this.process)
            .then(() => {
                IF NOT this.killed:
                    THROW ERROR("Application crashed unexpectedly")
            })

        // 4. Wait for initialization or error
        AWAIT RACE([
            DELAY(initDelay),      // Normal case: wait for app to initialize
            this.errorPromise      // Error case: app crashed during startup
        ])

        // 5. Application is ready for tests
        RETURN

    FUNCTION kill():
        this.killed = true

        // Kill entire process tree (parent + children)
        AWAIT KILL_PROCESS_TREE(this.process.pid, 'SIGTERM')

        // Process terminated successfully

PATH Augmentation:

// Ensure local node_modules/.bin tools are available
FUNCTION augmentPath(env):
    currentPath = env.PATH || env.Path  // Windows uses 'Path'
    nodeModulesBin = RESOLVE_PATH('./node_modules/.bin')

    // Prepend node_modules/.bin to PATH
    env.PATH = nodeModulesBin + PATH_DELIMITER + currentPath

    RETURN env

// Example:
// Original PATH: /usr/bin:/usr/local/bin
// Augmented PATH: /project/node_modules/.bin:/usr/bin:/usr/local/bin
// Now "webpack" command resolves to local webpack, not global

Initialization Delay Strategy:

// Why initialization delay is needed:
// 1. Server binds to port (fast, ~10ms)
// 2. Database connections established (medium, ~100ms)
// 3. Cache warming, asset compilation (slow, 1-10s)
// 4. Application routes become available

FUNCTION determineInitDelay(app):
    IF app.type == 'static_server':
        RETURN 100  // Very fast startup

    IF app.type == 'express_api':
        RETURN 1000  // Medium startup

    IF app.type == 'full_stack':
        IF app.hasDatabase:
            RETURN 3000  // Slow startup with DB
        ELSE:
            RETURN 1500  // Medium-slow startup

    // Default conservative delay
    RETURN 5000

Process Tree Termination:

// Why tree-kill is necessary:
// - Simple process.kill() only kills parent process
// - Child processes (workers, database connections) remain running
// - Can cause port conflicts in subsequent test runs

FUNCTION killProcessTree(pid, signal):
    // Find all child processes recursively
    children = GET_CHILD_PIDS(pid)

    // Kill children first (depth-first)
    FOR EACH childPid IN REVERSE(children):
        KILL(childPid, signal)

    // Kill parent last
    KILL(pid, signal)

    // Wait for all processes to terminate
    WAIT_FOR_TERMINATION(pid, timeout=5000)

// Example process tree:
// node server.js (PID 1234)
//   ├─ webpack-dev-server (PID 1235)
//   │   └─ webpack worker (PID 1236)
//   └─ express server (PID 1237)
//
// killProcessTree(1234) kills 1236, 1235, 1237, then 1234

Error Handling:

// Three error scenarios:

// 1. Application fails to start (syntax error, missing dependencies)
FUNCTION handleStartupError(error):
    LOG_ERROR("Application failed to start:", error)
    THROW RUNTIME_ERROR.testedAppFailedWithError(error.message)
    // Tests never run

// 2. Application crashes during tests (unhandled exception)
FUNCTION handleRuntimeCrash(error):
    IF this.killed:
        RETURN  // Expected termination, ignore error

    LOG_ERROR("Application crashed during test execution:", error)
    // Current test fails, remaining tests may continue

// 3. Application hangs (initialization never completes)
FUNCTION handleHang(initDelay):
    // initDelay timeout expires, but app not responsive
    LOG_WARNING("Application may not be fully initialized")
    // Tests proceed anyway (may fail due to app not ready)

Related Pages

Implemented By

Page Connections

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