or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-interactions.mdbidi-protocol.mdbrowser-automation.mdbrowser-options.mddriver-management.mdelement-location.mderror-handling.mdindex.mdwait-conditions.md
tile.json

error-handling.mddocs/

Error Handling

Comprehensive error hierarchy and handling strategies for robust WebDriver automation with specific error types and recovery patterns.

Capabilities

WebDriver Error Hierarchy

Complete error type system for identifying and handling specific automation failures.

/**
 * Base WebDriver error class
 */
class WebDriverError extends Error {
  constructor(message?: string);
  
  /** Error name */
  name: string;
  
  /** Error message */
  message: string;
  
  /** Remote stack trace if available */
  remoteStackTrace?: string;
}

/**
 * Element interaction errors
 */
class ElementClickInterceptedError extends WebDriverError {}
class ElementNotInteractableError extends WebDriverError {}
class ElementNotSelectableError extends WebDriverError {}
class StaleElementReferenceError extends WebDriverError {}
class InvalidElementStateError extends WebDriverError {}

/**
 * Element location errors
 */
class NoSuchElementError extends WebDriverError {}
class NoSuchFrameError extends WebDriverError {}
class NoSuchWindowError extends WebDriverError {}
class NoSuchShadowRootError extends WebDriverError {}
class NoSuchSessionError extends WebDriverError {}
class NoSuchCookieError extends WebDriverError {}
class DetachedShadowRootError extends WebDriverError {}

/**
 * Session and state errors  
 */
class InvalidSelectorError extends WebDriverError {}
class InvalidArgumentError extends WebDriverError {}
class InvalidCoordinatesError extends WebDriverError {}
class InvalidCookieDomainError extends WebDriverError {}
class SessionNotCreatedError extends WebDriverError {}
class InsecureCertificateError extends WebDriverError {}

/**
 * Execution errors
 */
class JavascriptError extends WebDriverError {}
class ScriptTimeoutError extends WebDriverError {}
class TimeoutError extends WebDriverError {}
class UnknownCommandError extends WebDriverError {}
class UnsupportedOperationError extends WebDriverError {}

/**
 * Navigation and interaction errors
 */
class MoveTargetOutOfBoundsError extends WebDriverError {}
class UnableToSetCookieError extends WebDriverError {}
class UnableToCaptureScreenError extends WebDriverError {}
class NoSuchAlertError extends WebDriverError {}
class UnexpectedAlertOpenError extends WebDriverError {}
class UnknownMethodError extends WebDriverError {}

Usage Examples:

const { 
  Builder, By, until, 
  NoSuchElementError, 
  TimeoutError, 
  StaleElementReferenceError 
} = require('selenium-webdriver');

let driver = await new Builder().forBrowser('chrome').build();

try {
  // This might throw various errors
  let element = await driver.findElement(By.id('missing-element'));
  await element.click();
  
} catch (error) {
  if (error instanceof NoSuchElementError) {
    console.log('Element not found:', error.message);
    // Handle missing element
    
  } else if (error instanceof StaleElementReferenceError) {
    console.log('Element became stale:', error.message);
    // Re-find element
    
  } else if (error instanceof TimeoutError) {
    console.log('Operation timed out:', error.message);
    // Retry or take alternative action
    
  } else {
    console.log('Unexpected error:', error.name, error.message);
    throw error; // Re-throw if not handled
  }
}

Element-Related Error Handling

Specific patterns for handling element interaction failures.

Usage Examples:

// Handle stale element references
async function safeElementInteraction(locator, action) {
  let maxRetries = 3;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      let element = await driver.findElement(locator);
      await action(element);
      return; // Success
      
    } catch (error) {
      if (error instanceof StaleElementReferenceError && attempt < maxRetries) {
        console.log(`Stale element, retrying... (${attempt}/${maxRetries})`);
        await driver.sleep(500);
        continue;
      }
      throw error;
    }
  }
}

// Usage
await safeElementInteraction(By.id('dynamic-button'), async (element) => {
  await element.click();
});

// Handle element not interactable
async function waitAndClick(locator) {
  try {
    let element = await driver.wait(until.elementLocated(locator), 5000);
    await driver.wait(until.elementIsClickable(element), 5000);
    await element.click();
    
  } catch (error) {
    if (error instanceof ElementNotInteractableError) {
      console.log('Element not interactable, trying JavaScript click');
      let element = await driver.findElement(locator);
      await driver.executeScript('arguments[0].click()', element);
      
    } else if (error instanceof ElementClickInterceptedError) {
      console.log('Click intercepted, scrolling to element');
      let element = await driver.findElement(locator);
      await driver.executeScript('arguments[0].scrollIntoView(true)', element);
      await driver.sleep(500);
      await element.click();
      
    } else {
      throw error;
    }
  }
}

// Handle missing elements gracefully
async function findElementSafely(locator, timeout = 5000) {
  try {
    return await driver.wait(until.elementLocated(locator), timeout);
  } catch (error) {
    if (error instanceof TimeoutError || error instanceof NoSuchElementError) {
      return null; // Element not found
    }
    throw error;
  }
}

