Cypress 12.8.1 не работает с iframe Stripe Elements

Я застрял на этом целую вечность и не могу понять, как заставить Cypress 12.8.1 работать с элементами Stripe для ввода данных кредитной карты и создания платежа.

Я обыскал Интернет, но ни одно из решений, похоже, не работает.

Любая помощь приветствуется.

Я пытался:

  1. Использование xpath https://www.browserstack.com/guide/frames-and-iframes-in-cypress у меня не работает. См. ошибку: https://github.com/cypress-io/cypress/issues/24764#issuecomment-1489438851

  2. Пробовал этот плагин, но он больше не работает. https://stackoverflow.com/a/70024952/10222449https://github.com/dbalatero/cypress-plugin-stripe-elements

  3. Пробовал это, но получил следующую ошибку.

    const $body = $element.contents().find('body')
    let stripe = cy.wrap($body)
    stripe.find('input[name = "number"]').click().type('4242424242424242')
    stripe = cy.wrap($body)
    stripe.find('input[name = "expiry"]').click().type('4242')
    stripe = cy.wrap($body)
    stripe.find('input[name = "cvc"]').click().type('424')
})

  1. Пробовал несколько версий добавления пользовательской команды Cypress «iframeLoaded», но я не могу понять, как добавить их в новый формат машинописного текста Cypress 12 и просто получать ошибки. https://medium.com/@michabahr/testing-stripe-elements-with-cypress-5a2fc17ab27bhttps://bionicjulia.com/blog/cypress-testing-stripe-elements

Мой код в support/commands.ts

// ***********************************************
// This example namespace declaration will help
// with Intellisense and code completion in your
// IDE or Text Editor.
// ***********************************************
declare namespace Cypress {
  interface Chainable<Subject = any> {
    iframeLoaded($iframe: any): typeof iframeLoaded;
  }
}

function iframeLoaded($iframe: any): Promise<any> {
  const contentWindow = $iframe.prop('contentWindow')
  return new Promise(resolve => {
    if (contentWindow && contentWindow.document.readyState === 'complete') {
      resolve(contentWindow)
    } else {
      $iframe.on('load', () => {
        resolve(contentWindow)
      })
    }
  })
}

Cypress.Commands.add('iframeLoaded', {prevSubject: 'element'}, iframeLoaded);

Обновлять:

Я думаю, что у меня есть это, используя ответ Фуди. Я сделал 3 изменения. Пришлось изменить так:

    function getCardField(selector: any, attempts = 0) {
          Cypress.log({displayName: 'getCardField', message: `${selector}: ${attempts}`})
          
          if (attempts > 50) throw new Error('too many attempts')
        
          return cy.get('iframe', {timeout:10_000, log:false})
// CHANGE: .eq(1 to .eq(0
            .eq(0, {log:false})
            .its('0.contentDocument', {log:false})
            .find('body', {log:false})
            .then(body => {
              const cardField = body.find(selector)
              if (!cardField.length) {
                return cy.wait(300, {log:false})
                  .then(() => {
                    getCardField(selector, ++attempts)
                  })
              } else {
                return cy.wrap(cardField)
              }
            })
        }
        
// CHANGE: "div.CardField" to "div.CardNumberField input"
        getCardField('div.CardNumberField input')
          .type('4242424242424242')

// CHANGE: "div.CardField" to "div.CardNumberField-input-wrapper"
getCardField('div.CardNumberField-input-wrapper')
      .find('input').eq(0)
      .should('have.value', '4242 4242 4242 4242')   // passes
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
7
0
344
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Короткий ответ: iframes Stripe требуют времени для загрузки и отображения полей, к которым вам нужно получить доступ, поэтому вам нужно добавить повторную попытку.

Обычно вы используете .should() утверждения, чтобы повторять попытки, пока что-то, что вы хотите, не появится в DOM.

К сожалению, с <iframe> внутри страницы вы не можете использовать .should(), потому что он не повторяет все шаги в цепочке обратно к contentDocument.

Поэтому вам нужно повторить попытку с помощью рекурсивной функции.

Вот рабочий пример с использованием образца страницы Stripe:

cy.intercept({ resourceType: /xhr|fetch/ }, { log: false })  // suppress fetch logs 
cy.viewport(1500, 1000)
cy.visit('https://stripe-payments-demo.appspot.com');  

