microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
66.08k stars 3.61k forks source link

[BUG] Stripe iframe does not load in test when running in Github actions #6148

Closed reidbuzby closed 3 years ago

reidbuzby commented 3 years ago

Context:

Code Snippet I have a simple credit card form using Stripe Elements that looks something like this:

import { Elements, CardElement } from '@stripe/react-stripe-js';

function CardForm() {
  const [stripe, setStripe] = useState(null);

  useEffect(() => {
    setStripe(Stripe(<stripe_key>));
  });

  return (
    <div>
      <script src="https://js.stripe.com/v3/" />
      <Elements stripe={stripe}>
        <CardElement />
      </Elements>
    </div>
  );
}

And a Playwright test to fill the credit card form that looks something like:

const frames = page.frames();

// Find the stripe credit card iframe
const cardFrame = find(f => f._name.startsWith('__privateStripeFrame')(frames);

await cardFrame.fill(
  '[data-elements-stable-field-name="cardNumber"]',
  card.number
);
await cardFrame.fill(
  '[data-elements-stable-field-name="cardExpiry"]',
  `${card.exp_month}/${card.exp_year}`
);
await cardFrame.fill(
  '[data-elements-stable-field-name="cardCvc"]',
  card.cvc
);
await cardFrame.fill(
  '[data-elements-stable-field-name="postalCode"]',
  '12345'
);

Describe the bug When I run my test locally (in headless and headful), everything works perfectly and the Stripe CardElement iframe loads and gets filled successfully. However, when I run my test in Github Actions as a workflow, the iframe fails to render. Even if I wait 10+ seconds, the iframe never gets loaded. I am getting some sort of error in the useEffect where I call setStripe, but all that logs is JSHandle@error. I think it could be an issue loading the Stripe script from a different domain so I added a bunch of args to my browser setup as some similar posts have suggested:

browser = await chromium.launch({
  headless: IS_HEADLESS,
  chromiumSandbox: false,
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-web-security',
    '--disable-features=IsolateOrigins,site-per-process',
    '--disable-site-isolation-trials',
    '--allow-running-insecure-content',
    '--ignore-certificate-errors',
    '--allow-insecure-localhost'
  ]
});

But none of these have fixed the issue. Again, this only happens in CI and never reproduces locally. I have enabled screen recordings for the CI tests to see exactly what is going on, and the CardElement iframe just never renders.

Please, any help here would be much appreciated.

reidbuzby commented 3 years ago

I have also tried including stripe as a module instead of including it as a script using loadStripe. When I do this, the CardElement does actually load successfully, however when I try to create a token with the successfully inputted card info the createToken function hangs forever. Here is my basic function:

import { loadStripe } from '@stripe/stripe-js';

const stripe = loadStripe(<stripe_pk>);

async function generateCardToken(card) {
  const res = await (await stripe).createToken(card);

  return res;
};

Again, this function works locally without any issues and in the Playwright tests without any issues so I don't think it is a problem with the generateCardToken function. It just hangs when running the Playwright tests in Github Actions. The createToken call also does not throw an error in this case, it just hangs forever waiting for a response to be returned.

JoelEinbinder commented 3 years ago

Can you log the network requests that the test makes? Here is an example from the docs: https://playwright.dev/docs/network?_highlight=network#network-events

My suspicion is that stripe blocks requests from github actions. Maybe some kind of spam/bot filter?

dgozman commented 3 years ago

I am getting some sort of error in the useEffect where I call setStripe, but all that logs is JSHandle@error

Could you also try the following to get some idea of the error?

page.on('console', console.log);
page.on('pageerror', console.log);
dgozman commented 3 years ago

Closing since we cannot reproduce this issue. Please comment here if you have a repro case and we'll reopen.

edspencer commented 3 years ago

Just another data point: this works as expected for me when running Playwright tests against a Stripe integration executed inside Github actions.

cmacdonnacha commented 2 years ago

Here's the newest way to achieve this using Playwright's FrameLocator API which was added to v.1.17.

const stripeFrame = page.frameLocator('iframe').first();
await stripeFrame.locator('[placeholder="Card number"]').fill('4242424242424242');
await stripeFrame.locator('[placeholder="MM / YY"]').fill('04/30');
await stripeFrame.locator('[placeholder="CVC"]').fill('242');
mcbridemarci commented 8 months ago

It's best to locate the specific iframe:

const iframeLocator = this.page.frameLocator('div.__PrivateStripeElement iframe')
const cardNumber = await iframeLocator.locator('#Field-numberInput')
await cardNumber.click()
await cardNumber.fill('4242424242424242')

const expDate = await iframeLocator.locator('#Field-expiryInput')
await expDate.click()
await expDate.fill('12/34')

const cvc = await iframeLocator.locator('#Field-cvcInput')
await cvc.click()
await cvc.fill('567')

const country = await iframeLocator.locator('#Field-countryInput')
await country.selectOption('United States')

const zipCode = await iframeLocator.locator('#Field-postalCodeInput')
await zipCode.click()
await zipCode.fill('12345')