let optionalElement = await findElementSafely(By.id('optional-popup'));
if (optionalElement) {
  await optionalElement.click();
}

Session and Navigation Error Handling

Patterns for handling session and navigation-related failures.

Usage Examples:

// Handle session creation failures
async function createDriverWithRetry(browserName, options = {}) {
  let maxRetries = 3;
  let retryDelay = 2000;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      let builder = new Builder().forBrowser(browserName);
      
      if (options.chromeOptions) {
        builder.setChromeOptions(options.chromeOptions);
      }
      
      return await builder.build();
      
    } catch (error) {
      if (error instanceof SessionNotCreatedError && attempt < maxRetries) {
        console.log(`Session creation failed, retrying in ${retryDelay}ms... (${attempt}/${maxRetries})`);
        await new Promise(resolve => setTimeout(resolve, retryDelay));
        retryDelay *= 2; // Exponential backoff
        continue;
      }
      throw error;
    }
  }
}

// Handle navigation errors
async function navigateWithRetry(url, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await driver.get(url);
      
      // Verify navigation succeeded
      let currentUrl = await driver.getCurrentUrl();
      if (currentUrl.includes(new URL(url).hostname)) {
        return;
      }
      
    } catch (error) {
      if (attempt < maxRetries) {
        console.log(`Navigation failed, retrying... (${attempt}/${maxRetries})`);
        await driver.sleep(1000);
        continue;
      }
      throw error;
    }
  }
}

// Handle window/frame switching errors
async function safeFrameSwitch(frameLocator) {
  try {
    await driver.wait(until.ableToSwitchToFrame(frameLocator), 5000);
    
  } catch (error) {
    if (error instanceof NoSuchFrameError) {
      console.log('Frame not found, staying in current context');
      return false;
    }
    throw error;
  }
  return true;
}

async function safeWindowSwitch(windowHandle) {
  try {
    await driver.switchTo().window(windowHandle);
    
  } catch (error) {
    if (error instanceof NoSuchWindowError) {
      console.log('Window not found, using current window');
      return false;
    }
    throw error;
  }
  return true;
}

Alert and Dialog Error Handling

Handling unexpected alerts and dialog interactions.

Usage Examples:

// Handle unexpected alerts
async function handleUnexpectedAlert() {
  try {
    let alert = await driver.switchTo().alert();
    let alertText = await alert.getText();
    console.log('Unexpected alert:', alertText);
    await alert.accept();
    return true;
    
  } catch (error) {
    if (error instanceof NoSuchAlertError) {
      return false; // No alert present
    }
    throw error;
  }
}

// Wrapper for operations that might trigger alerts
async function executeWithAlertHandling(operation) {
  try {
    await operation();
    
  } catch (error) {
    if (error instanceof UnexpectedAlertOpenError) {
      console.log('Unexpected alert detected');
      let alertHandled = await handleUnexpectedAlert();
      
      if (alertHandled) {
        // Retry operation after handling alert
        await operation();
      }
    } else {
      throw error;
    }
  }
}

// Usage
await executeWithAlertHandling(async () => {
  await driver.findElement(By.id('submit-button')).click();
});

// Wait for expected alert
async function waitForAlert(timeout = 5000) {
  try {
    return await driver.wait(until.alertIsPresent(), timeout);
  } catch (error) {
    if (error instanceof TimeoutError) {
      console.log('Expected alert did not appear within timeout');
      return null;
    }
    throw error;
  }
}

JavaScript Execution Error Handling

Handling JavaScript execution and script timeout errors.

Usage Examples:

// Handle JavaScript execution errors
async function safeExecuteScript(script, ...args) {
  try {
    return await driver.executeScript(script, ...args);
    
  } catch (error) {
    if (error instanceof JavascriptError) {
      console.log('JavaScript error:', error.message);
      
      // Try alternative approach
      if (script.includes('click()')) {
        console.log('Trying alternative click method');
        return await driver.executeScript(`
          try { 
            arguments[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); 
          } catch(e) { 
            console.log('Click event failed:', e); 
          }
        `, args[0]);
      }
      
    } else if (error instanceof ScriptTimeoutError) {
      console.log('Script execution timed out');
      
      // Cancel long-running script and try shorter version
      await driver.executeScript('window.stop()');
      
    } else {
      throw error;
    }
  }
}

// Handle async script timeouts
async function executeAsyncScriptWithRetry(script, timeout = 30000, ...args) {
  // Set script timeout
  await driver.manage().setTimeouts({script: timeout});
  
  try {
    return await driver.executeAsyncScript(script, ...args);
    
  } catch (error) {
    if (error instanceof ScriptTimeoutError) {
      console.log(`Async script timed out after ${timeout}ms`);
      
      // Try with longer timeout
      let newTimeout = timeout * 2;
      await driver.manage().setTimeouts({script: newTimeout});
      
      return await driver.executeAsyncScript(script, ...args);
    }
    throw error;
  }
}

