Class: Capybara::Selenium::Driver

Inherits:
Driver::Base show all
Defined in:
lib/capybara/selenium/driver.rb

Constant Summary

DEFAULT_OPTIONS =
{
  browser: :firefox,
  clear_local_storage: false,
  clear_session_storage: false
}.freeze
SPECIAL_OPTIONS =
i[browser clear_local_storage clear_session_storage].freeze

Instance Attribute Summary collapse

Attributes inherited from Driver::Base

#session

Instance Method Summary collapse

Methods inherited from Driver::Base

#response_headers, #session_options, #status_code

Constructor Details

#initialize(app, **options) ⇒ Driver

Returns a new instance of Driver



39
40
41
42
43
44
45
46
47
# File 'lib/capybara/selenium/driver.rb', line 39

def initialize(app, **options)
  load_selenium
  @session = nil
  @app = app
  @browser = nil
  @exit_status = nil
  @frame_handles = {}
  @options = DEFAULT_OPTIONS.merge(options)
end

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app



14
15
16
# File 'lib/capybara/selenium/driver.rb', line 14

def app
  @app
end

#optionsObject (readonly)

Returns the value of attribute options



14
15
16
# File 'lib/capybara/selenium/driver.rb', line 14

def options
  @options
end

Instance Method Details

#accept_modal(_type, **options) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/capybara/selenium/driver.rb', line 217

def accept_modal(_type, **options)
  if headless_chrome?
    raise ArgumentError, "Block that triggers the system modal is missing" unless block_given?
    insert_modal_handlers(true, options[:with])
    yield
    find_headless_modal(options)
  else
    yield if block_given?
    modal = find_modal(options)

    modal.send_keys options[:with] if options[:with]

    message = modal.text
    modal.accept
    message
  end
end

#browserObject



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/capybara/selenium/driver.rb', line 16

def browser
  unless @browser
    if firefox?
      options[:desired_capabilities] ||= {}
      options[:desired_capabilities][:unexpectedAlertBehaviour] = "ignore"
    end

    @processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
    @browser = Selenium::WebDriver.for(options[:browser], @processed_options)

    @w3c = ((defined?(Selenium::WebDriver::Remote::W3CCapabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3CCapabilities)) ||
            (defined?(Selenium::WebDriver::Remote::W3C::Capabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3C::Capabilities)))
    main = Process.pid
    at_exit do
      # Store the exit status of the test run since it goes away after calling the at_exit proc...
      @exit_status = $ERROR_INFO.status if $ERROR_INFO.is_a?(SystemExit)
      quit if Process.pid == main
      exit @exit_status if @exit_status # Force exit with stored status
    end
  end
  @browser
end

#chrome?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


291
292
293
# File 'lib/capybara/selenium/driver.rb', line 291

def chrome?
  browser_name == "chrome"
end

#close_window(handle) ⇒ Object



199
200
201
202
203
# File 'lib/capybara/selenium/driver.rb', line 199

def close_window(handle)
  within_given_window(handle) do
    browser.close
  end
end

#current_urlObject



76
77
78
# File 'lib/capybara/selenium/driver.rb', line 76

def current_url
  browser.current_url
end

#current_window_handleObject



170
171
172
# File 'lib/capybara/selenium/driver.rb', line 170

def current_window_handle
  browser.window_handle
end

#dismiss_modal(_type, **options) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/capybara/selenium/driver.rb', line 235

def dismiss_modal(_type, **options)
  if headless_chrome?
    raise ArgumentError, "Block that triggers the system modal is missing" unless block_given?
    insert_modal_handlers(false, options[:with])
    yield
    find_headless_modal(options)
  else
    yield if block_given?
    modal = find_modal(options)
    message = modal.text
    modal.dismiss
    message
  end
end

#evaluate_async_script(script, *args) ⇒ Object



100
101
102
103
104
# File 'lib/capybara/selenium/driver.rb', line 100

def evaluate_async_script(script, *args)
  browser.manage.timeouts.script_timeout = Capybara.default_max_wait_time
  result = browser.execute_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg })
  unwrap_script_result(result)
