Comprehensive error hierarchy and handling strategies for robust WebDriver automation with specific error types and recovery patterns.
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
}
}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();
}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;
}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;
}
}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;
}
}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();
}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())
)
]
);