Skip to content

Instantly share code, notes, and snippets.

@danwhitston
Last active March 31, 2022 21:39
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save danwhitston/5cea26ae0861ce1520695cff3c2c3315 to your computer and use it in GitHub Desktop.
Save danwhitston/5cea26ae0861ce1520695cff3c2c3315 to your computer and use it in GitHub Desktop.
Browser testing for Ruby from within Windows Subsystem for Linux

This is a rough guide to setting up browser testing through Selenium on Windows Subsystem for Linux (WSL), aka Bash on Ubuntu on Windows. It assumes the following environment:

  • Windows 10, running WSL
  • A Ruby dev environment, running inside WSL
  • Code that we want to test using a web driver, in this case Selenium, with a Capybara and RSpec test framework

The coding project folders are stored in the main Windows filing hierarchy and accessed via dev/mnt, but that makes no real difference to development and testing other than making it possible to edit the code using a GUI based editor within Windows.

The problem with browser testing in WSL is that it relies on opening and controlling a web browser, and browsers don’t work on WSL at present as it deliberately doesn’t include X Windows or some other GUI manager - it’s meant to be command line after all. So while you can apt-get firefox, trying to actually run it isn’t going to work.

Some possible solutions

Give WSL a graphical interface

This kind of goes against the point of WSL, but is apparently possible. The idea is to run an X server in Windows and connect it to WSL. I haven’t tried this out yet as it feels slightly horrifying.

Run tests on a local virtual machine

Anything with GUI functionality should get round this issue, and using Docker in particular might open a path to easy automation so it’s not too awkward to run tests during development. This would be important, as I’d prefer not to manually spin up and run tests on a VM each time I want to run tests during coding, at least not at this point. I’ve played with Docker and with Windows’ own virtualisation software, and used to use VirtualBox as a mildly clunky dev environment. However, I haven’t quite reached a workable Dockerfile for my own purposes yet.

Run tests on a remote CI server

CI tools such as Travis can happily run browser tests in a VM, and offer a free integrated service on GitHub repos. This isn’t useful for swift checking of code, or for working through issues in a REPL such as IRB or Pry.

Use a headless browser

I’ve had luck using gems such as Mechanize, but the obvious choice, PhantomJS, has been pretty painful to get working in WSL. This would obviate the X Windows issue, but isn’t as thorough as driving a GUI browser.

Launch and drive a Windows test suite directly from WSL

There’s a tool called Windows Application Driver that could potentially be driven using the to-be-launched interoperability between WSL and Windows.

Run a Windows Selenium server and remote driver

This appears to be the recommended and the cleanest approach. Set up a Selenium server in Windows, and then use a remote client in WSL to carry out testing. Both WSL and Windows have access to the same localhost, so this isn't that complex in theory.

Implemented solution - Windows Selenium server

To set up the Windows Selenium server:

That should get the server up and running. However, it’s still necessary to have some ‘glue’ that connects the server to the browser, for example Chromedriver or Geckodriver, and for the server to know where to find both the glue and the browser executable.

For Firefox (incomplete)

  • Install geckodriver on Windows. Note that the modern webdriver for Firefox is now known as Marionette, but that, if I understand correctly, the old webdriver is used for remote connections such as the one we're setting up.

  • Try something similar to the Chrome code below, but with :firefox instead of :chrome

  • Get errors such as 'Selenium::WebDriver::Error::UnknownError: The path to the driver executable must be set by the webdriver.gecko.driver system property' and try Chrome instead

Worth noting: this page suggests that the path to geckodriver can be set in the file properties of the Selenium server executable, which seems more useful than the standard instructions on how to include the path in a Java program, and may resolve the issue.

