【问题标题】:Selenium send keys incorrect order in Stripe credit card inputSelenium 在 Stripe 信用卡输入中发送密钥的顺序不正确
【发布时间】:2019-03-07 14:14:54
【问题描述】:

使用 selenium 将键发送到输入字段后,结果与预期不符 - 键的插入顺序不正确。

例如send_keys('4242424242424242') -> 结果是“4224242424242424”

编辑:在某些机器上,我只是随机观察到问题,10 次尝试中只有 1 次。在另一台机器上是 10/10

Stripe 付款表单会发生这种情况 + 我只在 Chrome 69 版中看到这个问题(在以前的版本中它工作正常)

这可以在示例 Stripe 网站上轻松复制:https://stripe.github.io/elements-examples/

示例 python 代码:

from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://stripe.github.io/elements-examples/')
driver.switch_to.frame(driver.find_element_by_tag_name('iframe'))  # First iframe
cc_input = driver.find_element_by_css_selector('input[name="cardnumber"]')
cc_input.send_keys('4242424242424242')

结果:

我可以通过一一发送密钥稍有延迟来通过这个 - 但这也不是 100% 可靠的(而且速度非常慢)

我不确定这是 selenium (3.14.1)/chromedriver (2.41.578737) 的问题,还是我做错了什么。

有什么想法吗?

【问题讨论】:

  • 作为一个小点,Stripe 通常不建议以这种方式对 Elements 表单进行自动化测试(因为内容是在 iframe 中动态加载的,并且 ID 和 DOM 结构等内容可能会发生变化随时)。为了进行测试,您可以模拟整个付款表单并将test token 传递给您的后端。
  • 刚刚试过你的脚本,我可以看到脚本运行正常。它进入 4242 4242 4242 4242。
  • 先尝试清除cookies,再检查
  • @karllekko 好点,我想我将不得不这样做。不幸的是,这是我的 E2E 脚本的一部分,例如在 CC 无效等情况下测试应用程序的正确错误处理。我不确定我是否能够仅使用令牌进行测试
  • @Vafliik :是的,我在 chrome 版本 69.0.3497.100(官方版本)(64 位)中进行了尝试。请清除您的浏览历史并再试一次:)

标签: selenium google-chrome selenium-chromedriver stripe-payments


【解决方案1】:

我们在 MacOS 和 Ubuntu 18.04 上以及在我们的带有量角器 5.4.1 和相同版本的 selenium 和 chromedriver 的 CI 服务器上遇到了完全相同的问题。从 Chrome 69 开始,它才开始出现故障,在 v70 中更糟。

更新 - 工作(目前)

经过进一步调查,我记得 React 倾向于覆盖更改/输入事件,并且信用卡输入、ccv 输入等中的值是从 React 组件状态呈现的,而不仅仅是从输入值。于是我开始寻找,发现What is the best way to trigger onchange event in react js

我们的测试正在运行(目前):

//Example credit input
function creditCardInput (): ElementFinder {
  return element(by.xpath('//input[contains(@name, "cardnumber")]'))
}

/// ... snippet of our method ...
await ensureCreditCardInputIsReady()
await stripeInput(creditCardInput, ccNumber)
await stripeInput(creditCardExpiry, ccExpiry)
await stripeInput(creditCardCvc, ccCvc)
await browser.wait(this.hasCreditCardZip(), undefined, 'Should have a credit card zip')
await stripeInput(creditCardZip, ccZip)
await browser.switchTo().defaultContent()
/// ... snip ...

async function ensureCreditCardInputIsReady (): Promise<void> {
  await browser.wait(ExpectedConditions.presenceOf(paymentIFrame()), undefined, 'Should have a payment iframe')
  await browser.switchTo().frame(await paymentIFrame().getWebElement())
  await browser.wait(
    ExpectedConditions.presenceOf(creditCardInput()),
    undefined,
    'Should have a credit card input'
  )
}

/**
 * SendKeys for the Stripe gateway was having issues in Chrome since version 69. Keys were coming in out of order,
 * which resulted in failed tests.
 */
async function stripeInput (inputElement: Function, value: string): Promise<void> {
  await browser.executeScript(`
      var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
          nativeInputValueSetter.call(arguments[0], '${value}');
      var inputEvent = new Event('input', { bubbles: true});
          arguments[0].dispatchEvent(inputEvent);
        `, inputElement()
  )
  await browser.sleep(100)
  const typedInValue = await inputElement().getWebElement().getAttribute('value')

  if (typedInValue.replace(/\s/g, '') === value) {
    return
  }

  throw new Error(`Failed set '${typedInValue}' on ${inputElement}`)
}

以前的想法(只是偶尔工作):

我已经使用https://stripe.com/docs/stripe-js/elements/quickstart 设置了一个最小复制,当测试按顺序运行时它会成功,但不是并行运行(我们认为是由于切换到 iframe 时的焦点/模糊问题)。

我们的解决方案是相似的,尽管我们从测试中注意到 input.clear() 不适用于 iframe 中使用的 tel 输入。

这仍然偶尔会失败,但频率要低得多。