end

#evaluate_script(script, *args) ⇒ Object



95
96
97
98
# File 'lib/capybara/selenium/driver.rb', line 95

def evaluate_script(script, *args)
  result = execute_script("return #{script}", *args)
  unwrap_script_result(result)
end

#execute_script(script, *args) ⇒ Object



91
92
93
# File 'lib/capybara/selenium/driver.rb', line 91

def execute_script(script, *args)
  browser.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg })
end

#find_css(selector) ⇒ Object



84
85
86
# File 'lib/capybara/selenium/driver.rb', line 84

def find_css(selector)
  browser.find_elements(:css, selector).map { |node| Capybara::Selenium::Node.new(self, node) }
end

#find_xpath(selector) ⇒ Object



80
81
82
# File 'lib/capybara/selenium/driver.rb', line 80

def find_xpath(selector)
  browser.find_elements(:xpath, selector).map { |node| Capybara::Selenium::Node.new(self, node) }
end

#firefox?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


286
287
288
# File 'lib/capybara/selenium/driver.rb', line 286

def firefox?
  browser_name == "firefox"
end

#go_backObject



60
61
62
# File 'lib/capybara/selenium/driver.rb', line 60

def go_back
  browser.navigate.back
end

#go_forwardObject



64
65
66
# File 'lib/capybara/selenium/driver.rb', line 64

def go_forward
  browser.navigate.forward
end

#headless_chrome?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


296
297
298
299
300
301
302
303
304
# File 'lib/capybara/selenium/driver.rb', line 296

def headless_chrome?
  if chrome?
    caps = @processed_options[:desired_capabilities]
    chrome_options = caps[:chrome_options] || caps[:chromeOptions] || {}
    args = chrome_options['args'] || chrome_options[:args] || []
    return args.include?("--headless") || args.include?("headless")
  end
  return false
end

#htmlObject



68
69
70
# File 'lib/capybara/selenium/driver.rb', line 68

def html
  browser.page_source
end

#invalid_element_errorsObject



263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/capybara/selenium/driver.rb', line 263

def invalid_element_errors
  [
    ::Selenium::WebDriver::Error::StaleElementReferenceError,
    ::Selenium::WebDriver::Error::UnhandledError,
    ::Selenium::WebDriver::Error::ElementNotVisibleError,
    ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around a race condition that can occur with chromedriver and #go_back/#go_forward
    ::Selenium::WebDriver::Error::ElementNotInteractableError,
    ::Selenium::WebDriver::Error::ElementClickInterceptedError,
    ::Selenium::WebDriver::Error::InvalidElementStateError,
    ::Selenium::WebDriver::Error::ElementNotSelectableError
  ]
end

#marionette?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


281
282
283
# File 'lib/capybara/selenium/driver.rb', line 281

def marionette?
  firefox? && browser && @w3c
end

#maximize_window(handle) ⇒ Object



192
193
194
195
196
197
# File 'lib/capybara/selenium/driver.rb', line 192

def maximize_window(handle)
  within_given_window(handle) do
    browser.manage.window.maximize
  end
  sleep 0.1 # work around for https://code.google.com/p/selenium/issues/detail?id=7405
end

#needs_server?Boolean

Returns:

  • (Boolean)


89
# File 'lib/capybara/selenium/driver.rb', line 89

def needs_server?; true; end

#no_such_window_errorObject



276
277
278
# File 'lib/capybara/selenium/driver.rb', line 276

def no_such_window_error
  Selenium::WebDriver::Error::NoSuchWindowError
end

#open_new_windowObject



209
210
211
# File 'lib/capybara/selenium/driver.rb', line 209

def open_new_window
  browser.execute_script('window.open();')
end

#quitObject



250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/capybara/selenium/driver.rb', line 250

def quit
  @browser.quit if @browser
rescue Errno::ECONNREFUSED
  # Browser must have already gone
rescue Selenium::WebDriver::Error::UnknownError => e
  unless silenced_unknown_error_message?(e.message) # Most likely already gone
    # probably already gone but not sure - so warn
    warn "Ignoring Selenium UnknownError during driver quit: #{e.message}"
  end
