У нас точно такая же проблема на MacOS и Ubuntu 18.04, а также на нашем CI-сервере с транспортиром 5.4.1 и той же версией селена и хромедривера.Он начал давать сбой только после Chrome 69, хуже в v70.
Обновление - Работает (на данный момент)
После долгих дальнейших исследований я вспомнил, что React имеет тенденцию кпереопределить события изменения / ввода, а также то, что значения на входе кредитной карты, входе ccv и т. д. отображаются из состояния компонента реакции, а не только из входного значения.Поэтому я начал искать и нашел Каков наилучший способ вызвать событие onchange в реакции 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, и он успешен, когда тесты выполняются последовательно, но не параллельно (мы думаем, чтодля фокусировки / размытия при переключении на iframes).
Наше решение аналогично, хотя мы наблюдали за просмотром тестов, что input.clear () не работал на входах tel, которые используются в iframe.
Иногда это не удается, но гораздо реже.
/**
* 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}`)
}