test: simplify clickXpath() by letting click() scroll

Instead of trying to proactively scroll if necessary, just let Selenium
handle scrolling. The only reason to handle scrolling manually was to
tell the difference between an off-screen element and one that isn't
visible due to being beneath another element (like the loading screen).
Catching `ElementClickInterceptedError` is a simpler way to do that.
This commit is contained in:
Christopher Willis-Ford 2023-09-22 13:34:06 -07:00
parent 7aa5a79a0b
commit bc9e771f2c

View file

@ -147,61 +147,30 @@ class SeleniumHelper {
return this.driver.wait(until.stalenessOf(element), DEFAULT_TIMEOUT_MILLISECONDS); return this.driver.wait(until.stalenessOf(element), DEFAULT_TIMEOUT_MILLISECONDS);
} }
async waitUntilClickable (xpath, allowScrolling = true) { clickXpath (xpath) {
return await this.driver.wait(new webdriver.WebElementCondition( return this.driver.wait(new webdriver.WebElementCondition(
'for element to be clickable', 'for element click to succeed',
async () => { async () => {
const elementAtPath = await this.findByXpath(xpath); const element = await this.findByXpath(xpath);
if (!elementAtPath) { if (!element) {
return null; return null;
} }
try {
if (allowScrolling) { await element.click();
await this.driver.actions() return element;
.move({origin: elementAtPath}) } catch (e) {
.perform(); if (e instanceof webdriver.error.ElementClickInterceptedError) {
// something is in front of the element we want to click
// probably the loading screen
// this is the main reason for using wait()
return null;
}
throw e;
} }
const elementAtPoint = await this.driver.executeScript(
`
const rect = arguments[0].getBoundingClientRect();
return document.elementFromPoint(
rect.x + rect.width / 2,
rect.y + rect.height / 2
);
`,
elementAtPath
);
if (!elementAtPoint) {
return null;
}
// If we ask to click on a button and Selenium finds an image on the button, or vice versa, that's OK.
// It doesn't have to be an exact match.
const match = await this.driver.executeScript(
'return arguments[0].contains(arguments[1]) || arguments[1].contains(arguments[0])',
elementAtPath,
elementAtPoint
);
if (!match) {
return null;
}
if (!await elementAtPath.isDisplayed()) {
return null;
}
if (!await elementAtPath.isEnabled()) {
return null;
}
return elementAtPath;
} }
), DEFAULT_TIMEOUT_MILLISECONDS); ), DEFAULT_TIMEOUT_MILLISECONDS);
} }
async clickXpath (xpath, allowScrolling = true) {
const element = await this.waitUntilClickable(xpath, allowScrolling);
element.click();
return element;
}
clickText (text) { clickText (text) {
return this.clickXpath(`//*[contains(text(), '${text}')]`); return this.clickXpath(`//*[contains(text(), '${text}')]`);
} }