For Chrome (implemented)

  • Download Chromedriver, and place it in the Windows PATH where it can be found automatically. I went overboard and copied it to a few places, just to be sure

  • Ensure Chrome can be reached at the default location of C:\Users\%USERNAME%\AppData\Local\Google\Chrome\Application\chrome.exe - I created a shortcut to the actual location on my setup. You can find the actual location via chrome://version/

  • There's also some useful info in the Selenium docs

  • If I'm reading those docs correctly, it may be possible to run Chromedriver directly without a separate Selenium server, but this is the way I've done it for now

Once the server’s all up and running, the following should do the trick in Ruby:

require 'selenium-webdriver'

def setup

  @driver = Selenium::WebDriver.for(:remote, :url => 'http://localhost:4445/wd/hub', :desired_capabilities => :chrome)

  #@driver = Selenium::WebDriver.for :chrome

  @base_url = "http://www.google.com/"

  @driver.manage.timeouts.implicit_wait = 30

  @verification_errors = []

end

Now just run:

setup
@driver.get "https://google.com"

Further examples of methods and usage are available.

Using Capybara with a remote Selenium server

The above solution successfully controls a browser through @driver. However, when using the above solution in IRB after requiring capybara and selenium-webdriver, 'visit' is an undefined method unless forced in with @driver.extend Capybara::DSL, at which point it appears to drop to a default driver. From various stories elsewhere, it appears that recent versions of Capybara may be limiting their functionality to scripts in spec_features.

Using the remote driver solution with actual Capybara tests in RSpec is a somewhat easier experience. Just add the following lines to spec_helper.rb, straight after defining the Capybara.app:

# This sets Capybara up to use a REMOTE Selenium server
Capybara.javascript_driver = :selenium_remote_chrome
Capybara.register_driver "selenium_remote_chrome".to_sym do |app|
  Capybara::Selenium::Driver.new(app, browser: :remote, url: "http://localhost:4445/wd/hub", desired_capabilities: :chrome)
end

Now, when you create a spec that requires JavaScript to complete, Capybara will successfully call the remote Selenium server and return the appropriate result:

feature 'Enter names' do
  scenario 'submitting names', :js => true  do 
    visit('/')
    fill_in :player_1_name, with: 'Dave'
    fill_in :player_2_name, with: 'Mittens'
    click_button 'Submit'
    expect(page).to have_content 'Dave vs. Mittens'
  end
end
@jefraroce
Copy link

Thank you very much for your help :)

@alejandromangione
Copy link

Hey, Thank you so much! I just needed to change server url. .

@driver = Selenium::WebDriver.for(:remote, url: 'http://localhost:9515', desired_capabilities: :chrome)

Windows 10 - Chrome Version 62.0 - The chromedriver.exe located in the same Chrome folder.

@adam-ludgate
Copy link

👍

@designium
Copy link

@danwhitston Thanks for the explanation and time spent on figuring this out. I spent over 8 hours trying different ways to get google-chrome and chromedriver to work on WSL (just got a new desktop computer with Win 10, I normally use Mac) and I bumped into your walkthrough. I tried using Selenium as a server on the Win10 side and it worked great with my ruby code inside WSL.

I did some modifications into your code so we can call chrome options such as headless, no-gpu, etc.

# This sets Capybara up to use a REMOTE Selenium server
Capybara.javascript_driver = :selenium_remote_chrome
Capybara.register_driver "selenium_remote_chrome".to_sym do |app|
   capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
     chromeOptions: { args: %w(headless no-gpu) }  )
  Capybara::Selenium::Driver.new(app, browser: :remote, url: "http://localhost:4220/wd/hub", desired_capabilities: capabilities)
end

agent = Capybara::Session.new(:selenium_remote_chrome)

In my case, I put Capybara inside the agent variable and I can call it or instantiate anywhere.

agent.visit("https://google.com")

Then, I can print all results I want:

agent.all("a").each {|e| p e.text}

Just my 2 cents.

@olepalm
Copy link

olepalm commented Sep 13, 2019

Thanks for the guide, it really helped out a lot.
I've spent a lot of time trying to get the wsl(2) native chromedriver and google-chrome to play nice, but this solution is so far the only one I could get to work.

