2023-10-18 17:00:41 -04:00
|
|
|
jest.setTimeout(30000); // eslint-disable-line no-undef
|
|
|
|
|
2018-07-05 14:16:36 -04:00
|
|
|
const webdriver = require('selenium-webdriver');
|
2023-04-11 09:05:06 -04:00
|
|
|
const {PageLoadStrategy} = require('selenium-webdriver/lib/capabilities');
|
2018-06-27 10:11:12 -04:00
|
|
|
const bindAll = require('lodash.bindall');
|
2018-11-27 11:36:16 -05:00
|
|
|
require('chromedriver');
|
2020-09-23 17:02:55 -04:00
|
|
|
const chromedriverVersion = require('chromedriver').version;
|
2017-08-11 16:13:52 -04:00
|
|
|
|
2018-05-31 11:52:57 -04:00
|
|
|
const headless = process.env.SMOKE_HEADLESS || false;
|
2018-06-25 15:17:15 -04:00
|
|
|
const remote = process.env.SMOKE_REMOTE || false;
|
2018-07-11 14:50:04 -04:00
|
|
|
const ci = process.env.CI || false;
|
2021-05-18 11:32:38 -04:00
|
|
|
const usingCircle = process.env.CIRCLECI || false;
|
2021-05-18 14:36:59 -04:00
|
|
|
const buildID = process.env.CIRCLE_BUILD_NUM || '0000';
|
2018-06-25 15:17:15 -04:00
|
|
|
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
|
2019-05-13 15:02:56 -04:00
|
|
|
const {By, Key, until} = webdriver;
|
2018-05-31 11:52:57 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
// The main reason for this timeout is so that we can control the timeout message and report details;
|
|
|
|
// if we hit the Jasmine default timeout then we get a terse message that we can't control.
|
|
|
|
// The Jasmine default timeout is 30 seconds so make sure this is lower.
|
2019-06-03 16:23:01 -04:00
|
|
|
const DEFAULT_TIMEOUT_MILLISECONDS = 20 * 1000;
|
|
|
|
|
2023-10-18 17:00:41 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add more debug information to an error:
|
|
|
|
* - Merge a causal error into an outer error with valuable stack information
|
|
|
|
* - Add the causal error's message to the outer error's message.
|
|
|
|
* - Add debug information from the web driver, if available.
|
|
|
|
* The outerError compensates for the loss of context caused by `regenerator-runtime`.
|
|
|
|
* @param {Error} outerError The error to embed the cause into.
|
|
|
|
* @param {Error} cause The "inner" error to embed.
|
|
|
|
* @param {webdriver.ThenableWebDriver} [driver] Optional driver to capture debug info from.
|
|
|
|
* @returns {Promise<Error>} The outerError, with the cause embedded.
|
|
|
|
*/
|
|
|
|
const enhanceError = async (outerError, cause, driver) => {
|
|
|
|
if (cause) {
|
|
|
|
// This is the official way to nest errors in modern Node.js, but Jest ignores this field.
|
|
|
|
// It's here in case a future version uses it, or in case the caller does.
|
|
|
|
outerError.cause = cause;
|
|
|
|
}
|
|
|
|
if (cause && cause.message) {
|
|
|
|
outerError.message += `\n${['Cause:', ...cause.message.split('\n')].join('\n ')}`;
|
|
|
|
} else {
|
|
|
|
outerError.message += '\nCause: unknown';
|
|
|
|
}
|
|
|
|
if (driver) {
|
|
|
|
const url = await driver.getCurrentUrl();
|
|
|
|
const title = await driver.getTitle();
|
|
|
|
const pageSource = await driver.getPageSource();
|
|
|
|
const browserLogEntries = await driver.manage()
|
|
|
|
.logs()
|
|
|
|
.get('browser');
|
|
|
|
const browserLogText = browserLogEntries.map(entry => entry.message).join('\n');
|
|
|
|
outerError.message += `\nBrowser URL: ${url}`;
|
|
|
|
outerError.message += `\nBrowser title: ${title}`;
|
|
|
|
outerError.message += `\nBrowser logs:\n*****\n${browserLogText}\n*****\n`;
|
|
|
|
outerError.message += `\nBrowser page source:\n*****\n${pageSource}\n*****\n`;
|
|
|
|
}
|
|
|
|
return outerError;
|
|
|
|
};
|
|
|
|
|
2018-06-27 10:11:12 -04:00
|
|
|
class SeleniumHelper {
|
|
|
|
constructor () {
|
|
|
|
bindAll(this, [
|
|
|
|
'buildDriver',
|
2019-05-20 17:16:34 -04:00
|
|
|
'clickButton',
|
|
|
|
'clickCss',
|
|
|
|
'clickText',
|
2018-06-27 10:11:12 -04:00
|
|
|
'clickXpath',
|
2021-10-20 10:11:33 -04:00
|
|
|
'containsClass',
|
2019-05-20 17:16:34 -04:00
|
|
|
'dragFromXpathToXpath',
|
|
|
|
'findByCss',
|
2018-06-27 10:11:12 -04:00
|
|
|
'findByXpath',
|
|
|
|
'findText',
|
2019-05-20 17:16:34 -04:00
|
|
|
'getKey',
|
|
|
|
'getDriver',
|
|
|
|
'getLogs',
|
|
|
|
'getSauceDriver',
|
2021-08-18 10:22:16 -04:00
|
|
|
'signIn',
|
2019-06-03 16:24:56 -04:00
|
|
|
'urlMatches',
|
|
|
|
'waitUntilGone'
|
2018-06-27 10:11:12 -04:00
|
|
|
]);
|
2023-10-24 14:42:26 -04:00
|
|
|
|
|
|
|
// this type declaration suppresses IDE type warnings throughout this file
|
|
|
|
/** @type {webdriver.ThenableWebDriver} */
|
|
|
|
this.driver = null;
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
2023-10-24 14:42:26 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Build a new webdriver instance. This will use Sauce Labs if the SMOKE_REMOTE environment variable is 'true', or
|
|
|
|
* `chromedriver` otherwise.
|
|
|
|
* @param {string} name The name to give to Sauce Labs.
|
|
|
|
* @returns {webdriver.ThenableWebDriver} The new webdriver instance.
|
|
|
|
*/
|
2018-06-27 10:11:12 -04:00
|
|
|
buildDriver (name) {
|
|
|
|
if (remote === 'true'){
|
2018-07-11 11:17:49 -04:00
|
|
|
let nameToUse;
|
2018-07-11 14:50:04 -04:00
|
|
|
if (ci === 'true'){
|
2023-10-24 14:22:28 -04:00
|
|
|
const ciName = usingCircle ? 'circleCi ' : 'unknown ';
|
|
|
|
nameToUse = `${ciName + buildID} : ${name}`;
|
2018-07-11 11:17:49 -04:00
|
|
|
} else {
|
|
|
|
nameToUse = name;
|
|
|
|
}
|
|
|
|
this.driver = this.getSauceDriver(SAUCE_USERNAME, SAUCE_ACCESS_KEY, nameToUse);
|
2018-06-27 10:11:12 -04:00
|
|
|
} else {
|
|
|
|
this.driver = this.getDriver();
|
|
|
|
}
|
|
|
|
return this.driver;
|
2018-05-31 11:52:57 -04:00
|
|
|
}
|
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Build a new webdriver instance using `chromedriver`.
|
|
|
|
* You should probably use `buildDriver` instead.
|
|
|
|
* @returns {webdriver.ThenableWebDriver} The new webdriver instance.
|
|
|
|
*/
|
2018-06-27 10:11:12 -04:00
|
|
|
getDriver () {
|
|
|
|
const chromeCapabilities = webdriver.Capabilities.chrome();
|
2023-10-24 14:22:28 -04:00
|
|
|
const args = [];
|
2018-06-27 10:11:12 -04:00
|
|
|
if (headless) {
|
|
|
|
args.push('--headless');
|
|
|
|
args.push('window-size=1024,1680');
|
|
|
|
args.push('--no-sandbox');
|
|
|
|
}
|
|
|
|
chromeCapabilities.set('chromeOptions', {args});
|
2023-04-11 09:05:06 -04:00
|
|
|
chromeCapabilities.setPageLoadStrategy(PageLoadStrategy.EAGER);
|
2023-10-24 14:22:28 -04:00
|
|
|
const driver = new webdriver.Builder()
|
2018-06-27 10:11:12 -04:00
|
|
|
.forBrowser('chrome')
|
|
|
|
.withCapabilities(chromeCapabilities)
|
|
|
|
.build();
|
|
|
|
return driver;
|
|
|
|
}
|
2017-08-11 16:13:52 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* @returns {string} The version of chromedriver being used.
|
|
|
|
*/
|
2020-09-14 15:25:01 -04:00
|
|
|
getChromeVersionNumber () {
|
2020-09-23 17:06:13 -04:00
|
|
|
const versionFinder = /\d+\.\d+/;
|
|
|
|
const versionArray = versionFinder.exec(chromedriverVersion);
|
2020-09-23 16:59:00 -04:00
|
|
|
if (versionArray === null) {
|
|
|
|
throw new Error('couldn\'t find version of chromedriver');
|
|
|
|
}
|
2020-09-14 15:25:01 -04:00
|
|
|
return versionArray[0];
|
|
|
|
}
|
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Build a new webdriver instance using Sauce Labs.
|
|
|
|
* You should probably use `buildDriver` instead.
|
|
|
|
* @param {string} username The Sauce Labs username.
|
|
|
|
* @param {string} accessKey The Sauce Labs access key.
|
|
|
|
* @param {string} name The name to give to Sauce Labs.
|
|
|
|
* @returns {webdriver.ThenableWebDriver} The new webdriver instance.
|
|
|
|
*/
|
2018-06-27 10:11:12 -04:00
|
|
|
getSauceDriver (username, accessKey, name) {
|
2020-09-23 17:06:13 -04:00
|
|
|
const chromeVersion = this.getChromeVersionNumber();
|
2018-06-27 10:11:12 -04:00
|
|
|
// Driver configs can be generated with the Sauce Platform Configurator
|
|
|
|
// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator
|
2023-10-24 14:22:28 -04:00
|
|
|
const driverConfig = {
|
2018-06-27 10:11:12 -04:00
|
|
|
browserName: 'chrome',
|
2023-10-03 11:25:26 -04:00
|
|
|
platform: 'macOS 10.15',
|
2020-09-14 15:25:01 -04:00
|
|
|
version: chromeVersion
|
2018-06-27 10:11:12 -04:00
|
|
|
};
|
2023-10-24 14:22:28 -04:00
|
|
|
const driver = new webdriver.Builder()
|
2018-06-27 10:11:12 -04:00
|
|
|
.withCapabilities({
|
|
|
|
browserName: driverConfig.browserName,
|
|
|
|
platform: driverConfig.platform,
|
|
|
|
version: driverConfig.version,
|
|
|
|
username: username,
|
|
|
|
accessKey: accessKey,
|
|
|
|
name: name
|
|
|
|
})
|
|
|
|
.usingServer(`http://${username}:${accessKey
|
|
|
|
}@ondemand.saucelabs.com:80/wd/hub`)
|
|
|
|
.build();
|
|
|
|
return driver;
|
|
|
|
}
|
2017-08-11 16:13:52 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Retrieves a key string by name.
|
|
|
|
* @example
|
|
|
|
* getKey('ENTER') // => '\uE007'
|
|
|
|
* @param {string} keyName The name of the key to retrieve.
|
|
|
|
* @returns {string} The key.
|
|
|
|
*/
|
2019-05-13 15:02:56 -04:00
|
|
|
getKey (keyName) {
|
|
|
|
return Key[keyName];
|
|
|
|
}
|
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Find an element by xpath.
|
|
|
|
* @param {string} xpath The xpath to search for.
|
|
|
|
* @returns {Promise<webdriver.WebElement>} A promise that resolves to the element.
|
|
|
|
*/
|
2023-10-18 17:00:41 -04:00
|
|
|
async findByXpath (xpath) {
|
|
|
|
const outerError = new Error(`findByXpath failed with arguments:\n\txpath: ${xpath}`);
|
|
|
|
try {
|
|
|
|
const el = await this.driver.wait(until.elementLocated(By.xpath(xpath)), DEFAULT_TIMEOUT_MILLISECONDS);
|
|
|
|
await this.driver.wait(el.isDisplayed(), DEFAULT_TIMEOUT_MILLISECONDS);
|
|
|
|
return el;
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2019-06-03 16:23:01 -04:00
|
|
|
}
|
2019-06-03 16:24:56 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* @param {webdriver.WebElement} element Wait until this element is gone (stale).
|
|
|
|
* @returns {Promise} A promise that resolves when the element is gone.
|
|
|
|
*/
|
2023-10-18 17:00:41 -04:00
|
|
|
async waitUntilGone (element) {
|
|
|
|
const outerError = new Error(`waitUntilGone failed with arguments:\n\telement: ${element}`);
|
|
|
|
try {
|
|
|
|
await this.driver.wait(until.stalenessOf(element));
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
2017-08-11 16:13:52 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Wait until an element can be found by the provided xpath, then click on it.
|
|
|
|
* @param {string} xpath The xpath to click.
|
|
|
|
* @returns {Promise} A promise that resolves when the element is clicked.
|
|
|
|
*/
|
|
|
|
async clickXpath (xpath) {
|
2023-10-18 17:00:41 -04:00
|
|
|
const outerError = new Error(`clickXpath failed with arguments:\n\txpath: ${xpath}`);
|
|
|
|
try {
|
|
|
|
const el = await this.findByXpath(xpath);
|
|
|
|
await el.click();
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
2017-08-11 16:13:52 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Wait until an element can be found by the provided text, then click on it.
|
|
|
|
* @param {string} text The text to click.
|
|
|
|
* @returns {Promise} A promise that resolves when the element is clicked.
|
|
|
|
*/
|
2023-10-18 17:00:41 -04:00
|
|
|
async clickText (text) {
|
|
|
|
const outerError = new Error(`clickText failed with arguments:\n\ttext: ${text}`);
|
|
|
|
try {
|
|
|
|
await this.clickXpath(`//*[contains(text(), '${text}')]`);
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
2017-08-11 16:13:52 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Wait until an element can be found by the provided text.
|
|
|
|
* @param {string} text The text to find.
|
|
|
|
* @returns {Promise<webdriver.WebElement>} The element containing the text.
|
|
|
|
*/
|
2023-10-18 17:00:41 -04:00
|
|
|
async findText (text) {
|
|
|
|
const outerError = new Error(`findText failed with arguments:\n\ttext: ${text}`);
|
|
|
|
try {
|
|
|
|
return await this.driver.wait(
|
|
|
|
until.elementLocated(By.xpath(`//*[contains(text(), '${text}')]`)),
|
|
|
|
DEFAULT_TIMEOUT_MILLISECONDS
|
|
|
|
);
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
2017-10-23 21:17:55 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Wait until a button can be found by the provided text, then click on it.
|
|
|
|
* @param {string} text The button text to find and click.
|
|
|
|
* @returns {Promise} A promise that resolves when the button is clicked.
|
|
|
|
*/
|
2023-10-18 17:00:41 -04:00
|
|
|
async clickButton (text) {
|
|
|
|
const outerError = new Error(`clickButton failed with arguments:\n\ttext: ${text}`);
|
|
|
|
try {
|
|
|
|
await this.clickXpath(`//button[contains(text(), '${text}')]`);
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
2017-08-11 16:13:52 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Wait until an element can be found by the provided CSS selector.
|
|
|
|
* @param {string} css The CSS selector to find.
|
|
|
|
* @returns {Promise<webdriver.WebElement>} The element matching the CSS selector.
|
|
|
|
*/
|
2023-10-18 17:00:41 -04:00
|
|
|
async findByCss (css) {
|
|
|
|
const outerError = new Error(`findByCss failed with arguments:\n\tcss: ${css}`);
|
|
|
|
try {
|
|
|
|
return await this.driver.wait(until.elementLocated(By.css(css)), DEFAULT_TIMEOUT_MILLISECONDS);
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
2017-08-11 16:13:52 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Wait until an element can be found by the provided CSS selector, then click on it.
|
|
|
|
* @param {string} css The CSS selector to find and click.
|
|
|
|
* @returns {Promise} A promise that resolves when the element is clicked.
|
|
|
|
*/
|
|
|
|
async clickCss (css) {
|
2023-10-18 17:00:41 -04:00
|
|
|
const outerError = new Error(`clickCss failed with arguments:\n\tcss: ${css}`);
|
|
|
|
try {
|
|
|
|
const el = await this.findByCss(css);
|
|
|
|
await el.click();
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
2018-05-17 11:33:21 -04:00
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Wait until the two elements can be found, then drag from the first to the second.
|
|
|
|
* @param {string} startXpath The xpath to drag from.
|
|
|
|
* @param {string} endXpath The xpath to drag to.
|
|
|
|
* @returns {Promise} A promise that resolves when the drag is complete.
|
|
|
|
*/
|
|
|
|
async dragFromXpathToXpath (startXpath, endXpath) {
|
2023-10-18 17:00:41 -04:00
|
|
|
const outerError = new Error(
|
|
|
|
`dragFromXpathToXpath failed with arguments:\n\tstartXpath: ${startXpath}\n\tendXpath: ${endXpath}`
|
|
|
|
);
|
|
|
|
try {
|
|
|
|
const startEl = await this.findByXpath(startXpath);
|
|
|
|
const endEl = await this.findByXpath(endXpath);
|
|
|
|
await this.driver.actions()
|
|
|
|
.dragAndDrop(startEl, endEl)
|
|
|
|
.perform();
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2019-05-20 17:16:34 -04:00
|
|
|
}
|
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Sign in on a `scratch-www` page.
|
|
|
|
* @param {string} username The username to sign in with.
|
|
|
|
* @param {string} password The password to sign in with.
|
|
|
|
* @returns {Promise} A promise that resolves when the user is signed in.
|
|
|
|
*/
|
2021-11-19 09:38:23 -05:00
|
|
|
async signIn (username, password) {
|
2023-10-18 17:00:41 -04:00
|
|
|
const outerError = new Error(
|
|
|
|
`signIn failed with arguments:\n\tusername: ${username}\n\tpassword: ${password ? 'provided' : 'absent'}`
|
|
|
|
);
|
|
|
|
try {
|
|
|
|
await this.clickXpath('//li[@class="link right login-item"]/a');
|
|
|
|
const name = await this.findByXpath('//input[@id="frc-username-1088"]');
|
|
|
|
await name.sendKeys(username);
|
|
|
|
const word = await this.findByXpath('//input[@id="frc-password-1088"]');
|
|
|
|
await word.sendKeys(password + this.getKey('ENTER'));
|
|
|
|
await this.findByXpath('//span[contains(@class, "profile-name")]');
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2021-08-18 10:22:16 -04:00
|
|
|
}
|
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Wait until the URL matches the provided regex.
|
|
|
|
* @param {RegExp} regex The regex to match the url against.
|
|
|
|
* @returns {Promise} A promise that resolves when the url matches the regex.
|
|
|
|
*/
|
2023-10-18 17:00:41 -04:00
|
|
|
async urlMatches (regex) {
|
|
|
|
const outerError = new Error(`urlMatches failed with arguments:\n\tregex: ${regex}`);
|
|
|
|
try {
|
|
|
|
await this.driver.wait(until.urlMatches(regex), 1000 * 5);
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2019-05-13 15:02:56 -04:00
|
|
|
}
|
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Get selected browser log entries.
|
|
|
|
* @param {Array<string>} whitelist A list of log strings to allow.
|
|
|
|
* @returns {Promise<Array<webdriver.logging.Entry>>} A promise that resolves to the log entries.
|
|
|
|
*/
|
|
|
|
async getLogs (whitelist) {
|
2023-10-18 17:00:41 -04:00
|
|
|
const outerError = new Error(`getLogs failed with arguments:\n\twhitelist: ${whitelist}`);
|
|
|
|
try {
|
|
|
|
const entries = await this.driver.manage()
|
|
|
|
.logs()
|
|
|
|
.get('browser');
|
|
|
|
return entries.filter(entry => {
|
|
|
|
const message = entry.message;
|
|
|
|
for (const element of whitelist) {
|
|
|
|
if (message.indexOf(element) !== -1) {
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
// console.warn('Ignoring whitelisted error: ' + whitelist[i]);
|
|
|
|
return false;
|
|
|
|
} else if (entry.level !== 'SEVERE') { // WARNING: this doesn't do what it looks like it does!
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
// console.warn('Ignoring non-SEVERE entry: ' + message);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
2023-10-24 14:22:28 -04:00
|
|
|
}
|
|
|
|
return true;
|
2023-10-18 17:00:41 -04:00
|
|
|
});
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* Check if an element's class attribute contains a given class.
|
|
|
|
* @param {webdriver.WebElement} element The element to check.
|
|
|
|
* @param {string} cl The class to check for.
|
|
|
|
* @returns {Promise<boolean>} True if the element's class attribute contains the given class, false otherwise.
|
|
|
|
*/
|
2021-10-20 10:11:33 -04:00
|
|
|
async containsClass (element, cl) {
|
2023-10-18 17:00:41 -04:00
|
|
|
const outerError = new Error(`containsClass failed with arguments:\n\telement: ${element}\n\tcl: ${cl}`);
|
|
|
|
try {
|
|
|
|
const classes = await element.getAttribute('class');
|
|
|
|
const classList = classes.split(' ');
|
|
|
|
return classList.includes(cl);
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2021-10-20 10:11:33 -04:00
|
|
|
}
|
|
|
|
|
2023-10-24 14:42:26 -04:00
|
|
|
/**
|
|
|
|
* @param {webdriver.WebElement} element Wait until this element is visible.
|
|
|
|
* @param {webdriver.ThenableWebDriver} driver The webdriver instance.
|
|
|
|
* @returns {Promise} A promise that resolves when the element is visible.
|
|
|
|
*/
|
2021-11-08 16:37:15 -05:00
|
|
|
async waitUntilVisible (element, driver) {
|
2023-10-18 17:00:41 -04:00
|
|
|
const outerError = new Error(`waitUntilVisible failed with arguments:\n\telement: ${element}`);
|
|
|
|
try {
|
|
|
|
await driver.wait(until.elementIsVisible(element));
|
|
|
|
} catch (cause) {
|
|
|
|
throw await enhanceError(outerError, cause, this.driver);
|
|
|
|
}
|
2021-11-08 16:37:15 -05:00
|
|
|
}
|
2018-06-27 10:11:12 -04:00
|
|
|
}
|
2017-08-11 16:13:52 -04:00
|
|
|
|
2018-06-27 10:11:12 -04:00
|
|
|
module.exports = SeleniumHelper;
|