/**
 * Types a value into an input field, and checks if the value of the input
 * matches the expected value. If not, it attempts for `maxAttempts` times to
 * type the value into the input again.
 *
 * This works around an issue with ChromeDriver where sendKeys() can send keys out of order,
 * so a string like "0260" gets typed as "0206" for example.
 *
 * It also works around an issue with IEDriver where sendKeys() can press the SHIFT key too soon
 * and cause letters or numbers to be converted to their SHIFT variants, "6" gets typed as "^", for example.
 */
export async function slowlyTypeOutField (
  value: string,
  inputElement: Function,
  maxAttempts = 20
): Promise<void> {
  for (let attemptNumber = 0; attemptNumber < maxAttempts; attemptNumber++) {
    if (attemptNumber > 0) {
      await browser.sleep(100)
    }

    /*
      Executing a script seems to be a lot more reliable in setting these flaky fields than using the sendKeys built-in
      method. However, I struggled in finding out which JavaScript events Stripe listens to. So we send the last key to
      the input field to trigger all events we need.
     */
    const firstPart = value.substring(0, value.length - 1)
    const secondPart = value.substring(value.length - 1, value.length)
    await browser.executeScript(`
        arguments[0].focus();
        arguments[0].value = "${firstPart}";
      `,
      inputElement()
    )
    await inputElement().sendKeys(secondPart)
    const typedInValue = await inputElement().getAttribute('value')

    if (typedInValue === value) {
      return
    }

    console.log(`Tried to set value ${value}, but instead set ${typedInValue} on ${inputElement}`)
  }

  throw new Error(`Failed after ${maxAttempts} attempts to set value on ${inputElement}`)
}

【讨论】:

  • 非常感谢@Benno。这实际上帮助我解决了问题!
【解决方案2】:

我在 ubuntu 14.04 中遇到了类似的问题,以下技巧帮助了我。 从那以后没有任何问题。 首先我使用了常规的 send_keys 方法。 然后我调用执行脚本来更新值

    input_data = "someimputdata"
    some_xpath = "//*[contains(@id,'input_fax.number_')]"
    element = web_driver_obj.find_element_by_xpath(some_xpath)
    element.clear()
    element.send_keys(input_data)
    web_driver_obj.execute_script("arguments[0].value = '{0}';".format(input_data), element)

【讨论】:

    【解决方案3】:

    编辑

    非常感谢@Benno - 他的回答是正确的。 我将根据他的 JS 添加对我有用的 python 解决方案

    driver.get('https://stripe.github.io/elements-examples/')
    driver.switch_to.frame(driver.find_element_by_tag_name('iframe'))  # First iframe
    cc_input = driver.find_element_by_css_selector('input[name="cardnumber"]')
    value = "4242424242424242"
    driver.execute_script('''
     input = arguments[0];
     var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; 
     nativeInputValueSetter.call(input, "{}");
     var eventCard = new Event("input", {{bubbles: true}});
     input.dispatchEvent(eventCard);
     '''.format(value), cc_input)
    driver.switch_to.default_content()
    driver.quit()
    

    经过几个小时的尝试,我放弃并接受了这确实是一个随机问题的事实,并采取了解决方法。

    1. 不需要更新的地方,我会继续使用Chrome版本
    2. 为了测试最新的 Chrome,我将使用 React 解决方案

    我发现了什么

    这个问题主要出现在 MacOS 上,很少出现在 Windows 上(很可能还有其他因素在起作用,这只是一个观察结果)

    我进行了一个实验,重复填写了 100 次表格。

    Mac - 68 次失败

    Windows - 6 次失败

    cookies/本地历史(如 cmets 中所建议的) 似乎不是问题。网络驱动程序始终生成一个“干净”的浏览器实例,没有 cookie 或本地存储。

    【讨论】:

      【解决方案4】:

      也许我的解决方案会对某人有所帮助:

      我用sendKeys(" 4242424242424242")
      cvc 字段也一样

      字符串前有一个空格,实际上对 selenide + chrome + java 有效

      【讨论】:

        【解决方案5】:

        您可以创建自己的通用 SendKeys 方法,该方法采用输入元素和您要发送的字符串。该方法会将字符串拆分为单个字符,然后对每个字符使用 selenium sendkeys 方法。

        【讨论】:

          【解决方案6】:

          无论出于何种原因,添加一些退格对我都有效:

          from selenium.webdriver.common.keys import Keys
          
          my_value = "123"
          my_xpath="//input[@class='form-text']"
          element = driver.find_element_by_xpath(my_xpath)
          element.clear()
          element.send_keys(Keys.BACKSPACE * 3, my_value)
          

          【讨论】:

            【解决方案7】:

            我在使用 RSelenium 时遇到了同样的问题,我想尝试在信用卡号上添加空格,因为它们出现在卡上,因为它们出现在 @Pavel 的回答中,因为在卡号之前添加一个空格对我不起作用。

            使用 RSelenium 这将是:

            element$sendKeysToElement(list("4242 4242 4242 4242"))

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-11-10
              • 1970-01-01
              • 2018-07-24
              • 1970-01-01
              相关资源
              最近更新 更多