We recently replaced PhantomJS with ChromeDriver for system tests in a project since PhantomJS is no longer maintained. Many modern browser features required workarounds and hacks to work on PhantomJS. For example the Element.trigger('click') method does not actually click an element but simulates a DOM click event. These workarounds meant that code was not being tested as the code would behave in real production environment.
ChromeDriver Installation & Configuration
ChromeDriver is needed to use Chrome as the browser for system tests. It can be installed on macOS using homebrew.
1brew cask install chromedriver
Remove poltergeist from Gemfile and add selenium-webdriver.
1#Gemfile 2 3- gem "poltergeist" 4+ gem "selenium-webdriver"
Configure Capybara to use ChromeDriver by adding following snippet.
1require 'selenium-webdriver' 2 3Capybara.register_driver(:chrome_headless) do |app| 4 args = [] 5 args << 'headless' unless ENV['CHROME_HEADLESS'] 6 7 capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( 8 chromeOptions: { args: args } 9 ) 10 11 Capybara::Selenium::Driver.new( 12 app, 13 browser: :chrome, 14 desired_capabilities: capabilities 15 ) 16end 17 18Capybara.default_driver = :chrome_headless 19
Above code would run tests in headless mode by default. For debugging purpose we would like to see the actual browser. That can be easily done by executing following command.
1CHROME_HEADLESS=false bin/rails test:system
After switching from Phantom.js to "headless chrome", we ran into many test failures due to the differences in implementation of Capybara API when using ChromeDriver. Here are solutions to some of the issues we faced.
1. Element.trigger('click') does not exist
Element.trigger('click') simulates a DOM event to click instead of actually clicking the element. This is a bad practice because the element might be obscured behind another element and still trigger the click. Selenium does not support this method, Element.click works as the solution but it is not a replacement. We can replace Element.trigger('click') with Element.send_keys(:return) or by executing javascript to trigger click event.
1#example 2 3find('.foo-link').trigger('click') 4 5# solutions 6 7find('.foo-link').click 8 9# or 10 11find('.foo-link').send_keys(:return) 12 13# or 14# if the link is not visible or is overlapped by another element 15 16execute_script("$('.foo-link').click();") 17
2. Element is not visible to click
When we switched to Element.click, some tests were failing because the element was not visible as it was behind another element. The easiest solution to fix these failing test was using Element.send_keys(:return) but purpose of the test is to simulate a real user clicking the element. So we had to make sure the element is visible. We fixed the UI issues which prevented the element from being visible.
3. Setting value of hidden fields do not work
When we try to set the value of a hidden input field using the set method of an element, Capybara throws a element not interactable error.
1#example 2find(".foo-field", visible: false).set("some text") 3#Error: element not interactable 4 5#solution 6page.execute_script('$(".foo-field").val("some text")')
4. Element.visible? returns false if the element is empty
ignore_hidden_elements option of Capybara is false by default. If ignore_hidden_elements is true, Capybara will find elements which are only visible on the page. Let's say we have <div class="empty-element"></div> on our page. find(".empty-element").visible? returns false because selenium considers empty elements as invisible. This issue can be resolved by using visible: :any.
1 2#example 3 4#ignore hidden elements 5Capybara.ignore_hidden_elements = true 6 7find(".empty-element").visible? 8# returns false 9 10#solution 11find('.empty-element', visible: :any) 12 13#or 14 15find('.empty-element', visible: :all) 16 17#or 18 19find('.empty-element', visible: false) 20