Comprehensive Error Handling Strategy

Complete error handling wrapper for robust automation.

Usage Examples:

class RobustWebDriver {
  constructor(driver) {
    this.driver = driver;
    this.maxRetries = 3;
    this.retryDelay = 1000;
  }
  
  async findElement(locator, options = {}) {
    const { timeout = 10000, retries = this.maxRetries } = options;
    
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        return await this.driver.wait(until.elementLocated(locator), timeout);
        
      } catch (error) {
        if (this.isRetryableError(error) && attempt < retries) {
          console.log(`Find element failed, retrying... (${attempt}/${retries})`);
          await this.driver.sleep(this.retryDelay);
          continue;
        }
        
        // Log error details
        await this.logError(error, { action: 'findElement', locator });
        throw error;
      }
    }
  }
  
  async click(locator, options = {}) {
    const element = await this.findElement(locator, options);
    
    try {
      await this.driver.wait(until.elementIsClickable(element), 5000);
      await element.click();
      
    } catch (error) {
      if (error instanceof ElementClickInterceptedError) {
        console.log('Click intercepted, trying JavaScript click');
        await this.driver.executeScript('arguments[0].click()', element);
        
      } else if (error instanceof ElementNotInteractableError) {
        console.log('Element not interactable, scrolling and retrying');
        await this.driver.executeScript('arguments[0].scrollIntoView(true)', element);
        await this.driver.sleep(500);
        await element.click();
        
      } else {
        throw error;
      }
    }
  }
  
  isRetryableError(error) {
    return error instanceof StaleElementReferenceError ||
           error instanceof TimeoutError ||
           error instanceof ElementNotInteractableError;
  }
  
  async logError(error, context = {}) {
    const errorInfo = {
      name: error.name,
      message: error.message,
      context: context,
      timestamp: new Date().toISOString(),
      url: await this.driver.getCurrentUrl().catch(() => 'unknown'),
      title: await this.driver.getTitle().catch(() => 'unknown')
    };
    
    console.error('WebDriver Error:', JSON.stringify(errorInfo, null, 2));
    
    // Take screenshot on error
    try {
      const screenshot = await this.driver.takeScreenshot();
      // Save screenshot logic here
    } catch (screenshotError) {
      console.log('Could not take error screenshot:', screenshotError.message);
    }
  }
  
  async quit() {
    try {
      await this.driver.quit();
    } catch (error) {
      console.log('Error during driver quit:', error.message);
    }
  }
}

// Usage
let robustDriver = new RobustWebDriver(driver);

try {
  await robustDriver.click(By.id('submit-button'));
  
} catch (error) {
  console.log('Operation failed after all retry attempts');
  // Handle final failure
  
} finally {
  await robustDriver.quit();
}

Error Recovery Patterns

Common patterns for recovering from automation failures.

Usage Examples:

// Page refresh recovery
async function recoverWithPageRefresh(operation, maxRetries = 2) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
      
    } catch (error) {
      if (attempt < maxRetries && this.isRecoverableError(error)) {
        console.log('Refreshing page and retrying...');
        await driver.navigate().refresh();
        await driver.wait(until.elementLocated(By.tagName('body')), 10000);
        continue;
      }
      throw error;
    }
  }
}

// Browser restart recovery
async function recoverWithBrowserRestart(operation) {
  try {
    return await operation();
    
  } catch (error) {
    if (this.isCriticalError(error)) {
      console.log('Critical error detected, restarting browser...');
      
      await driver.quit();
      driver = await new Builder().forBrowser('chrome').build();
      
      // Re-navigate to current page
      await driver.get(lastKnownUrl);
      
      // Retry operation
      return await operation();
    }
    throw error;
  }
}

// Fallback strategy chain
async function executeWithFallbacks(primaryAction, fallbacks = []) {
  let lastError;
  
  // Try primary action
  try {
    return await primaryAction();
  } catch (error) {
    lastError = error;
    console.log('Primary action failed:', error.message);
  }
  
  // Try fallback actions
  for (let i = 0; i < fallbacks.length; i++) {
    try {
      console.log(`Trying fallback ${i + 1}...`);
      return await fallbacks[i]();
    } catch (error) {
      lastError = error;
      console.log(`Fallback ${i + 1} failed:`, error.message);
    }
  }
  
  throw lastError; // All strategies failed
}

// Usage
await executeWithFallbacks(
  // Primary: Normal click
  () => driver.findElement(By.id('button')).then(el => el.click()),
  
  // Fallbacks
  [
    // Fallback 1: JavaScript click
    () => driver.executeScript('document.getElementById("button").click()'),
    
    // Fallback 2: Send ENTER key
    () => driver.findElement(By.id('button')).then(el => el.sendKeys(Key.RETURN)),
    
    // Fallback 3: Submit parent form
    () => driver.findElement(By.id('button')).then(el => 
      el.findElement(By.xpath('./ancestor::form')).then(form => form.submit())
    )
  ]
);