@marcosg
Copy link

marcosg commented Oct 26, 2019

I found a simpler solution based on this post. Notice that chromeOptions is modified to 'goog:chromeOptions'. I didn't have to install anything in windows, but I did install chrome in the Linux partition.

# spec/rails_helper.rb
Capybara.register_driver :headless_chrome do |app|
  capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
      'goog:chromeOptions': { args: %w(no-sandbox headless disable-gpu window-size=1280,1024 disable-features=VizDisplayCompositor) } )

  Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities)
end

Then the relevant tests:

        describe 'the test that needs javascript', js: true do
          before(:all) do
            # Selenium::WebDriver.logger.level = :debug
            Capybara.current_driver = :headless_chrome
            Capybara.javascript_driver = :headless_chrome
          end

          after(:all) do
            Capybara.use_default_driver
          end

          it 'tests something' do
            expect(1).to eq(1)
          end
      end

@MatthewSchultz
Copy link

MatthewSchultz commented Dec 15, 2019

In Rails 6, I got this working quite well with the Rails default test environment, with a couple extra steps:

Step 1: Disable the webdrivers gem

Comment out gem 'webdrivers' in your Gemfile. Failure to do this means Rails will still be dumb and try to load Chromedriver locally in WSL

Step 2: Add some code to a setup method:

In application_system_test_case.rb (created after you generate your first system test) - add the following setup method and comment out the driven_by method:

#driven_by :selenium, using: :firefox, screen_size: [1400, 1400]
def setup
  Capybara.register_driver "selenium_remote_chrome".to_sym do |app|
    Capybara::Selenium::Driver.new(app, browser: :remote, url: "http://localhost:4445/wd/hub", desired_capabilities: :chrome)
  end
  Capybara.javascript_driver = :selenium_remote_chrome
  Capybara.current_driver = :selenium_remote_chrome
end

@lackovic
Copy link

lackovic commented Jan 7, 2020

A solution which worked for me is to install chromedriver in Windows and run it from WSL.

@marcosg
Copy link

marcosg commented Feb 10, 2020

I agree with @lackovic for several reasons, but mostly because tests are much more consistent. Running headless chrome in Ubuntu, I had failures that would come and go. This problem disappeared running chromedriver in Windows.
Here is an answer where I explained what I did in detail. https://stackoverflow.com/a/59444992/11368140

@stevenpcc
Copy link

stevenpcc commented May 31, 2021

I used the method suggested by lackovic, but did encounter one problem and I haven't seen a solution posted anywhere else so thought I'd share.

When running any system tests with javascript it would fail with this error

RSpec::Core::MultipleExceptionError: unknown error: cannot create temp dir for user data dir

unknown error: cannot create temp dir for user data dir

unknown error: cannot create temp dir for user data dir

It would only work if the tests were run with admin permissions because it was trying to write to C:/windows. Chromedriver uses GetTempPath() to find the temp folder, which first checks for environment variables like TMP and TEMP (and others) before defaulting to the windows folder. So the solution is to make those environment variables available to chromedriver when launched in WSL.

Solution: Create a user environment variable named WSLENV in windows, with the the value TMP/p:TEMP/p

New Project (11)

@lackovic
Copy link

lackovic commented Jun 1, 2021

Solution: Create a user environment variable named WSLENV in windows, with the the value TMP/p:TEMP/p

Thank you for sharing! Which option did you choose among those I listed?

@stevenpcc
Copy link

I used "Option 2: use chromedriver from Windows".

@markokajzer
Copy link

I think the Implemented solution as described above might not actually be valid for WSL2. Apparently WSL2 cannot access networking apps running on Windows, see microsoft/WSL#5211.

@stevenpcc
Copy link

I forgot to mention it in my original post but I was using WSL 1.

@peeyush14goyal
Copy link

@stevepcc please update the Readme file that its for WSL 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment