# frozen_string_literal: truerequire'uri'require'English'classCapybara::Selenium::Driver<Capybara::Driver::BaseDEFAULT_OPTIONS={browser: :firefox,clear_local_storage: false,clear_session_storage: false}.freezeSPECIAL_OPTIONS=%i[browser clear_local_storage clear_session_storage].freezeattr_reader:app,:optionsdefself.load_seleniumrequire'selenium-webdriver'warn"Warning: You're using an unsupported version of selenium-webdriver, please upgrade."ifGem.loaded_specs['selenium-webdriver'].version<Gem::Version.new('3.5.0')rescueLoadError=>errraiseerriferr.message!~/selenium-webdriver/raiseLoadError,"Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."enddefbrowser@browser||=beginprocessed_options=options.reject{|key,_val|SPECIAL_OPTIONS.include?(key)}Selenium::WebDriver.for(options[:browser],processed_options).tapdo|driver|specialize_driver(driver)setup_exit_handlerendend@browserenddefinitialize(app,**options)self.class.load_selenium@app=app@browser=nil@exit_status=nil@frame_handles=Hash.new{|hash,handle|hash[handle]=[]}@options=DEFAULT_OPTIONS.merge(options)@node_class=::Capybara::Selenium::Nodeenddefvisit(path)browser.navigate.to(path)enddefrefreshbrowser.navigate.refreshenddefgo_backbrowser.navigate.backenddefgo_forwardbrowser.navigate.forwardenddefhtmlbrowser.page_sourceenddeftitlebrowser.titleenddefcurrent_urlbrowser.current_urlenddeffind_xpath(selector)browser.find_elements(:xpath,selector).map(&method(:build_node))enddeffind_css(selector)browser.find_elements(:css,selector).map(&method(:build_node))enddefwait?;true;enddefneeds_server?;true;enddefexecute_script(script,*args)browser.execute_script(script,*native_args(args))enddefevaluate_script(script,*args)result=execute_script("return #{script}",*args)unwrap_script_result(result)enddefevaluate_async_script(script,*args)browser.manage.timeouts.script_timeout=Capybara.default_max_wait_timeresult=browser.execute_async_script(script,*native_args(args))unwrap_script_result(result)enddefsave_screenshot(path,**_options)browser.save_screenshot(path)enddefreset!# Use instance variable directly so we avoid starting the browser just to reset the sessionreturnunless@browsernavigated=falsetimer=Capybara::Helpers.timer(expire_in: 10)beginunlessnavigated# Only trigger a navigation if we haven't done it already, otherwise it# can trigger an endless series of unload modalsclear_browser_state@browser.navigate.to('about:blank')endnavigated=true# Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unloaduntilfind_xpath('/html/body/*').empty?raiseCapybara::ExpectationNotMet,'Timed out waiting for Selenium session reset'iftimer.expired?sleep0.05endrescueSelenium::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 itbegin@browser.switch_to.alert.acceptsleep0.25# allow time for the modal to be handledrescuemodal_error# The alert is now gone.# If navigation has not occurred attempt again and accept alert# since FF may have dismissed the alert at first attempt.navigate_with_accept('about:blank')ifcurrent_url!='about:blank'end# try cleaning up the browser againretryendenddefswitch_to_frame(frame)handles=@frame_handles[current_window_handle]caseframewhen:tophandles.clearbrowser.switch_to.default_contentwhen:parenthandles.popbrowser.switch_to.parent_frameelsehandles<<frame.nativebrowser.switch_to.frame(frame.native)endenddefcurrent_window_handlebrowser.window_handleenddefwindow_size(handle)within_given_window(handle)dosize=browser.manage.window.size[size.width,size.height]endenddefresize_window_to(handle,width,height)within_given_window(handle)dobrowser.manage.window.resize_to(width,height)endenddefmaximize_window(handle)within_given_window(handle)dobrowser.manage.window.maximizeendsleep0.1# work around for https://code.google.com/p/selenium/issues/detail?id=7405enddeffullscreen_window(handle)within_given_window(handle)dobrowser.manage.window.full_screenendenddefclose_window(handle)raiseArgumentError,'Not allowed to close the primary window'ifhandle==window_handles.firstwithin_given_window(handle)dobrowser.closeendenddefwindow_handlesbrowser.window_handlesenddefopen_new_windowbrowser.execute_script('window.open();')enddefswitch_to_window(handle)browser.switch_to.windowhandleenddefaccept_modal(_type,**options)yieldifblock_given?modal=find_modal(options)modal.send_keysoptions[:with]ifoptions[:with]message=modal.textmodal.acceptmessageenddefdismiss_modal(_type,**options)yieldifblock_given?modal=find_modal(options)message=modal.textmodal.dismissmessageenddefquit@browser&.quitrescueSelenium::WebDriver::Error::SessionNotCreatedError,Errno::ECONNREFUSED# rubocop:disable Lint/HandleExceptions# Browser must have already gonerescueSelenium::WebDriver::Error::UnknownError=>errunlesssilenced_unknown_error_message?(err.message)# Most likely already gone# probably already gone but not sure - so warnwarn"Ignoring Selenium UnknownError during driver quit: #{err.message}"endensure@browser=nilenddefinvalid_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,::Selenium::WebDriver::Error::ElementNotSelectableError,::Selenium::WebDriver::Error::NoSuchElementError,# IE::Selenium::WebDriver::Error::InvalidArgumentError# IE]enddefno_such_window_errorSelenium::WebDriver::Error::NoSuchWindowErrorendprivatedefnative_args(args)args.map{|arg|arg.is_a?(Capybara::Selenium::Node)?arg.native:arg}enddefclear_browser_statedelete_all_cookiesclear_storagerescueSelenium::WebDriver::Error::UnhandledError# rubocop:disable Lint/HandleExceptions# delete_all_cookies fails when we've previously gone# to about:blank, so we rescue this error and do nothing# instead.enddefdelete_all_cookies@browser.manage.delete_all_cookiesenddefclear_storageclear_session_storageifoptions[:clear_session_storage]clear_local_storageifoptions[:clear_local_storage]enddefclear_session_storageif@browser.respond_to?:session_storage@browser.session_storage.clearelsewarn'sessionStorage clear requested but is not available for this driver'endenddefclear_local_storageif@browser.respond_to?:local_storage@browser.local_storage.clearelsewarn'localStorage clear requested but is not available for this driver'endenddefnavigate_with_accept(url)@browser.navigate.to(url)sleep0.1# slight wait for alert@browser.switch_to.alert.acceptrescuemodal_error# rubocop:disable Lint/HandleExceptions# alert now gone, should mean navigation happenedenddefmodal_errorSelenium::WebDriver::Error::NoSuchAlertErrorenddefwithin_given_window(handle)original_handle=current_window_handleifhandle==original_handleyieldelseswitch_to_window(handle)result=yieldswitch_to_window(original_handle)resultendenddeffind_modal(text: nil,**options)# Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time# Actual wait time may be longer than specifiedwait=Selenium::WebDriver::Wait.new(timeout: options.fetch(:wait,session_options.default_max_wait_time)||0,ignore: modal_error)beginwait.untildoalert=@browser.switch_to.alertregexp=text.is_a?(Regexp)?text:Regexp.escape(text.to_s)alert.text.match(regexp)?alert:nilendrescueSelenium::WebDriver::Error::TimeOutErrorraiseCapybara::ModalNotFound,"Unable to find modal dialog#{" with #{text}"iftext}"endenddefsilenced_unknown_error_message?(msg)silenced_unknown_error_messages.any?{|regex|msg=~regex}enddefsilenced_unknown_error_messages[/Error communicating with the remote browser/]enddefunwrap_script_result(arg)caseargwhenArrayarg.map{|arr|unwrap_script_result(arr)}whenHasharg.each{|key,value|arg[key]=unwrap_script_result(value)}whenSelenium::WebDriver::Elementbuild_node(arg)elseargendenddefbuild_node(native_node)::Capybara::Selenium::Node.new(self,native_node)enddefspecialize_driver(sel_driver)casesel_driver.browserwhen:chromeextendChromeDriverwhen:firefoxrequire'capybara/selenium/patches/pause_duration_fix'ifsel_driver.capabilities['moz:geckodriverVersion']&.start_with?('0.22.')extendMarionetteDriverifsel_driver.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)endenddefsetup_exit_handlermain=Process.pidat_exitdo# Store the exit status of the test run since it goes away after calling the at_exit proc...@exit_status=$ERROR_INFO.statusif$ERROR_INFO.is_a?(SystemExit)quitifProcess.pid==mainexit@exit_statusif@exit_status# Force exit with stored statusendendendrequire'capybara/selenium/driver_specializations/chrome_driver'require'capybara/selenium/driver_specializations/marionette_driver'