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

  1. Increase the timeout: Selenium::WebDriver::Wait.new(timeout: 30).until { ... }.
  2. Verify the selector/condition is correct by testing it manually (e.g., inspect the page DOM).
  3. Add a custom message for diagnosis: Wait.new(timeout: 30, message: 'Login button never appeared').until { ... }.
  4. Broaden the set of ignored exceptions if needed: Wait.new(ignore: [NoSuchElementError, StaleElementReferenceError]).until { ... }.
  5. 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

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

Related errors


AI-assisted analysis of SeleniumHQ/selenium@aa36b38e69 (2026-08-14). Data as JSON: /api/errors/d37a9a1f4f7d537c. Report an issue: GitHub.