2021-02-04 19:15:23 +01:00
|
|
|
const { promises: fs } = require('fs');
|
|
|
|
const { strict: assert } = require('assert');
|
2021-04-02 21:45:11 +02:00
|
|
|
const { until, error: webdriverError, By } = require('selenium-webdriver');
|
2020-01-13 16:07:32 +01:00
|
|
|
|
|
|
|
class Driver {
|
|
|
|
/**
|
|
|
|
* @param {!ThenableWebDriver} driver - A {@code WebDriver} instance
|
|
|
|
* @param {string} browser - The type of browser this driver is controlling
|
|
|
|
* @param {number} timeout
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
constructor(driver, browser, extensionUrl, timeout = 10000) {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.driver = driver;
|
|
|
|
this.browser = browser;
|
|
|
|
this.extensionUrl = extensionUrl;
|
|
|
|
this.timeout = timeout;
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 21:45:11 +02:00
|
|
|
buildLocator(locator) {
|
|
|
|
if (typeof locator === 'string') {
|
|
|
|
// If locator is a string we assume its a css selector
|
|
|
|
return By.css(locator);
|
|
|
|
} else if (locator.value) {
|
|
|
|
// For backwards compatibility, checking if the locator has a value prop
|
|
|
|
// tells us this is a Selenium locator
|
|
|
|
return locator;
|
|
|
|
} else if (locator.xpath) {
|
|
|
|
// Providing an xpath prop to the object will consume the locator as an
|
|
|
|
// xpath locator.
|
|
|
|
return By.xpath(locator.xpath);
|
|
|
|
} else if (locator.text) {
|
|
|
|
// Providing a text prop, and optionally a tag, will use xpath to look
|
|
|
|
// for an element with the tag that has matching text.
|
|
|
|
return By.xpath(
|
|
|
|
`//${locator.tag ?? '*'}[contains(text(), '${locator.text}')]`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
throw new Error(
|
|
|
|
`The locator '${locator}' is not supported by the E2E test driver`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async delay(time) {
|
2021-02-04 19:15:23 +01:00
|
|
|
await new Promise((resolve) => setTimeout(resolve, time));
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async wait(condition, timeout = this.timeout) {
|
2021-02-04 19:15:23 +01:00
|
|
|
await this.driver.wait(condition, timeout);
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async quit() {
|
2021-02-04 19:15:23 +01:00
|
|
|
await this.driver.quit();
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Element interactions
|
|
|
|
|
2021-04-02 21:45:11 +02:00
|
|
|
async findElement(rawLocator) {
|
|
|
|
const locator = this.buildLocator(rawLocator);
|
2021-02-04 19:15:23 +01:00
|
|
|
return await this.driver.wait(until.elementLocated(locator), this.timeout);
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 21:45:11 +02:00
|
|
|
async findVisibleElement(rawLocator) {
|
|
|
|
const locator = this.buildLocator(rawLocator);
|
2021-02-04 19:15:23 +01:00
|
|
|
const element = await this.findElement(locator);
|
|
|
|
await this.driver.wait(until.elementIsVisible(element), this.timeout);
|
|
|
|
return element;
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 21:45:11 +02:00
|
|
|
async findClickableElement(rawLocator) {
|
|
|
|
const locator = this.buildLocator(rawLocator);
|
2021-02-04 19:15:23 +01:00
|
|
|
const element = await this.findElement(locator);
|
2020-01-15 20:34:15 +01:00
|
|
|
await Promise.all([
|
|
|
|
this.driver.wait(until.elementIsVisible(element), this.timeout),
|
|
|
|
this.driver.wait(until.elementIsEnabled(element), this.timeout),
|
2021-02-04 19:15:23 +01:00
|
|
|
]);
|
|
|
|
return element;
|
2020-01-15 20:34:15 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 21:45:11 +02:00
|
|
|
async findElements(rawLocator) {
|
|
|
|
const locator = this.buildLocator(rawLocator);
|
2021-02-04 19:15:23 +01:00
|
|
|
return await this.driver.wait(until.elementsLocated(locator), this.timeout);
|
2020-01-15 20:34:15 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 21:45:11 +02:00
|
|
|
async findClickableElements(rawLocator) {
|
|
|
|
const locator = this.buildLocator(rawLocator);
|
2021-02-04 19:15:23 +01:00
|
|
|
const elements = await this.findElements(locator);
|
2020-11-03 00:41:28 +01:00
|
|
|
await Promise.all(
|
|
|
|
elements.reduce((acc, element) => {
|
2020-01-15 20:34:15 +01:00
|
|
|
acc.push(
|
|
|
|
this.driver.wait(until.elementIsVisible(element), this.timeout),
|
|
|
|
this.driver.wait(until.elementIsEnabled(element), this.timeout),
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
|
|
|
return acc;
|
2020-11-03 00:41:28 +01:00
|
|
|
}, []),
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
|
|
|
return elements;
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 21:45:11 +02:00
|
|
|
async clickElement(rawLocator) {
|
|
|
|
const locator = this.buildLocator(rawLocator);
|
2021-02-04 19:15:23 +01:00
|
|
|
const element = await this.findClickableElement(locator);
|
|
|
|
await element.click();
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 21:45:11 +02:00
|
|
|
async clickPoint(rawLocator, x, y) {
|
|
|
|
const locator = this.buildLocator(rawLocator);
|
2021-02-04 19:15:23 +01:00
|
|
|
const element = await this.findElement(locator);
|
2020-05-27 17:31:53 +02:00
|
|
|
await this.driver
|
|
|
|
.actions()
|
|
|
|
.move({ origin: element, x, y })
|
|
|
|
.click()
|
2021-02-04 19:15:23 +01:00
|
|
|
.perform();
|
2020-05-27 17:31:53 +02:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async scrollToElement(element) {
|
|
|
|
await this.driver.executeScript(
|
|
|
|
'arguments[0].scrollIntoView(true)',
|
|
|
|
element,
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2021-04-02 21:45:11 +02:00
|
|
|
async assertElementNotPresent(rawLocator) {
|
|
|
|
const locator = this.buildLocator(rawLocator);
|
2021-02-04 19:15:23 +01:00
|
|
|
let dataTab;
|
2020-01-13 16:07:32 +01:00
|
|
|
try {
|
2021-02-04 19:15:23 +01:00
|
|
|
dataTab = await this.findElement(locator);
|
2020-01-13 16:07:32 +01:00
|
|
|
} catch (err) {
|
2020-11-03 00:41:28 +01:00
|
|
|
assert(
|
|
|
|
err instanceof webdriverError.NoSuchElementError ||
|
|
|
|
err instanceof webdriverError.TimeoutError,
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
assert.ok(!dataTab, 'Found element that should not be present');
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2020-01-20 18:03:07 +01:00
|
|
|
// Navigation
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async navigate(page = Driver.PAGES.HOME) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return await this.driver.get(`${this.extensionUrl}/${page}.html`);
|
2020-01-20 18:03:07 +01:00
|
|
|
}
|
|
|
|
|
Add benchmark script (#7869)
The script `benchmark.js` will collect page load metrics from the
extension, and print them to a file or the console. A method for
collecting metrics was added to the web driver to help with this.
This script will calculate the min, max, average, and standard
deviation for four metrics: 'firstPaint', 'domContentLoaded', 'load',
and 'domInteractive'. The variation between samples is sometimes high,
with the results varying between samples if only 3 were taken. However,
all tests I've done locally with 5 samples have produced results within
one standard deviation of each other. The default number of samples has
been set to 10, which should be more than enough to produce consistent
results.
The benchmark can be run with the npm script `benchmark:chrome` or
`benchmark:firefox`, e.g. `yarn benchmark:chrome`.
2020-01-21 17:02:45 +01:00
|
|
|
// Metrics
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async collectMetrics() {
|
2021-02-04 19:15:23 +01:00
|
|
|
return await this.driver.executeScript(collectMetrics);
|
Add benchmark script (#7869)
The script `benchmark.js` will collect page load metrics from the
extension, and print them to a file or the console. A method for
collecting metrics was added to the web driver to help with this.
This script will calculate the min, max, average, and standard
deviation for four metrics: 'firstPaint', 'domContentLoaded', 'load',
and 'domInteractive'. The variation between samples is sometimes high,
with the results varying between samples if only 3 were taken. However,
all tests I've done locally with 5 samples have produced results within
one standard deviation of each other. The default number of samples has
been set to 10, which should be more than enough to produce consistent
results.
The benchmark can be run with the npm script `benchmark:chrome` or
`benchmark:firefox`, e.g. `yarn benchmark:chrome`.
2020-01-21 17:02:45 +01:00
|
|
|
}
|
|
|
|
|
2020-01-13 16:07:32 +01:00
|
|
|
// Window management
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async openNewPage(url) {
|
2021-02-04 19:15:23 +01:00
|
|
|
const newHandle = await this.driver.switchTo().newWindow();
|
|
|
|
await this.driver.get(url);
|
|
|
|
return newHandle;
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async switchToWindow(handle) {
|
2021-02-04 19:15:23 +01:00
|
|
|
await this.driver.switchTo().window(handle);
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async getAllWindowHandles() {
|
2021-02-04 19:15:23 +01:00
|
|
|
return await this.driver.getAllWindowHandles();
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async waitUntilXWindowHandles(x, delayStep = 1000, timeout = 5000) {
|
2021-02-04 19:15:23 +01:00
|
|
|
let timeElapsed = 0;
|
|
|
|
let windowHandles = [];
|
2020-01-13 16:07:32 +01:00
|
|
|
while (timeElapsed <= timeout) {
|
2021-02-04 19:15:23 +01:00
|
|
|
windowHandles = await this.driver.getAllWindowHandles();
|
2020-01-13 16:07:32 +01:00
|
|
|
if (windowHandles.length === x) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return windowHandles;
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
await this.delay(delayStep);
|
|
|
|
timeElapsed += delayStep;
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
throw new Error('waitUntilXWindowHandles timed out polling window handles');
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async switchToWindowWithTitle(title, windowHandles) {
|
2020-08-15 13:58:11 +02:00
|
|
|
// eslint-disable-next-line no-param-reassign
|
2021-02-04 19:15:23 +01:00
|
|
|
windowHandles = windowHandles || (await this.driver.getAllWindowHandles());
|
2020-01-13 16:07:32 +01:00
|
|
|
|
|
|
|
for (const handle of windowHandles) {
|
2021-02-04 19:15:23 +01:00
|
|
|
await this.driver.switchTo().window(handle);
|
|
|
|
const handleTitle = await this.driver.getTitle();
|
2020-01-13 16:07:32 +01:00
|
|
|
if (handleTitle === title) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return handle;
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
throw new Error(`No window with title: ${title}`);
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Closes all windows except those in the given list of exceptions
|
2020-01-13 19:36:36 +01:00
|
|
|
* @param {Array<string>} exceptions - The list of window handle exceptions
|
|
|
|
* @param {Array} [windowHandles] - The full list of window handles
|
2020-01-13 16:07:32 +01:00
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
async closeAllWindowHandlesExcept(exceptions, windowHandles) {
|
2020-08-15 13:58:11 +02:00
|
|
|
// eslint-disable-next-line no-param-reassign
|
2021-02-04 19:15:23 +01:00
|
|
|
windowHandles = windowHandles || (await this.driver.getAllWindowHandles());
|
2020-01-13 16:07:32 +01:00
|
|
|
|
|
|
|
for (const handle of windowHandles) {
|
|
|
|
if (!exceptions.includes(handle)) {
|
2021-02-04 19:15:23 +01:00
|
|
|
await this.driver.switchTo().window(handle);
|
|
|
|
await this.delay(1000);
|
|
|
|
await this.driver.close();
|
|
|
|
await this.delay(1000);
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error handling
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async verboseReportOnFailure(title) {
|
2021-02-04 19:15:23 +01:00
|
|
|
const artifactDir = `./test-artifacts/${this.browser}/${title}`;
|
|
|
|
const filepathBase = `${artifactDir}/test-failure`;
|
|
|
|
await fs.mkdir(artifactDir, { recursive: true });
|
|
|
|
const screenshot = await this.driver.takeScreenshot();
|
2020-11-03 00:41:28 +01:00
|
|
|
await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, {
|
|
|
|
encoding: 'base64',
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
|
|
|
const htmlSource = await this.driver.getPageSource();
|
|
|
|
await fs.writeFile(`${filepathBase}-dom.html`, htmlSource);
|
2020-08-10 17:58:17 +02:00
|
|
|
const uiState = await this.driver.executeScript(
|
|
|
|
() => window.getCleanAppState && window.getCleanAppState(),
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-11-03 00:41:28 +01:00
|
|
|
await fs.writeFile(
|
|
|
|
`${filepathBase}-state.json`,
|
|
|
|
JSON.stringify(uiState, null, 2),
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
async checkBrowserForConsoleErrors() {
|
2021-02-04 19:15:23 +01:00
|
|
|
const ignoredLogTypes = ['WARNING'];
|
2020-01-13 16:07:32 +01:00
|
|
|
const ignoredErrorMessages = [
|
|
|
|
// Third-party Favicon 404s show up as errors
|
|
|
|
'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)',
|
2021-02-04 19:15:23 +01:00
|
|
|
];
|
|
|
|
const browserLogs = await this.driver.manage().logs().get('browser');
|
2020-11-03 00:41:28 +01:00
|
|
|
const errorEntries = browserLogs.filter(
|
|
|
|
(entry) => !ignoredLogTypes.includes(entry.level.toString()),
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
|
|
|
const errorObjects = errorEntries.map((entry) => entry.toJSON());
|
2020-11-03 00:41:28 +01:00
|
|
|
return errorObjects.filter(
|
|
|
|
(entry) =>
|
|
|
|
!ignoredErrorMessages.some((message) =>
|
|
|
|
entry.message.includes(message),
|
|
|
|
),
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-01-13 16:07:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
function collectMetrics() {
|
Add benchmark script (#7869)
The script `benchmark.js` will collect page load metrics from the
extension, and print them to a file or the console. A method for
collecting metrics was added to the web driver to help with this.
This script will calculate the min, max, average, and standard
deviation for four metrics: 'firstPaint', 'domContentLoaded', 'load',
and 'domInteractive'. The variation between samples is sometimes high,
with the results varying between samples if only 3 were taken. However,
all tests I've done locally with 5 samples have produced results within
one standard deviation of each other. The default number of samples has
been set to 10, which should be more than enough to produce consistent
results.
The benchmark can be run with the npm script `benchmark:chrome` or
`benchmark:firefox`, e.g. `yarn benchmark:chrome`.
2020-01-21 17:02:45 +01:00
|
|
|
const results = {
|
|
|
|
paint: {},
|
|
|
|
navigation: [],
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
Add benchmark script (#7869)
The script `benchmark.js` will collect page load metrics from the
extension, and print them to a file or the console. A method for
collecting metrics was added to the web driver to help with this.
This script will calculate the min, max, average, and standard
deviation for four metrics: 'firstPaint', 'domContentLoaded', 'load',
and 'domInteractive'. The variation between samples is sometimes high,
with the results varying between samples if only 3 were taken. However,
all tests I've done locally with 5 samples have produced results within
one standard deviation of each other. The default number of samples has
been set to 10, which should be more than enough to produce consistent
results.
The benchmark can be run with the npm script `benchmark:chrome` or
`benchmark:firefox`, e.g. `yarn benchmark:chrome`.
2020-01-21 17:02:45 +01:00
|
|
|
|
2020-04-15 19:23:27 +02:00
|
|
|
window.performance.getEntriesByType('paint').forEach((paintEntry) => {
|
2021-02-04 19:15:23 +01:00
|
|
|
results.paint[paintEntry.name] = paintEntry.startTime;
|
|
|
|
});
|
Add benchmark script (#7869)
The script `benchmark.js` will collect page load metrics from the
extension, and print them to a file or the console. A method for
collecting metrics was added to the web driver to help with this.
This script will calculate the min, max, average, and standard
deviation for four metrics: 'firstPaint', 'domContentLoaded', 'load',
and 'domInteractive'. The variation between samples is sometimes high,
with the results varying between samples if only 3 were taken. However,
all tests I've done locally with 5 samples have produced results within
one standard deviation of each other. The default number of samples has
been set to 10, which should be more than enough to produce consistent
results.
The benchmark can be run with the npm script `benchmark:chrome` or
`benchmark:firefox`, e.g. `yarn benchmark:chrome`.
2020-01-21 17:02:45 +01:00
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
window.performance
|
|
|
|
.getEntriesByType('navigation')
|
|
|
|
.forEach((navigationEntry) => {
|
|
|
|
results.navigation.push({
|
|
|
|
domContentLoaded: navigationEntry.domContentLoadedEventEnd,
|
|
|
|
load: navigationEntry.loadEventEnd,
|
|
|
|
domInteractive: navigationEntry.domInteractive,
|
|
|
|
redirectCount: navigationEntry.redirectCount,
|
|
|
|
type: navigationEntry.type,
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
|
|
|
});
|
Add benchmark script (#7869)
The script `benchmark.js` will collect page load metrics from the
extension, and print them to a file or the console. A method for
collecting metrics was added to the web driver to help with this.
This script will calculate the min, max, average, and standard
deviation for four metrics: 'firstPaint', 'domContentLoaded', 'load',
and 'domInteractive'. The variation between samples is sometimes high,
with the results varying between samples if only 3 were taken. However,
all tests I've done locally with 5 samples have produced results within
one standard deviation of each other. The default number of samples has
been set to 10, which should be more than enough to produce consistent
results.
The benchmark can be run with the npm script `benchmark:chrome` or
`benchmark:firefox`, e.g. `yarn benchmark:chrome`.
2020-01-21 17:02:45 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
return results;
|
Add benchmark script (#7869)
The script `benchmark.js` will collect page load metrics from the
extension, and print them to a file or the console. A method for
collecting metrics was added to the web driver to help with this.
This script will calculate the min, max, average, and standard
deviation for four metrics: 'firstPaint', 'domContentLoaded', 'load',
and 'domInteractive'. The variation between samples is sometimes high,
with the results varying between samples if only 3 were taken. However,
all tests I've done locally with 5 samples have produced results within
one standard deviation of each other. The default number of samples has
been set to 10, which should be more than enough to produce consistent
results.
The benchmark can be run with the npm script `benchmark:chrome` or
`benchmark:firefox`, e.g. `yarn benchmark:chrome`.
2020-01-21 17:02:45 +01:00
|
|
|
}
|
|
|
|
|
2020-01-20 18:03:07 +01:00
|
|
|
Driver.PAGES = {
|
|
|
|
HOME: 'home',
|
|
|
|
NOTIFICATION: 'notification',
|
|
|
|
POPUP: 'popup',
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2020-01-20 18:03:07 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
module.exports = Driver;
|