Skip to content

Commit 831384c

Browse files
committed
Add debugging guide.
1 parent 0a6700b commit 831384c

File tree

8 files changed

+747
-2
lines changed

8 files changed

+747
-2
lines changed

context/debugging.md

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# Debugging
2+
3+
This guide explains how to debug WebDriver issues by capturing HTML source and screenshots when tests fail.
4+
5+
## Overview
6+
7+
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:
8+
9+
- **HTML Source**: Shows the current DOM structure, helpful for understanding why element selectors might be failing
10+
- **Screenshots**: Provides a visual representation of what the browser is actually showing
11+
12+
## Core Concepts
13+
14+
`async-webdriver` provides built-in methods for capturing debugging information:
15+
16+
- {ruby Async::WebDriver::Session#document_source} returns the HTML source of the current page.
17+
- {ruby Async::WebDriver::Session#screenshot} captures a screenshot of the entire page.
18+
- {ruby Async::WebDriver::Element#screenshot} captures a screenshot of a specific element.
19+
20+
## Basic Debugging
21+
22+
### Capturing HTML Source
23+
24+
To save the current page HTML to a file:
25+
26+
```ruby
27+
require "async/webdriver"
28+
29+
Async::WebDriver::Bridge::Pool.open do |pool|
30+
pool.session do |session|
31+
session.visit("https://example.com")
32+
33+
# Save HTML source for debugging
34+
html = session.document_source
35+
File.write("debug.html", html)
36+
37+
puts "HTML saved to debug.html"
38+
end
39+
end
40+
```
41+
42+
### Capturing Screenshots
43+
44+
To save a screenshot of the current page:
45+
46+
```ruby
47+
require "async/webdriver"
48+
49+
Async::WebDriver::Bridge::Pool.open do |pool|
50+
pool.session do |session|
51+
session.visit("https://example.com")
52+
53+
# Take a screenshot (returns PNG binary data)
54+
screenshot_data = session.screenshot
55+
File.binwrite("debug.png", screenshot_data)
56+
57+
puts "Screenshot saved to debug.png"
58+
end
59+
end
60+
```
61+
62+
### Element Screenshots
63+
64+
To capture a screenshot of a specific element:
65+
66+
```ruby
67+
require "async/webdriver"
68+
69+
Async::WebDriver::Bridge::Pool.open do |pool|
70+
pool.session do |session|
71+
session.visit("https://example.com")
72+
73+
# Find an element and screenshot it
74+
element = session.find_element_by_tag_name("body")
75+
element_screenshot = element.screenshot
76+
File.binwrite("element-debug.png", element_screenshot)
77+
78+
puts "Element screenshot saved to element-debug.png"
79+
end
80+
end
81+
```
82+
83+
## Debugging Failed Element Searches
84+
85+
A common debugging scenario is when `find_element` fails. Here's how to capture debugging information:
86+
87+
```ruby
88+
require "async/webdriver"
89+
90+
def debug_element_search(session, locator_type, locator_value)
91+
begin
92+
# Use the correct locator format for async-webdriver
93+
locator = {using: locator_type, value: locator_value}
94+
element = session.find_element(locator)
95+
puts "✅ Element found: #{locator_type}=#{locator_value}"
96+
return element
97+
rescue Async::WebDriver::NoSuchElementError => e
98+
puts "❌ Element not found: #{locator_type}=#{locator_value}"
99+
100+
# Capture debugging information
101+
timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
102+
103+
# Save HTML source
104+
html = session.document_source
105+
html_file = "debug-#{timestamp}.html"
106+
File.write(html_file, html)
107+
puts "📄 HTML saved to #{html_file}"
108+
109+
# Save screenshot
110+
screenshot_data = session.screenshot
111+
screenshot_file = "debug-#{timestamp}.png"
112+
File.binwrite(screenshot_file, screenshot_data)
113+
puts "📸 Screenshot saved to #{screenshot_file}"
114+
115+
# Re-raise the original error
116+
raise e
117+
end
118+
end
119+
120+
# Usage example
121+
Async::WebDriver::Bridge::Pool.open do |pool|
122+
pool.session do |session|
123+
session.visit("https://example.com")
124+
125+
# This will save debug files if the element isn't found
126+
button = debug_element_search(session, "id", "submit-button")
127+
end
128+
end
129+
```
130+
131+
## Advanced Debugging Techniques
132+
133+
### Configuring Timeouts for Debugging
134+
135+
WebDriver uses different timeout settings that affect how long operations wait:
136+
137+
```ruby
138+
require "async/webdriver"
139+
140+
Async::WebDriver::Bridge::Pool.open do |pool|
141+
pool.session do |session|
142+
# Configure timeouts for debugging (values in milliseconds)
143+
session.implicit_wait_timeout = 10_000 # 10 seconds for element finding
144+
session.page_load_timeout = 30_000 # 30 seconds for page loads
145+
session.script_timeout = 5_000 # 5 seconds for JavaScript execution
146+
147+
puts "Current timeouts: #{session.timeouts}"
148+
149+
# Now element finding will wait up to 10 seconds
150+
session.visit("https://example.com")
151+
element = session.find_element(:id, "dynamic-content") # Will wait up to 10s
152+
end
153+
end
154+
```
155+
156+
### Wait and Debug Pattern
157+
158+
Sometimes elements appear after a delay. Here's how to debug timing issues:
159+
160+
```ruby
161+
require "async/webdriver"
162+
163+
def wait_and_debug(session, locator_type, locator_value, timeout: 10000)
164+
# Set implicit wait timeout (in milliseconds)
165+
original_timeout = session.implicit_wait_timeout
166+
session.implicit_wait_timeout = timeout
167+
168+
start_time = Time.now
169+
170+
begin
171+
# Try to find the element (will use implicit wait timeout)
172+
locator = {using: locator_type, value: locator_value}
173+
session.find_element(locator)
174+
rescue Async::WebDriver::NoSuchElementError => error
175+
elapsed = Time.now - start_time
176+
puts "⏰ Timeout after #{elapsed.round(2)}s waiting for #{locator_type}=#{locator_value}"
177+
178+
# Capture final state
179+
timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
180+
181+
html = session.document_source
182+
File.write("timeout-debug-#{timestamp}.html", html)
183+
184+
screenshot_data = session.screenshot
185+
File.binwrite("timeout-debug-#{timestamp}.png", screenshot_data)
186+
187+
puts "📄 Final HTML saved to timeout-debug-#{timestamp}.html"
188+
puts "📸 Final screenshot saved to timeout-debug-#{timestamp}.png"
189+
190+
raise
191+
ensure
192+
# Restore original timeout
193+
session.implicit_wait_timeout = original_timeout
194+
end
195+
end
196+
```
197+
198+
### Multi-Step Debugging
199+
200+
For complex test scenarios, capture state at multiple points:
201+
202+
```ruby
203+
require "async/webdriver"
204+
205+
class DebugHelper
206+
def initialize(test_name)
207+
@test_name = test_name
208+
@step = 0
209+
end
210+
211+
def capture_state(session, description)
212+
@step += 1
213+
timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
214+
prefix = "#{@test_name}-step#{@step}-#{timestamp}"
215+
216+
# Save HTML
217+
html = session.document_source
218+
html_file = "#{prefix}-#{description}.html"
219+
File.write(html_file, html)
220+
221+
# Save screenshot
222+
screenshot_data = session.screenshot
223+
screenshot_file = "#{prefix}-#{description}.png"
224+
File.binwrite(screenshot_file, screenshot_data)
225+
226+
puts "🔍 Step #{@step}: #{description}"
227+
puts " 📄 #{html_file}"
228+
puts " 📸 #{screenshot_file}"
229+
end
230+
end
231+
232+
# Usage example
233+
debug = DebugHelper.new("login-test")
234+
235+
Async::WebDriver::Bridge::Pool.open do |pool|
236+
pool.session do |session|
237+
debug.capture_state(session, "initial-page")
238+
239+
session.visit("https://example.com/login")
240+
debug.capture_state(session, "login-page-loaded")
241+
242+
session.find_element_by_id("username").send_keys("[email protected]")
243+
debug.capture_state(session, "username-entered")
244+
245+
session.find_element_by_id("password").send_keys("password")
246+
debug.capture_state(session, "password-entered")
247+
248+
session.find_element_by_id("submit").click
249+
debug.capture_state(session, "form-submitted")
250+
end
251+
end
252+
```

context/getting-started.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Getting Started
2+
3+
This guide explains how to use `async-webdriver` for controlling a browser.
4+
5+
## Installation
6+
7+
Add the gem to your project:
8+
9+
~~~ bash
10+
$ bundle add async-webdriver
11+
~~~
12+
13+
## Core Concepts
14+
15+
`async-webdriver` is a Ruby implementation of the [WebDriver](https://www.w3.org/TR/webdriver/) protocol. It allows you to control a browser from Ruby code. It is built on top of [async](https://github.com/socketry/async) and [async-http](https://github.com/socketry/async-http). It has several core concepts:
16+
17+
- A {ruby Async::WebDriver::Bridge} can be used to start a web driver process, e.g. `chromedriver`, `geckodriver`, etc. It can be used in isolation, or not at all.
18+
- A {ruby Async::WebDriver::Client} is used to connect to a running web driver and can be used to create new sessions.
19+
- A {ruby Async::WebDriver::Session} represents a single browser session. It is used to control a browser window and navigate to different pages.
20+
- A {ruby Async::WebDriver::Element} represents a single element on a page. It can be used to interact with the element, e.g. click, type, etc.
21+
22+
## Basic Usage
23+
24+
The following example shows how to use `async-webdriver` to open a browser, navigate to a page, and click a button:
25+
26+
~~~ ruby
27+
require 'async/webdriver'
28+
29+
Async do
30+
bridge = Async::WebDriver::Bridge::Chrome.new(headless: false)
31+
32+
driver = bridge.start
33+
client = Async::WebDriver::Client.open(driver.endpoint)
34+
35+
session = client.session(bridge.default_capabilities)
36+
# Set the implicit wait timeout to 10 seconds since we are dealing with the real internet (which can be slow):
37+
session.implicit_wait_timeout = 10_000
38+
39+
session.visit('https://google.com')
40+
41+
session.fill_in('q', 'async-webdriver')
42+
session.click_button("I'm Feeling Lucky")
43+
44+
puts session.document_title
45+
ensure
46+
session&.close
47+
client&.close
48+
driver&.close
49+
end
50+
~~~
51+
52+
### Using a Pool to Manage Sessions
53+
54+
If you are running multiple tests in parallel, you may want to use a session pool to manage the sessions. This can be done as follows:
55+
56+
~~~ ruby
57+
require 'async/webdriver'
58+
59+
Async do
60+
bridge = Async::WebDriver::Bridge::Pool.new(Async::WebDriver::Bridge::Chrome.new(headless: false))
61+
62+
session = bridge.session
63+
# Set the implicit wait timeout to 10 seconds since we are dealing with the real internet (which can be slow):
64+
session.implicit_wait_timeout = 10_000
65+
66+
session.visit('https://google.com')
67+
68+
session.fill_in('q', 'async-webdriver')
69+
session.click_button("I'm Feeling Lucky")
70+
71+
puts session.document_title
72+
ensure
73+
session&.close
74+
bridge&.close
75+
end
76+
~~~
77+
78+
The sessions will be cached and reused if possible.
79+
80+
## Integration vs Unit Testing
81+
82+
`async-webdriver` is designed for integration testing. It is not designed for unit testing (e.g. wrapping a tool like `rack-test` as `capybara` can do). It is designed for testing your application in a real browser and web server. It is designed for testing your application in the same way that a real user would use it. Unfortunately, this style of integration testing is significantly slower than unit testing, but it is also significantly more representative of how your application will behave in production. There are other tools, e.g. [rack-test](https://github.com/rack/rack-test) which provide significantly faster unit testing, but they do not test how your application will behave in an actual web browser. A comprehensive test suite should include both unit tests and integration tests.
83+
84+
### Headless Mode
85+
86+
During testing, often you will want to see the real browser window to determine if the test is working correctly. By default, for performance reasons, `async-webdriver` will run the browser in headless mode. This means that the browser will not be visible on the screen. If you want to see the browser window, you can disable headless mode by setting the `headless` option to `false`:
87+
88+
~~~ shell
89+
$ ASYNC_WEBDRIVER_BRIDGE_HEADLESS=false ./webdriver-script.rb
90+
~~~

0 commit comments

Comments
 (0)