How to write integration tests for Stripe checkout

2019-01-22 20:03发布

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!

5条回答
迷人小祖宗
2楼-- · 2019-01-22 20:35

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
查看更多
一纸荒年 Trace。
3楼-- · 2019-01-22 20:40

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?

查看更多
戒情不戒烟
4楼-- · 2019-01-22 20:43

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.

查看更多
Emotional °昔
5楼-- · 2019-01-22 20:44

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
查看更多
Rolldiameter
6楼-- · 2019-01-22 20:55

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 .

查看更多
登录 后发表回答