34

I'm writing some RSpec tests for my Rails 3 application and trying to switch from Webrat to Capybara. So far so good but the application uses HTTP basic auth to authorize my admin user, any idea how I can test that with Capybara?

Here is my current Webrat step:

it 'should authenticate for admin' do
  basic_auth('user', 'secret')
  visit '/admin'
  response.status.should eql 200
  response.status.should_not eql 401
end

How do I do this with Capybara? Thanks!

Cimm
  • 4,653
  • 8
  • 40
  • 66

6 Answers6

44

I got it to work using page.driver.basic_authorize(name, password) instead

Update:

At the moment, after a Capybara upgrade, I'm using this pile of workarounds:

if page.driver.respond_to?(:basic_auth)
  page.driver.basic_auth(name, password)
elsif page.driver.respond_to?(:basic_authorize)
  page.driver.basic_authorize(name, password)
elsif page.driver.respond_to?(:browser) && page.driver.browser.respond_to?(:basic_authorize)
  page.driver.browser.basic_authorize(name, password)
else
  raise "I don't know how to log in!"
end
Anders Kindberg
  • 865
  • 10
  • 7
  • 1
    Cool, this works! For future reference: I found a comment by Jonas Nicklas, the creator of Capybara, advising against using the driver as not all possible Capybara drivers support HTTP headers (https://github.com/jnicklas/capybara/issues/issue/17)... if someone would need this. As far as I am concerned this is the solution I need. Thanks Anders! – Cimm Dec 03 '10 at 20:58
  • You rock! `page.driver.browser.basic_authorize` worked for me – Petr Gazarov Mar 23 '17 at 23:52
29

The default Capybara driver, rack-test, has a basic_authorize method (with alias authorize) for Basic HTTP Auth, and digest_authorize for Digest HTTP Auth, here you can find them: https://github.com/brynary/rack-test/blob/master/lib/rack/test.rb

So you can do:

page.driver.browser.authorize 'login', 'password'

Or you can write a simple helper for Basic HTTP Auth:

def basic_auth(user, password)
  encoded_login = ["#{user}:#{password}"].pack("m*")
  page.driver.header 'Authorization', "Basic #{encoded_login}"
end
pablobm
  • 2,026
  • 2
  • 20
  • 30
Szymon Przybył
  • 660
  • 9
  • 10
  • 1
    Your first line {page.driver.browser.authorize 'login', 'password'} works a treat, shame it won't work in a before :each block for an rspec 2 test but it does work fine directly before to a call to visit. Thanks for the answer – jamesc Nov 26 '11 at 12:52
  • 2
    Worth noting that this method works by adding the `Authorization` header to the request, so you need to call this BEFORE `visit`. – Lachlan Cotter Feb 10 '13 at 14:17
  • 2
    When I try to use the first option, I get an error: undefined method `authorize' for # (NoMethodError) has anyone seen anything similar? – danielle Sep 17 '13 at 00:06
  • @Szymon I tried `page.driver.browser.authorize 'login', 'password'` and it works fine but now the rest of examples in my spec file just fail and if I move the one with the basic auth to the bottom they all pass. Is there anything to clean up on the driver after using it on the required pages only? – alexventuraio Apr 06 '23 at 17:25
7

None of the page.driver.* solutions worked for me. I'm using Poltergeist, not Selenium, so that might have something to do with it. Here's what did work:

RSpec.shared_context "When authenticated" do
  before do
    username = 'yourusername'
    password = 'yourpassword'
    visit "http://#{username}:#{password}@#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}/"
  end
end

Then, in your spec:

feature "Your feature", js: true do
  include_context "When authenticated"

  # Your test code here...
end
Pistos
  • 23,070
  • 14
  • 64
  • 77
  • This is easy and clean. Instead of a shared_context, I put in the `config.before(:each) do` block. – B Seven Dec 10 '15 at 17:53
2

This has changed in recent versions of cucumber-rails (I am using 1.0.2).

cucumber-rails uses the Rack/Test driver by default, so if you have not changed that, the following instructions will work.

Create features/step_definitions/authorize.rb:

Given /^I am logged in as "([^\"]*)" with "([^\"]*)"$/ do |username, password|
  authorize username, password
end

Now you can use this in your features:

Given I am logged in as "admin" with "password"
Joost Baaij
  • 7,538
  • 3
  • 32
  • 34
  • Thanks Joost but the question didn't mention Cucumber. It was a Capybara only question. Good to know there is a simple way in Cucumber to do this though. – Cimm Aug 02 '11 at 13:11
  • Regardless if you use RSpec and not Cucumber to run the features, the answer is equally valid if you have kept the default driver in Capybara which is Rack::Test. – Joost Baaij Aug 17 '11 at 07:42
1

I had to do this horrible hack to get it work worth headless and with javascript

Given /^I am logged in$/ do
 if page.driver.respond_to?(:basic_authorize)
   page.driver.basic_authorize('admin', 'password')
 else
   # FIXME for this to work you need to add pref("network.http.phishy-userpass-length", 255); to /Applications/Firefox.app/Contents/MacOS/defaults/pref/firefox.js
   page.driver.visit('/')
   page.driver.visit("http://admin:password@#{page.driver.current_url.gsub(/^http\:\/\//, '')}")
 end
end
Sam
  • 6,240
  • 4
  • 42
  • 53
0

Man, none of these solutions worked for me.

Pistos' solution came close and worked for feature specs with js: true but failed when headless.

This below solution works for me for both headless and js: true specs.

spec/support/when_authenticated.rb

RSpec.shared_context 'When authenticated' do
  background do
    authenticate
  end

  def authenticate
    if page.driver.browser.respond_to?(:authorize)
      # When headless
      page.driver.browser.authorize(username, password)
    else
      # When javascript test
      visit "http://#{username}:#{password}@#{host}:#{port}/"     
     end
  end

  def username
    # Your value here. Replace with string or config location
    Rails.application.secrets.http_auth_username
  end

  def password
    # Your value here. Replace with string or config location
    Rails.application.secrets.http_auth_password
  end

  def host
    Capybara.current_session.server.host
  end

  def port
    Capybara.current_session.server.port
  end
end

Then, in your spec:

feature 'User does something' do
  include_context 'When authenticated'

  # test examples
end
madcow
  • 2,567
  • 14
  • 31