ensure
  @browser = nil
end

#refreshObject



53
54
55
56
57
58
# File 'lib/capybara/selenium/driver.rb', line 53

def refresh
  accept_modal(nil, wait: 0.1) do
    browser.navigate.refresh
  end
rescue Capybara::ModalNotFound
end

#reset!Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/capybara/selenium/driver.rb', line 110

def reset!
  # Use instance variable directly so we avoid starting the browser just to reset the session
  return unless @browser

  navigated = false
  start_time = Capybara::Helpers.monotonic_time
  begin
    unless navigated
      # Only trigger a navigation if we haven't done it already, otherwise it
      # can trigger an endless series of unload modals
      begin
        @browser.manage.delete_all_cookies
        clear_storage
      rescue Selenium::WebDriver::Error::UnhandledError
        # delete_all_cookies fails when we've previously gone
        # to about:blank, so we rescue this error and do nothing
        # instead.
      end
      @browser.navigate.to("about:blank")
    end
    navigated = true

    # Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
    until find_xpath("/html/body/*").empty?
      raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if (Capybara::Helpers.monotonic_time - start_time) >= 10
      sleep 0.05
    end
  rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError
    # This error is thrown if an unhandled alert is on the page
    # Firefox appears to automatically dismiss this alert, chrome does not
    # We'll try to accept it
    begin
      @browser.switch_to.alert.accept
      sleep 0.25 # allow time for the modal to be handled
    rescue modal_error
      # The alert is now gone - nothing to do
    end
    # try cleaning up the browser again
    retry
  end
end

#resize_window_to(handle, width, height) ⇒ Object



181
182
183
184
185
186
187
188
189
190
# File 'lib/capybara/selenium/driver.rb', line 181

def resize_window_to(handle, width, height)
  within_given_window(handle) do
    # Don't set the size if already set - See https://github.com/mozilla/geckodriver/issues/643
    if marionette? && (window_size(handle) == [width, height])
      {}
    else
      browser.manage.window.resize_to(width, height)
    end
  end
end

#save_screenshot(path, **_options) ⇒ Object



106
107
108
# File 'lib/capybara/selenium/driver.rb', line 106

def save_screenshot(path, **_options)
  browser.save_screenshot(path)
end

#switch_to_frame(frame) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/capybara/selenium/driver.rb', line 152

def switch_to_frame(frame)
  case frame
  when :top
    @frame_handles[browser.window_handle] = []
    browser.switch_to.default_content
  when :parent
    # would love to use browser.switch_to.parent_frame here
    # but it has an issue if the current frame is removed from within it
    @frame_handles[browser.window_handle].pop
    browser.switch_to.default_content
    @frame_handles[browser.window_handle].each { |fh| browser.switch_to.frame(fh) }
  else
    @frame_handles[browser.window_handle] ||= []
    @frame_handles[browser.window_handle] << frame.native
    browser.switch_to.frame(frame.native)
  end
end

#switch_to_window(handle) ⇒ Object



213
214
215
# File 'lib/capybara/selenium/driver.rb', line 213

def switch_to_window(handle)
  browser.switch_to.window handle
end

#titleObject



72
73
74
# File 'lib/capybara/selenium/driver.rb', line 72

def title
  browser.title
end

#visit(path) ⇒ Object



49
50
51
# File 'lib/capybara/selenium/driver.rb', line 49

def visit(path)
  browser.navigate.to(path)
end

#wait?Boolean

Returns:

  • (Boolean)


88
# File 'lib/capybara/selenium/driver.rb', line 88

def wait?; true; end

#window_handlesObject



205
206
207
# File 'lib/capybara/selenium/driver.rb', line 205

def window_handles
  browser.window_handles
end

#window_size(handle) ⇒ Object



174
175
176
177
178
179
# File 'lib/capybara/selenium/driver.rb', line 174

def window_size(handle)
  within_given_window(handle) do
    size = browser.manage.window.size
    [size.width, size.height]
  end
end