Async::WebDriverGuidesDebugging

Debugging

This guide explains how to debug WebDriver issues by capturing HTML source and screenshots when tests fail.

Overview

When WebDriver tests fail, it's often helpful to capture the current state of the page to understand what went wrong. The most useful debugging artifacts are:

  • HTML Source: Shows the current DOM structure, helpful for understanding why element selectors might be failing
  • Screenshots: Provides a visual representation of what the browser is actually showing

Core Concepts

async-webdriver provides built-in methods for capturing debugging information:

  • Async::WebDriver::Session#document_source returns the HTML source of the current page.
  • Async::WebDriver::Session#screenshot captures a screenshot of the entire page.
  • Async::WebDriver::Element#screenshot captures a screenshot of a specific element.

Basic Debugging

Capturing HTML Source

To save the current page HTML to a file:

require "async/webdriver"

Async::WebDriver::Bridge::Pool.open do |pool|
	pool.session do |session|
		session.visit("https://example.com")
		
		# Save HTML source for debugging
		html = session.document_source
		File.write("debug.html", html)
		
		puts "HTML saved to debug.html"
	end
end

Capturing Screenshots

To save a screenshot of the current page:

require "async/webdriver"

Async::WebDriver::Bridge::Pool.open do |pool|
	pool.session do |session|
		session.visit("https://example.com")
		
		# Take a screenshot (returns PNG binary data)
		screenshot_data = session.screenshot
		File.binwrite("debug.png", screenshot_data)
		
		puts "Screenshot saved to debug.png"
	end
end

Element Screenshots

To capture a screenshot of a specific element:

require "async/webdriver"

Async::WebDriver::Bridge::Pool.open do |pool|
	pool.session do |session|
		session.visit("https://example.com")
		
		# Find an element and screenshot it
		element = session.find_element_by_tag_name("body")
		element_screenshot = element.screenshot
		File.binwrite("element-debug.png", element_screenshot)
		
		puts "Element screenshot saved to element-debug.png"
	end
end

Debugging Failed Element Searches

A common debugging scenario is when find_element fails. Here's how to capture debugging information:

require "async/webdriver"

def debug_element_search(session, locator_type, locator_value)
	begin
		# Use the correct locator format for async-webdriver
		locator = {using: locator_type, value: locator_value}
		element = session.find_element(locator)
		puts "✅ Element found: #{locator_type}=#{locator_value}"
		return element
	rescue Async::WebDriver::NoSuchElementError => e
		puts "❌ Element not found: #{locator_type}=#{locator_value}"
		
		# Capture debugging information
		timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
		
		# Save HTML source
		html = session.document_source
		html_file = "debug-#{timestamp}.html"
		File.write(html_file, html)
		puts "📄 HTML saved to #{html_file}"
		
		# Save screenshot
		screenshot_data = session.screenshot
		screenshot_file = "debug-#{timestamp}.png"
		File.binwrite(screenshot_file, screenshot_data)
		puts "📸 Screenshot saved to #{screenshot_file}"
		
		# Re-raise the original error
		raise e
	end
end

# Usage example
Async::WebDriver::Bridge::Pool.open do |pool|
	pool.session do |session|
		session.visit("https://example.com")
		
		# This will save debug files if the element isn't found
		button = debug_element_search(session, "id", "submit-button")
	end
end

Advanced Debugging Techniques

Configuring Timeouts for Debugging

WebDriver uses different timeout settings that affect how long operations wait:

require "async/webdriver"

Async::WebDriver::Bridge::Pool.open do |pool|
	pool.session do |session|
		# Configure timeouts for debugging (values in milliseconds)
		session.implicit_wait_timeout = 10_000  # 10 seconds for element finding
		session.page_load_timeout = 30_000      # 30 seconds for page loads
		session.script_timeout = 5_000          # 5 seconds for JavaScript execution
		
		puts "Current timeouts: #{session.timeouts}"
		
		# Now element finding will wait up to 10 seconds
		session.visit("https://example.com")
		element = session.find_element(:id, "dynamic-content")  # Will wait up to 10s
	end
end

Wait and Debug Pattern

Sometimes elements appear after a delay. Here's how to debug timing issues:

require "async/webdriver"

def wait_and_debug(session, locator_type, locator_value, timeout: 10000)
	# Set implicit wait timeout (in milliseconds)
	original_timeout = session.implicit_wait_timeout
	session.implicit_wait_timeout = timeout
	
	start_time = Time.now
	
	begin
		# Try to find the element (will use implicit wait timeout)
		locator = {using: locator_type, value: locator_value}
		session.find_element(locator)
	rescue Async::WebDriver::NoSuchElementError => error
		elapsed = Time.now - start_time
		puts "⏰ Timeout after #{elapsed.round(2)}s waiting for #{locator_type}=#{locator_value}"
		
		# Capture final state
		timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
		
		html = session.document_source
		File.write("timeout-debug-#{timestamp}.html", html)
		
		screenshot_data = session.screenshot
		File.binwrite("timeout-debug-#{timestamp}.png", screenshot_data)
		
		puts "📄 Final HTML saved to timeout-debug-#{timestamp}.html"
		puts "📸 Final screenshot saved to timeout-debug-#{timestamp}.png"
		
		raise
	ensure
		# Restore original timeout
		session.implicit_wait_timeout = original_timeout
	end
end

Multi-Step Debugging

For complex test scenarios, capture state at multiple points:

require "async/webdriver"

class DebugHelper
	def initialize(test_name)
		@test_name = test_name
		@step = 0
	end
	
	def capture_state(session, description)
		@step += 1
		timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
		prefix = "#{@test_name}-step#{@step}-#{timestamp}"
		
		# Save HTML
		html = session.document_source
		html_file = "#{prefix}-#{description}.html"
		File.write(html_file, html)
		
		# Save screenshot
		screenshot_data = session.screenshot
		screenshot_file = "#{prefix}-#{description}.png"
		File.binwrite(screenshot_file, screenshot_data)
		
		puts "🔍 Step #{@step}: #{description}"
		puts "   📄 #{html_file}"
		puts "   📸 #{screenshot_file}"
	end
end

# Usage example
debug = DebugHelper.new("login-test")

Async::WebDriver::Bridge::Pool.open do |pool|
	pool.session do |session|
		debug.capture_state(session, "initial-page")
		
		session.visit("https://example.com/login")
		debug.capture_state(session, "login-page-loaded")
		
		session.find_element_by_id("username").send_keys("user@example.com")
		debug.capture_state(session, "username-entered")
		
		session.find_element_by_id("password").send_keys("password")
		debug.capture_state(session, "password-entered")
		
		session.find_element_by_id("submit").click
		debug.capture_state(session, "form-submitted")
	end
end