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:Webdriverio Webdriverio BrowserStack Launcher Tunnel

From Leeroopedia

Metadata

Field Value
Page ID BrowserStack_Launcher_Tunnel
Wiki Webdriverio_Webdriverio
Type Implementation (API Doc)
Domains Testing, Cloud, Networking
Knowledge Sources Repo (https://github.com/webdriverio/webdriverio), Doc (https://www.browserstack.com/docs/local-testing)
Related Principles Principle: Local_Tunnel_Connectivity

Overview

Concrete tool for managing local tunnels within the WDIO service lifecycle for cloud testing providers. This document covers the BrowserStack Local tunnel (primary), Sauce Connect tunnel, and TestingBot tunnel implementations. Each service manages the tunnel binary lifecycle: starting in onPrepare (before any tests run), automatically injecting tunnel identifiers into capabilities, and stopping in onComplete (after all tests finish).

Description

The tunnel launcher services follow a consistent pattern across all three providers:

  1. Constructor -- Receives service options and WDIO configuration.
  2. onPrepare -- Checks if tunneling is enabled, configures tunnel options, injects tunnel identifiers into capabilities, starts the tunnel binary, and measures boot time.
  3. onComplete -- Stops the tunnel binary and cleans up.

The BrowserStack implementation is the most feature-rich, with support for local testing, test reporting, Percy visual testing, accessibility automation, and test orchestration. The tunnel management is one component of the larger BrowserstackLauncherService.

Source

File Lines Description
packages/wdio-browserstack-service/src/launcher.ts L70-577 BrowserstackLauncherService class with tunnel management in onPrepare
packages/wdio-browserstack-service/src/launcher.ts L529-577 BrowserStack Local tunnel start logic
packages/wdio-browserstack-service/src/launcher.ts L580-670+ onComplete hook with tunnel stop logic
packages/wdio-sauce-service/src/launcher.ts L20-143 SauceLauncher with Sauce Connect tunnel management
packages/wdio-testingbot-service/src/launcher.ts L12-78 TestingBotLauncher with TestingBot Tunnel management
packages/wdio-browserstack-service/src/types.ts L62-212 BrowserstackConfig interface (tunnel options)
packages/wdio-sauce-service/src/types.ts L4-51 SauceServiceConfig interface (tunnel options)
packages/wdio-testingbot-service/src/types.ts L49-60 TestingbotOptions interface (tunnel options)

BrowserStack Local Tunnel

Configuration

services: [['browserstack', {
    browserstackLocal: boolean,     // Enable/disable the tunnel (default: false)
    forcedStop: boolean,            // Kill tunnel without waiting for callback (default: false)
    opts: Partial<BSOptions>        // BrowserStack Local binary options
}]]

I/O Contract -- Service Configuration:

Parameter Type Default Description
browserstackLocal boolean false Enable local tunnel
forcedStop boolean false Force-kill tunnel binary on completion
opts Partial<BSOptions> {} Binary options (localIdentifier, verbose, proxy, etc.)
opts.localIdentifier string auto-generated Unique identifier for this tunnel instance

Tunnel Start (onPrepare)

// packages/wdio-browserstack-service/src/launcher.ts (L529-577)
// Simplified tunnel start logic:

if (!this._options.browserstackLocal) {
    return BStackLogger.info('browserstackLocal is not enabled - skipping...')
}

const opts = {
    key: this._config.key,
    ...this._options.opts
}

this.browserstackLocal = new BrowserstackLocalLauncher.Local()

// Inject 'local' flag into all capabilities
this._updateCaps(capabilities, 'local')
if (opts.localIdentifier) {
    this._updateCaps(capabilities, 'localIdentifier', opts.localIdentifier)
}

// Start with 60-second timeout
performance.mark('tbTunnelStart')
return Promise.race([
    promisify(this.browserstackLocal.start.bind(this.browserstackLocal))(opts),
    new Promise((resolve, reject) => {
        timer = setTimeout(function () {
            reject('Browserstack Local failed to start within 60 seconds!')
        }, 60000)
    })
])

I/O Contract -- onPrepare:

Input Type Description
config Options.Testrunner WDIO configuration (includes key for authentication)
capabilities Capabilities.TestrunnerCapabilities Capabilities array (mutated to add tunnel flags)
Output Type Description
Returns Promise<void> Resolves when tunnel is ready, rejects on timeout (60s) or error
Side effects Capability mutation Adds local: true and localIdentifier to all capabilities

Tunnel Stop (onComplete)

// packages/wdio-browserstack-service/src/launcher.ts (L653-670+)
if (!this.browserstackLocal || !this.browserstackLocal.isRunning()) {
    return
}

const pid = this.browserstackLocal.pid
this.browserstackLocal.stop((err: Error) => {
    // cleanup callback
})

Sauce Connect Tunnel

Configuration

services: [['sauce', {
    sauceConnect: boolean,              // Enable/disable Sauce Connect (default: false)
    sauceConnectOpts: SauceConnectOptions  // Sauce Connect options
}]]

Tunnel Start (onPrepare)

// packages/wdio-sauce-service/src/launcher.ts (L35-101)
async onPrepare (config, capabilities) {
    if (!this._options.sauceConnect) {
        return
    }

    const sauceConnectTunnelName = (
        this._options.sauceConnectOpts?.tunnelName ||
        `SC-tunnel-${Math.random().toString().slice(2)}`
    )

    const sauceConnectOpts: SauceConnectOptions = {
        tunnelName: sauceConnectTunnelName,
        ...this._options.sauceConnectOpts,
        metadata: metadata
    }

    // Inject tunnel name into all capabilities
    const prepareCapability = makeCapabilityFactory(sauceConnectTunnelName)
    for (const capability of capabilities) {
        prepareCapability(capability)
    }

    log.info('Starting Sauce Connect Tunnel')
    performance.mark('sauceConnectStart')
    this._sauceConnectProcess = await this.startTunnel(sauceConnectOpts)
    performance.mark('sauceConnectEnd')
}

I/O Contract -- SauceServiceConfig tunnel options:

Parameter Type Default Description
sauceConnect boolean false Enable Sauce Connect tunnel
sauceConnectOpts SauceConnectOptions {} Sauce Connect binary options
sauceConnectOpts.tunnelName string auto-generated Unique tunnel identifier

Tunnel Stop (onComplete)

// packages/wdio-sauce-service/src/launcher.ts (L136-142)
onComplete () {
    if (!this._sauceConnectProcess) {
        return
    }
    return this._sauceConnectProcess.close()
}

TestingBot Tunnel

Configuration

services: [['testingbot', {
    tbTunnel: boolean,                   // Enable/disable TestingBot Tunnel (default: false)
    tbTunnelOpts: TunnelLauncherOptions  // Tunnel binary options
}]]

Tunnel Start (onPrepare)

// packages/wdio-testingbot-service/src/launcher.ts (L20-65)
async onPrepare (config, capabilities) {
    if (!this.options.tbTunnel || !config.user || !config.key) {
        return
    }

    const tbTunnelIdentifier = (
        this.options.tbTunnelOpts?.tunnelIdentifier ||
        `TB-tunnel-${Math.random().toString().slice(2)}`
    )

    this.tbTunnelOpts = Object.assign({
        apiKey: config.user,
        apiSecret: config.key,
        'tunnel-identifier': tbTunnelIdentifier,
    }, this.options.tbTunnelOpts)

    // Inject tunnel identifier into all capabilities
    for (const capability of capabilitiesEntries) {
        const c = (caps as Capabilities.W3CCapabilities).alwaysMatch || caps
        if (!c['tb:options']) {
            c['tb:options'] = {}
        }
        c['tb:options']['tunnel-identifier'] = tbTunnelIdentifier
    }

    performance.mark('tbTunnelStart')
    this.tunnel = await promisify(testingbotTunnel)(this.tbTunnelOpts)
    performance.mark('tbTunnelEnd')
}

I/O Contract -- TestingbotOptions tunnel options:

Parameter Type Default Description
tbTunnel boolean false Enable TestingBot Tunnel
tbTunnelOpts TunnelLauncherOptions {} Tunnel binary options
tbTunnelOpts.tunnelIdentifier string auto-generated Unique tunnel identifier
tbTunnelOpts.apiKey string from config.user TestingBot API key
tbTunnelOpts.apiSecret string from config.key TestingBot API secret

Tunnel Stop (onComplete)

// packages/wdio-testingbot-service/src/launcher.ts (L71-77)
onComplete () {
    if (!this.tunnel) {
        return
    }
    return new Promise(resolve => this.tunnel!.close(resolve))
}

Cross-Provider Comparison

Feature BrowserStack Sauce Labs TestingBot
Enable flag browserstackLocal: true sauceConnect: true tbTunnel: true
Options key opts sauceConnectOpts tbTunnelOpts
Tunnel ID property opts.localIdentifier sauceConnectOpts.tunnelName tbTunnelOpts.tunnelIdentifier
Capability namespace bstack:options.localIdentifier sauce:options.tunnelName tb:options['tunnel-identifier']
Auto-ID generation No (requires manual ID) Yes (SC-tunnel-*) Yes (TB-tunnel-*)
Start timeout 60 seconds No explicit timeout No explicit timeout
Retry on failure No Yes (3 retries for ENOENT) No
Boot time measurement Yes (PerformanceObserver) Yes (PerformanceObserver) Yes (PerformanceObserver)

Full Example: BrowserStack Local Configuration

// wdio.conf.ts
export const config: WebdriverIO.Config = {
    user: process.env.BROWSERSTACK_USERNAME,
    key: process.env.BROWSERSTACK_ACCESS_KEY,

    services: [['browserstack', {
        browserstackLocal: true,
        opts: {
            localIdentifier: 'ci-tunnel-' + process.env.BUILD_NUMBER,
            verbose: true
        },
        forcedStop: false,
        setSessionName: true,
        setSessionStatus: true
    }]],

    capabilities: [{
        browserName: 'chrome',
        browserVersion: 'latest',
        'bstack:options': {
            os: 'Windows',
            osVersion: '11',
            local: true,
            localIdentifier: 'ci-tunnel-' + process.env.BUILD_NUMBER,
            buildName: 'Local Dev Build',
            video: true
        }
    }],

    baseUrl: 'http://localhost:3000',
    specs: ['./test/specs/**/*.spec.ts'],
    framework: 'mocha'
}

Related Pages

Page Connections

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