function getCardField(selector, attempts = 0) {
  Cypress.log({displayName: 'getCardField', message: `${selector}: ${attempts}`})
  
  if (attempts > 50) throw new Error('too many attempts')

  return cy.get('iframe', {timeout:10_000, log:false})
    .eq(1, {log:false})
    .its('0.contentDocument', {log:false}) 
    .find('body', {log:false})
    .then(body => {
      const cardField = body.find(selector)
      if (!cardField.length) {
        return cy.wait(300, {log:false})
          .then(() => {
            getCardField(selector, ++attempts)
          })
      } else {
        return cy.wrap(cardField)
      }
    })
}

getCardField('div.CardField')
  .type('4242424242424242')

getCardField('div.CardField')
  .find('input').eq(0)
  .should('have.value', '4242 4242 4242 4242')   // ✅ passes

Более общая рекурсивная функция

function getStripeField({iframeSelector, fieldSelector}, attempts = 0) {
  Cypress.log({displayName: 'getCardField', message: `${fieldSelector}: ${attempts}`})

  if (attempts > 50) throw new Error('too many attempts')

  return cy.get(iframeSelector, {timeout:10_000, log:false})
    .eq(0, {log:false})
    .its('0.contentDocument', {log:false}) 
    .find('body', {log:false})
    .then(body => {
      const stripeField = body.find(fieldSelector)
      if (!stripeField.length) {
        return cy.wait(300, {log:false})
          .then(() => {
            getStripeField({iframeSelector, fieldSelector}, ++attempts)
          })
      } else {
        return cy.wrap(stripeField)
      }
    })
}
cy.visit('https://hivepass.app/temp-stripe-example.html')

getStripeField({
  iframeSelector: 'iframe[title = "Secure card number input frame"]', 
  fieldSelector: 'div.CardNumberField-input-wrapper'
})
.type('4242424242424242')

getStripeField({
  iframeSelector: 'iframe[title = "Secure card number input frame"]', 
  fieldSelector: 'div.CardNumberField-input-wrapper input'
})
.should('have.value', '4242 4242 4242 4242')

getStripeField({
  iframeSelector: '[title = "Secure expiration date input frame"]', 
  fieldSelector: '[name = "exp-date"]'
})
.type('0323')

getStripeField({
  iframeSelector: '[title = "Secure expiration date input frame"]', 
  fieldSelector: '[name = "exp-date"]'
})
.should('have.value', '03 / 23')

Обратите внимание, что важно повторно запросить поле полосы после обновления при подтверждении его нового значения.

большое спасибо за подробный ответ. Он работает с тестовой страницей Stripe Elements, но я не могу заставить его работать с моей страницей Stripe Elements. Я разместил свою страницу здесь, чтобы вы могли попробовать. Извините за форматирование, так как нет стиля. hivepass.app/temp-stripe-example.html Сейчас работаю над этим, пытаюсь разобраться.

MadMac 31.03.2023 00:44

Я думаю, что он может быть у меня. В настоящее время тестирует дату, CVC и отправку. Смотрите мое обновление выше. Спасибо за указатели. Очень признателен.

MadMac 31.03.2023 05:27

.eq(1) специфичен для страницы https://stripe-payments-demo.appspot.com, поэтому его нужно изменить, но я не смог пройти строку .its('0.contentDocument'). Вместо этого я использовал 0.contentWindow.window.document, и, похоже, это работает.

Fody 31.03.2023 06:07

Теперь он работает в реальном приложении. У меня возникли некоторые проблемы с синхронизацией, когда он получает неправильный iframe, поскольку iframe, который я хочу, еще недоступен, а iframe уже есть на странице. Ссылаться на него по номеру элемента не идеально. На данный момент я поставил cy.wait(10000) перед ним, и, похоже, он работает. Это будет отличный ресурс для будущих людей, использующих Stripe с Cypress, поскольку я не смог найти ничего, что сработало бы.

MadMac 31.03.2023 06:21

Ваше изменение сработало на моем конце, я был заблокирован им по какой-то причине. Я также пропускаю селектор iframe, поскольку в каждом поле есть свой селектор. Рекурсивная функция должна позаботиться о любой задержке при загрузке того или иного iframe, если вы можете найти уникальный селектор для каждого.

Fody 31.03.2023 06:23

Другие вопросы по теме