browsertime
Version:
Get performance metrics from your web page using Browsertime.
281 lines (264 loc) • 9.42 kB
JavaScript
import webdriver from 'selenium-webdriver';
import { getLogger } from '@sitespeed.io/log';
import { executeCommand } from './commandHelper.js';
import { parseSelector } from './selectorParser.js';
const log = getLogger('browsertime.command.wait');
const delay = ms => new Promise(res => setTimeout(res, ms));
/**
* Provides functionality to wait for different conditions in the browser.
*
* @class
* @hideconstructor
*/
export class Wait {
constructor(browser, pageCompleteCheck) {
/**
* @private
*/
this.browser = browser;
/**
* @private
*/
this.pageCompleteCheck = pageCompleteCheck;
}
/**
* Waits for an element with a specific ID to be located within a maximum time.
*
* @async
* @private
* @param {string} id - The ID of the element to wait for.
* @param {number} maxTime - Maximum time to wait in milliseconds.
* @returns {Promise<void>} A promise that resolves when the element is found or the time times out.
* @throws {Error} Throws an error if the element is not found within the specified time.
*/
async byId(id, maxTime) {
const driver = this.browser.getDriver();
const time = maxTime || 6000;
try {
await driver.wait(
webdriver.until.elementLocated(webdriver.By.id(id)),
time
);
} catch (error) {
log.error('Element by id %s was not located in %s ms', id, time);
log.verbose(error);
throw new Error(`Element by id ${id} was not located in ${time} ms`);
}
}
/**
* Waits for an element with a specific ID to be located and visible within a maximum time.
*
* @async
* @private
* @param {string} id - The ID of the element to wait for.
* @param {number} maxTime - Maximum time to wait in milliseconds.
* @returns {Promise<void>} A promise that resolves when the element is found or the time times out.
* @throws {Error} Throws an error if the element is not found within the specified time.
*/
async byIdAndVisible(id, maxTime = 6000) {
const driver = this.browser.getDriver();
await this.byId(id, maxTime);
try {
await driver.wait(
webdriver.until.elementIsVisible(
driver.findElement(webdriver.By.id(id))
),
maxTime
);
} catch (error) {
log.error('Element by id %s was not visible in %s ms', id, maxTime);
log.verbose(error);
throw new Error(`Element by id ${id} was not located in ${maxTime} ms`);
}
}
/**
* Waits for an element located by XPath to appear within a maximum time.
*
* @async
* @private
* @param {string} xpath - The XPath of the element to wait for.
* @param {number} maxTime - Maximum time to wait in milliseconds.
* @returns {Promise<void>} A promise that resolves when the element is found or the time times out.
* @throws {Error} Throws an error if the element is not found within the specified time.
*/
async byXpath(xpath, maxTime) {
const driver = this.browser.getDriver();
const time = maxTime || 6000;
try {
await driver.wait(
webdriver.until.elementLocated(webdriver.By.xpath(xpath)),
time
);
} catch (error) {
log.error('Element by xpath %s was not located in %s ms', xpath, time);
log.verbose(error);
throw new Error(
`Element by xpath ${xpath} was not located in ${time} ms`
);
}
}
/**
* Waits for an element located by XPath to appear and visible within a maximum time.
*
* @async
* @private
* @param {string} xpath - The XPath of the element to wait for.
* @param {number} maxTime - Maximum time to wait in milliseconds.
* @returns {Promise<void>} A promise that resolves when the element is found or the time times out.
* @throws {Error} Throws an error if the element is not found within the specified time.
*/
async byXpathAndVisible(xpath, maxTime = 6000) {
const driver = this.browser.getDriver();
await this.byXpath(xpath, maxTime);
try {
await driver.wait(
webdriver.until.elementIsVisible(
driver.findElement(webdriver.By.xpath(xpath))
),
maxTime
);
} catch (error) {
log.error('Element by xpath %s was not visible in %s ms', xpath, maxTime);
log.verbose(error);
throw new Error(
`Element by xpath ${xpath} was not located in ${maxTime} ms`
);
}
}
/**
* Waits for an element located by a CSS selector to appear within a maximum time.
*
* @async
* @private
* @param {string} selector - The CSS selector of the element to wait for.
* @param {number} maxTime - Maximum time to wait in milliseconds.
* @returns {Promise<void>} A promise that resolves when the element is found or the time times out.
* @throws {Error} Throws an error if the element is not found within the specified time.
*/
async bySelector(selector, maxTime) {
const driver = this.browser.getDriver();
const time = maxTime || 6000;
try {
await driver.wait(
webdriver.until.elementLocated(webdriver.By.css(selector)),
time
);
} catch (error) {
log.error(
'Element by selector %s was not located in %s ms',
selector,
time
);
log.verbose(error);
throw new Error(
`Element by selector ${selector} was not located in ${time} ms`
);
}
}
/**
* Waits for an element located by a CSS selector to be visible within a maximum time.
*
* @async
* @private
* @param {string} selector - The CSS selector of the element to wait for.
* @param {number} maxTime - Maximum time to wait in milliseconds.
* @returns {Promise<void>} A promise that resolves when the element is found or the time times out.
* @throws {Error} Throws an error if the element is not found within the specified time.
*/
async bySelectorAndVisible(selector, maxTime = 6000) {
const driver = this.browser.getDriver();
try {
await driver.wait(
webdriver.until.elementIsVisible(
driver.findElement(webdriver.By.css(selector))
),
maxTime
);
} catch (error) {
log.error(
'Element by selector %s was not visible in %s ms',
selector,
maxTime
);
log.verbose(error);
throw new Error(
`Element by selector ${selector} was not visible in ${maxTime} ms`
);
}
}
/**
* Waits for a specified amount of time.
*
* @async
* @example async commands.wait.byTime(1000);
* @param {number} ms - The time in milliseconds to wait.
* @returns {Promise<void>} A promise that resolves when the specified time has elapsed.
*/
async byTime(ms) {
return delay(ms);
}
/**
* Waits for the page to finish loading.
* @async
* @example async commands.wait.byPageToComplete();
* @returns {Promise<void>} A promise that resolves when the page complete check has finished.
*/
async byPageToComplete() {
return this.browser.extraWait(this.pageCompleteCheck);
}
/**
* Waits for an element using a unified selector string to appear within a maximum time.
* Supports CSS selectors (default), and prefix-based strategies:
* 'id:myId', 'xpath://div', 'name:field', 'class:loaded'.
*
* @async
* @param {string} selector - The selector string. CSS by default, or use a prefix.
* @param {Object} [options] - Options for waiting.
* @param {number} [options.timeout=6000] - Maximum time to wait in milliseconds.
* @param {boolean} [options.visible=false] - If true, waits for the element to be visible, not just present.
* @returns {Promise<void>} A promise that resolves when the element is found.
* @throws {Error} Throws an error if the element is not found within the timeout.
*/
async run(selector, options = {}) {
const timeout = options.timeout ?? 6000;
const visible = options.visible ?? false;
const { locator, description } = parseSelector(selector);
return executeCommand(
log,
'Element %s was not located in ' + timeout + ' ms',
description,
async () => {
const driver = this.browser.getDriver();
await driver.wait(webdriver.until.elementLocated(locator), timeout);
if (visible) {
const element = await driver.findElement(locator);
await driver.wait(webdriver.until.elementIsVisible(element), timeout);
}
},
this.browser
);
}
/**
* Waits for a JavaScript condition to return a truthy value within a maximum time.
*
* @async
* @param {string} jsExpression - The JavaScript expression to evaluate.
* @param {number} maxTime - Maximum time to wait in milliseconds.
* @returns {Promise<void>} A promise that resolves when the condition becomes truthy or the time times out.
* @throws {Error} Throws an error if the condition is not met within the specified time.
*/
async byCondition(jsExpression, maxTime) {
const driver = this.browser.getDriver();
const time = maxTime || 6000;
try {
const script = `return ${jsExpression}`;
await driver.wait(() => {
return driver.executeScript(script);
}, time);
} catch (error) {
log.error('Condition was not met', jsExpression, time);
log.verbose(error);
throw new Error(`Condition ${jsExpression} was not met in ${time} ms`);
}
}
}