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.

Implementation:Webdriverio Webdriverio Error Handling Pattern

From Leeroopedia

Template:Implementation Metadata

Overview

The Error Handling Pattern is an interface specification and set of concrete classes for handling WebDriver errors in standalone browser automation scripts. WebdriverIO maps W3C WebDriver protocol errors to JavaScript Error objects, providing structured error information for programmatic handling.

Description

WebdriverIO maps W3C WebDriver protocol errors to JavaScript Error objects through a class hierarchy. The error handling system operates at two levels:

Error Classes

  • WebDriverError (abstract base class): Provides the computeErrorMessage() method that enriches error messages with the executing command name, HTTP method, and arguments. All WebDriver errors extend this class.
  • WebDriverRequestError: Represents errors at the HTTP transport level -- connection failures, timeouts, and network errors. Contains the original error, the request URL, and request options. Handles special cases like fetch failed (connection refused) and UND_ERR_CONNECT_TIMEOUT (connection timeout).
  • WebDriverResponseError: Represents errors returned in the WebDriver protocol response body. Parses the W3C error format ({ value: { error, message, stacktrace } }) and handles browser-specific variations (Chrome, Firefox, Safari). Maps the protocol error field to the JavaScript Error.name property for programmatic matching.

Element Lookup Error Pattern

The findElement() utility in WebdriverIO follows a distinctive pattern: when an element is not found, it returns an Error instance rather than throwing. This enables the element wrapper to create a "not found" element object that defers the error until the user actually tries to interact with it. This is different from the standard protocol behavior (which would throw immediately) and enables patterns like:

const elem = await browser.$('#might-not-exist')
// No error thrown yet -- elem is a valid object
const exists = await elem.isExisting()
// Now you can check without try/catch

Error Message Enrichment

All WebDriver errors are enriched with contextual information via computeErrorMessage():

  • The command name being executed (extracted from the URL path)
  • The HTTP method used
  • The command arguments (with sensitive data masked)

This produces error messages like: WebDriverError: no such element when running "findElement" with method "POST" and args "{"using":"css selector","value":"#missing"}"

Source Locations

Component File Lines
WebDriverError (abstract base) packages/webdriver/src/request/error.ts L5-50
WebDriverRequestError packages/webdriver/src/request/error.ts L52-90
WebDriverResponseError packages/webdriver/src/request/error.ts L92-154
findElement() error handling packages/webdriverio/src/utils/index.ts L463-558
Session abort mechanism packages/webdriver/src/command.ts L221-274

Repository: https://github.com/webdriverio/webdriverio

Error Types

Class Parent Description
WebDriverError Error Abstract base class for all WebDriver errors. Provides computeErrorMessage().
WebDriverRequestError WebDriverError Transport-level errors (connection refused, timeout, network failure). Contains statusCode, body, and code properties.
WebDriverResponseError WebDriverError Protocol-level errors from the WebDriver response body. The name property is set to the W3C error code (e.g., "no such element", "stale element reference").

Pattern Interface

The standard error handling interface for standalone WebdriverIO scripts:

// Error class interface
abstract class WebDriverError extends Error {
    abstract url: URL
    abstract opts: RequestInit
    computeErrorMessage(): string
}

class WebDriverRequestError extends WebDriverError {
    url: URL
    opts: RequestInit
    statusCode?: number
    body?: unknown
    code?: string
    constructor(err: Error, url: URL, opts: RequestInit)
}

class WebDriverResponseError extends WebDriverError {
    url: URL
    opts: RequestInit
    constructor(response: unknown, url: URL, opts: RequestInit)
}

Usage Example

Basic try/catch/finally pattern:

import { remote } from 'webdriverio'

const browser = await remote({
    capabilities: { browserName: 'chrome' }
})

try {
    await browser.url('https://example.com')

    // Element interaction with error handling
    const submitBtn = await browser.$('#submit-button')
    await submitBtn.waitForDisplayed({ timeout: 5000 })
    await submitBtn.click()

} catch (err) {
    // Classify the error by its name (W3C error code)
    if (err.name === 'no such element') {
        console.error('Element not found:', err.message)
    } else if (err.name === 'stale element reference') {
        console.error('Element became stale:', err.message)
    } else if (err.name === 'timeout') {
        console.error('Operation timed out:', err.message)
    } else if (err.name === 'invalid session id') {
        console.error('Session was lost:', err.message)
    } else {
        console.error('Unexpected error:', err.message)
    }

    // Capture diagnostic information
    try {
        await browser.saveScreenshot('./error-screenshot.png')
    } catch (_) {
        // Screenshot may fail if session is invalid
    }

} finally {
    // Always clean up the session
    try {
        await browser.deleteSession()
    } catch (_) {
        // Session may already be invalid
    }
}

Graceful element existence check (leveraging the non-throwing findElement pattern):

const browser = await remote({
    capabilities: { browserName: 'chrome' }
})

try {
    await browser.url('https://example.com')

    // $ does not throw when element is not found
    // Instead it returns an element object that knows it was not found
    const elem = await browser.$('#optional-element')
    if (await elem.isExisting()) {
        await elem.click()
    } else {
        console.log('Optional element not present, skipping')
    }

} finally {
    await browser.deleteSession()
}

Retry pattern for stale elements:

async function clickWithRetry(browser, selector, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            const elem = await browser.$(selector)
            await elem.click()
            return // Success
        } catch (err) {
            if (err.name === 'stale element reference' && attempt < maxRetries - 1) {
                console.log(`Stale element, retrying (${attempt + 1}/${maxRetries})`)
                continue
            }
            throw err // Re-throw if not recoverable
        }
    }
}

Internal Flow

Request Error Path

  1. A protocol command is issued via the command factory (command.ts).
  2. The request is sent via the Request class.
  3. If the HTTP request itself fails (network error, timeout):
    • A WebDriverRequestError is constructed with the original error, URL, and request options.
    • The error message is enriched with command context via computeErrorMessage().
    • The error is thrown to the caller.

Response Error Path

  1. The HTTP response is received successfully but contains an error in the body.
  2. The response body is parsed to extract the W3C error fields (error, message, stacktrace).
  3. A WebDriverResponseError is constructed:
    • The name property is set to the W3C error code (e.g., "no such element").
    • Browser-specific error formats (Chrome, Firefox, Safari) are normalized.
    • Invalid selector errors receive improved error messages with the actual selector and strategy.
  4. The error message is enriched via computeErrorMessage().
  5. The error is thrown to the caller.

Element Not Found Path

  1. findElement() is called with a selector.
  2. The protocol command returns no matching element.
  3. Instead of throwing, an Error instance is created: new Error('Couldn't find element with selector "..."').
  4. This Error is returned (not thrown) and passed to getElement().
  5. getElement() wraps it in a "not found" element object.
  6. The user can check isExisting() without triggering an exception.
  7. If the user calls an action method (e.g., click()), the deferred error is thrown at that point.

Related Pages

Page Connections

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