20

I've hit the wall trying to write an integration test for Stripe's checkout.js [ https://checkout.stripe.com/checkout.js ] for my Rails 3.2 app.

Stripe checkout works correctly for me when manually tested (using Stripe's testing keys), but I cannot get Capybara to detect and fill_in the email field in the Stripe checkout iframe modal.

I am using poltergeist for headless javascript, though have also tested this with capybara-webkit and even selenium with the same problem.

What I am trying to test is the complete subscription sign-up flow, to show that a new user can create a subscriber account after entering their payment details in Stripe - but I cannot get past the Stripe checkout pop-up.

Here is my before .. do:

describe "show Stripe checkout", :js => true do
  before do
    visit pricing_path
    click_button 'plan-illuminated'
    stripe_iframe = all('iframe[name=stripe_checkout_app]').last
    Capybara.within_frame stripe_iframe do        
      fill_in "email", :with => "test-user@example.com"
      fill_in "billing-name", :with => "Mr Name"
      fill_in "billing-street", :with => "test Street"
      fill_in "billing-zip", :with => 10000
      fill_in "billing-city", :with => "Berlin"
      click_button "Payment Info"
    end
  end
  it { should have_selector('button', text: "Subscribe") }
end

Which errors with:

Failure/Error: Capybara.within_frame stripe_iframe do
 Capybara::Poltergeist::TimeoutError:
   Timed out waiting for response to {"name":"push_frame","args":[null]}

If I swap out the attempt to choose the correct iframe (suggested here: Capybara trouble filling in JS modal ) like so:

# stripe_iframe = all('iframe[name=stripe_checkout_app]').last
# Capybara.within_frame stripe_iframe do  
Capybara.within_frame 'stripe_checkout_app' do

I still get the similar:

Capybara::Poltergeist::TimeoutError:
   Timed out waiting for response to {"name":"push_frame","args":["stripe_checkout_app"]}

It appears that whichever javascript testing gem I use, rspec/capybara cannot find the Stripe checkout iframe. When I check with Selenium I see the Choose this Plan button pressed and the Checkout pop-up, but the spec times out looking for the email field to fill in.

Any ideas?

I've already tried:

  • Various ways of choosing or finding the email field.
  • Updating all my gems.
  • Using StripeMock before this (not that it should be involved, right?).
  • Running the same tests against Stripe's own site, which give the same errors:

Testing with:

  visit "https://stripe.com/docs/checkout"
  click_button 'Pay with Card'
  stripe_iframe = all('iframe[name=stripe_checkout_app]').last
  Capybara.within_frame stripe_iframe do
    fill_in 'Email', with: 'test@example.com'
    sleep 3
  end

Depending which method I use to select the iframe I receive the same errors. Using just Capybara.within_frame 'stripe_checkout_app' do:

 Failure/Error: Capybara.within_frame stripe_iframe do
 Capybara::Poltergeist::TimeoutError:
   Timed out waiting for response to {"name":"push_frame","args":[null]}

or using Selenium with stripe_iframe = all('iframe[name=stripe_checkout_app]').last:

 Failure/Error: Unable to find matching line from backtrace
 SystemStackError:
   stack level too deep

or even just:

 Failure/Error: fill_in 'Email', with: 'test@example.com'
 Capybara::ElementNotFound:
   cannot fill in, no text field, text area or password field with id, name, or label 'Email' found

...depending on which testing javascript gem I am using.

Any help or wisdom is greatly appreciated!

Community
  • 1
  • 1
djoll
  • 1,139
  • 1
  • 12
  • 31

5 Answers5

11

I could not get any of the solutions here so far to work for me, and then reading this post: https://gist.github.com/nruth/b2500074749e9f56e0b7 I realized that the key was to add a delay to the test to give the Stripe enough time to 1) load the checkout window and 2) process the token.

For that reason the only code that I could get to work was this (feel free to play with timing) :

SELENIUM

describe "test stripe" do, js: true, driver: :selenium do

  before do
    ... # fill in order form or whatever
    click_button "checkout_with_stripe"
    sleep(2) # allows stripe_checkout_app frame to load
    stripe_iframe = all('iframe[name=stripe_checkout_app]').last
    Capybara.within_frame stripe_iframe do
      page.execute_script(%Q{ $('input#email').val('jamesdd9302@yahoo.com'); })
        page.execute_script(%Q{ $('input#card_number').val('4242424242424242'); })
        page.execute_script(%Q{ $('input#cc-exp').val('08/44'); })
        page.execute_script(%Q{ $('input#cc-csc').val('999'); })
        page.execute_script(%Q{ $('#submitButton').click(); })
      sleep(3) # allows stripe_checkout_app to submit
    end
  end

  it "should successfully process the request" do
     ... # test should pass
  end 
end

For Capybara-webkit, the sleep trick didn't work nor sadly did @Ryan's solution, and I got too tired of trying to figure this out so I just stopped, but hopefully someone else will get it because I'd rather use webkit for speed reasons! (I was using capybara-webkit 1.3.0)

In case it helps, here are my relevant versions:

selenium-webdriver 2.35.1
rspec-rails 2.14.2
capybara 2.1.0
stripe 1.16.0 da216fd
rails 4.1.1
ruby 2.1.2
james
  • 3,989
  • 8
  • 47
  • 102
  • This worked perfectly, however I did increase the sleep a bit and instead of putting the code in a before block, I put it into the actual scenario. Either way, great solution. Thanks. – Brian Dear Dec 02 '14 at 21:27
  • Favor using `Capybara.default_wait_time = 2` over `sleep(2)` – Eliot Sykes Apr 23 '15 at 18:03
  • The only working solution for me. It is a pity that I need selenium for this test! – hcarreras Sep 24 '15 at 07:44
5

Problem solved!

With some help from parov via his similar question & answer [ Poltergeist Stripe checkout.js ] I tracked the problem down to using an old version of Capybara '~>1.1.2' and the subsequent dependency-effect this had on the various javascript testing gems (ie. selenium, capybara-webkit, poltergeist...).

Doing a bundle update of Capybara to 2.3.0, and so bringing poltergeist to 1.5.1, with selenium-webdriver at 2.42.0 and capybara-webkit at 1.1.0, gets (almost) everything working.

For selenium, this method via Capybara trouble filling in JS modal does work:

  stripe_iframe = all('iframe[name=stripe_checkout_app]').last
  Capybara.within_frame stripe_iframe do
    fill_in "email", with: "test-user@example.com"
    ...
  end

However, that does not work in poltergeist or capybara-webkit.

For poltergeist, parov's suggestion works:

  stripe = page.driver.window_handles.last
  page.within_window stripe do
    fill_in "email", with: "test-user@example.com"
    ...
  end

For capybara-webkit I wasn't able to get anything working, though given I had something working with poltergeist I didn't put too much time into finding a capybara-webkit solution.

Comments?

Community
  • 1
  • 1
djoll
  • 1,139
  • 1
  • 12
  • 31
  • Selenium version works many thanks, but poltergeist version is not working, it actually stucks there. – vasilakisfil Sep 05 '14 at 14:51
  • Yes, mine has also stopped working with ? recent gem update. Will need to look into it again... grrr. – djoll Sep 06 '14 at 03:48
  • 1
    This doesn't seem to work with recent gem versions (I'm using Poltergeist too), it can't find "Card Number" for me. Any updates? – tirdadc Jul 18 '15 at 14:32
3

I've been fighting with this for some time (as the gist james mentions shows) and in the end found it's too brittle and too slow to test the checkout js.

Instead you can use the following (ruby, capybara, selenium) to stub checkout.js and post your form with a stripe token:

  # optionally ensure the iframe was opened
  expect(page).to have_css('iframe[name="stripe_checkout_app"]')
  # post the form
  token = Stripe::Token.create(
    :card => {
      :number => "4242424242424242",
      :exp_month => 7,
      :exp_year => 2019,
      :cvc => "314",
      address_line1: Faker::Address.street_address,
      address_city: Faker::Address.city,
      address_zip: Faker::Address.postcode,
      address_country: 'United Kingdom'
    },
  )
  page.execute_script("$('#payment_token').val('#{token.id}');")
  page.execute_script("$('#our-stripe-payment-form').submit();")

n.b. this assumes you already have the stripe api gem loaded in your test environment (by your rails app) and have a registered API key etc, otherwise see the docs.

nruth
  • 1,068
  • 7
  • 22
  • Thanks nruth, I'll have to give that a try. – djoll Jul 30 '15 at 11:54
  • This solution seems good but I got the following error on Stripe::Token.create: "Stripe::InvalidRequestError: You must verify a phone number on your Stripe account before you can send raw credit card numbers to the Stripe API. You can avoid this requirement by using Stripe.js, the Stripe mobile bindings, or Stripe Checkout. For more information, see https://dashboard.stripe.com/phone-verification." – Junichi Ito Sep 14 '18 at 20:43
  • @JunichiIto that sounds like a problem with your Stripe account not the testing method. I'd suggest checking you used the test API keys not the real ones, and that you look at verifying your account like they're asking for. – nruth Sep 15 '18 at 23:43
  • @nruth You are right. I got a phone verification mail while I was struggling with my test code – Junichi Ito Sep 16 '18 at 21:56
3

I tried James's answer and modified for my current environment. Here is my code (system spec with Chrome headless):

require 'rails_helper'

describe 'Orders', type: :system do
  before do
    # Temporarily change default_max_wait_time to wait for Stripe response
    @old_wait_time = Capybara.default_max_wait_time
    Capybara.default_max_wait_time = 10
  end

  after do
    Capybara.default_max_wait_time = @old_wait_time
  end

  scenario 'Payment via Stripe', js: true do
    visit payment_path
    click_button 'Pay with Card'

    # Use VCR to avoid actual data creation
    VCR.use_cassette 'orders/payment_via_stripe' do
      expect(page).to have_css('iframe[name="stripe_checkout_app"]')
      stripe_iframe = all('iframe[name=stripe_checkout_app]').last

      Capybara.within_frame stripe_iframe do
        # Set values by placeholders
        fill_in 'Card number', with: '4242424242424242'
        fill_in 'MM / YY', with: '08/44'
        fill_in 'CVC', with: '999'
        # You might need to fill more fields...

        click_button 'Pay $9.99'
      end

      # Confirm payment completed
      expect(page).to have_content 'Payment completed.'
    end
  end
end

I am using:

  • selenium-webdriver 3.14.0
  • rspec-rails 3.8.0
  • capybara 3.7.1
  • stripe 3.26.0
  • rails 5.2.1
  • ruby 2.5.1
  • vcr 4.0.0
  • webmock 3.4.2
  • Chrome 69

My app is built according to https://stripe.com/docs/checkout/rails .

Junichi Ito
  • 2,438
  • 1
  • 23
  • 46
2

For capybara-webkit, I was able to get this to work:

  stripe_iframe = page.driver.window_handles.last
  page.within_window stripe_iframe do
    fill_in "email", with: "test-user@example.com" 
    ...
  end
Ryan Koehler
  • 193
  • 1
  • 8
  • Thanks Ryan. That's the same as my poltergeist code, so I'll give it another go. In general I find poltergeist better than capybara-webkit - though now have gems and the dependencies for all of them kicking around...! – djoll Jun 09 '14 at 23:05