SeleniumHQ/selenium · error · Error::TimeoutError
timed out after #{@timeout} seconds
Error message
timed out after #{@timeout} seconds What it means
Raised by Selenium::WebDriver::Wait#until when the block does not return a truthy value within the configured :timeout (default 5 seconds). The method polls the block every :interval (default 0.2s); if the end_time is reached without a truthy return, it raises Error::TimeoutError. If a custom :message or :message_provider was supplied, that text is used; otherwise the default 'timed out after N seconds' is shown. If an ignored exception was caught during polling, its message is appended.
Source
Thrown at rb/lib/selenium/webdriver/common/wait.rb:76
return result if result
rescue *@ignored => last_error # rubocop:disable Naming/RescuedExceptionsVariableName
# swallowed
end
sleep @interval
end
msg = if @message
@message.dup
elsif @message_provider
@message_provider.call
else
"timed out after #{@timeout} seconds"
end
msg << " (#{last_error.message})" if last_error
raise Error::TimeoutError, msg
end
private
def current_time
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
end # Wait
end # WebDriver
end # Selenium
View on GitHub (pinned to aa36b38e69)
Solutions
- Increase the timeout: Selenium::WebDriver::Wait.new(timeout: 30).until { ... }.
- Verify the selector/condition is correct by testing it manually (e.g., inspect the page DOM).
- Add a custom message for diagnosis: Wait.new(timeout: 30, message: 'Login button never appeared').until { ... }.
- Broaden the set of ignored exceptions if needed: Wait.new(ignore: [NoSuchElementError, StaleElementReferenceError]).until { ... }.
- Ensure the block returns a truthy value on success (not just nil from a side-effecting call).
Example fix
// before
wait = Selenium::WebDriver::Wait.new # default 5s
wait.until { driver.find_element(:id, 'slow-loading-btn') }
# raises: timed out after 5 seconds (no such element...)
// after
wait = Selenium::WebDriver::Wait.new(
timeout: 30,
message: 'Login button did not appear within 30s'
)
element = wait.until { driver.find_element(:id, 'slow-loading-btn') } Defensive patterns
Strategy: try-catch
Validate before calling
# Validate preconditions before entering the wait
def wait_for_element(driver, how, what, timeout: 30)
wait = Selenium::WebDriver::Wait.new(
timeout: timeout,
message: "Element #{how}=#{what} not found within #{timeout}s",
ignore: [
Selenium::WebDriver::Error::NoSuchElementError,
Selenium::WebDriver::Error::StaleElementReferenceError
]
)
wait.until { driver.find_element(how, what) }
end Try / catch
wait = Selenium::WebDriver::Wait.new(timeout: 30, message: 'Element not found')
begin
element = wait.until { driver.find_element(:css, '.loaded') }
rescue Selenium::WebDriver::Error::TimeoutError => e
warn "Wait failed: #{e.message}"
# take a screenshot or snapshot state for debugging
driver.save_screenshot('timeout_debug.png')
raise
end Prevention
- Set a realistic timeout based on expected page load / AJAX behavior (15-60s for complex SPAs).
- Always provide a descriptive :message to the Wait for faster diagnosis.
- Include StaleElementReferenceError in the :ignore list for dynamic pages with rapid DOM updates.
- Ensure the block returns a truthy value on success — a side-effecting call returning nil will always time out.
- Use explicit waits (Wait.until) instead of fixed sleep() calls.
When it happens
Trigger: Waiting for an element that never appears: Selenium::WebDriver::Wait.new.until { driver.find_element(:id, 'missing') }. Waiting for a page condition that is never met (e.g., a title that doesn't change). The block always returns nil/false. The element exists but find_element raises NoSuchElementError (ignored by default) every poll cycle.
Common situations: Page load is slower than the default 5s timeout. Element selectors changed in a UI update so the element is never found. AJAX content that loads conditionally and sometimes doesn't appear. SPA navigation not triggering the expected DOM state.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- remote server not ready in {} seconds
- remote server not stopped in #{@timeout} seconds
- -> #{@pid} still alive after #{timeout} seconds
- unable to connect to #{@executable_path} #{@host}:#{@port}:
- unable to bind to locking port #{@port} within #{@timeout} s
AI-assisted analysis of SeleniumHQ/selenium@aa36b38e69 (2026-08-14).
Data as JSON: /api/errors/d37a9a1f4f7d537c.
Report an issue: GitHub.