taiko
Version:
Taiko is a Node.js library for automating Chromium based browsers
1,266 lines (1,199 loc) • 102 kB
JavaScript
const { doActionAwaitingNavigation } = require("./doActionAwaitingNavigation");
const {
wait,
isString,
isRegex,
isStrictObject,
isFunction,
waitUntil,
descEvent,
isSelector,
isElement,
isObject,
} = require("./helper");
const inputHandler = require("./handlers/inputHandler");
const domHandler = require("./handlers/domHandler");
const networkHandler = require("./handlers/networkHandler");
const fetchHandler = require("./handlers/fetchHandler");
const pageHandler = require("./handlers/pageHandler");
const targetHandler = require("./handlers/targetHandler");
const runtimeHandler = require("./handlers/runtimeHandler");
const browserHandler = require("./handlers/browserHandler");
const emulationHandler = require("./handlers/emulationHandler");
const { description } = require("./actions/pageActionChecks");
const {
match,
$$xpath,
findElements,
findFirstElement,
} = require("./elementSearch");
const { RelativeSearchElement } = require("./proximityElementSearch");
const { scrollToElement } = require("./actions/scrollTo");
const {
setConfig,
getConfig,
defaultConfig,
setNavigationOptions,
} = require("./config");
const fs = require("fs-extra");
const path = require("node:path");
const childProcess = require("node:child_process");
const crypto = require("node:crypto");
const { eventHandler, eventRegexMap } = require("./eventBus");
const { highlightElement } = require("./elements/elementHelper");
const { launchBrowser } = require("./browser/launcher");
const {
connect_to_cri,
closeConnection,
cleanUpListenersOnClient,
validate,
getClient,
} = require("./connection");
const { getPlugins, registerHooks } = require("./plugins");
let eventHandlerProxy;
module.exports.emitter = descEvent;
/**
* Launches a browser with a tab. The browser will be closed when the parent node.js process is closed.<br>
* Note : `openBrowser` launches the browser in headless mode by default, but when `openBrowser` is called from {@link repl} it launches the browser in headful mode.
* @example
* await openBrowser({headless: false})
* @example
* await openBrowser()
* @example
* await openBrowser({args:['--window-size=1440,900']})
* @example
* await openBrowser({args: [
* '--disable-gpu',
* '--disable-dev-shm-usage',
* '--disable-setuid-sandbox',
* '--no-first-run',
* '--no-sandbox',
* '--no-zygote']}) # These are recommended args that has to be passed when running in docker
*
* @param {Object} [options={headless:true}] eg. {headless: true|false, args:['--window-size=1440,900']}
* @param {boolean} [options.headless=true] - Option to open browser in headless/headful mode.
* @param {Array<string>} [options.args=[]] - [Chromium browser launch options](https://peter.sh/experiments/chromium-command-line-switches/).
* @param {string} [options.host='127.0.0.1'] - Remote host to connect to.
* @param {string} [options.target] - Determines which target the client should interact.(https://github.com/cyrus-and/chrome-remote-interface#cdpoptions-callback)
* @param {number} [options.port=0] - Remote debugging port, if not given connects to any open port.
* @param {number} [options.useHostName=false] - If the browser should be called using the hostname itself or with IP address
* @param {number} [options.secure=false] - HTTPS/WSS frontend. Defaults to false.
* @param {boolean} [options.ignoreCertificateErrors=true] - Option to ignore certificate errors.
* @param {boolean} [options.observe=false] - Option to run each command after a delay. Useful to observe what is happening in the browser.
* @param {number} [options.observeTime=3000] - Option to modify delay time for observe mode. Accepts value in milliseconds.
* @param {boolean} [options.dumpio=false] - Option to dump IO from browser.
*
* @returns {Promise<void>}
*/
module.exports.openBrowser = async (
options = {
headless: true,
},
) => {
if (!isStrictObject(options)) {
throw new TypeError(
"Invalid option parameter. Refer https://docs.taiko.dev/api/openBrowser for the correct format.",
);
}
defaultConfig.alterPath = options.alterPath;
if ((options.host && options.port) || options.target) {
defaultConfig.host = options.host;
defaultConfig.port = options.port;
defaultConfig.useHostName = options.useHostName;
defaultConfig.secure = options.secure;
defaultConfig.browserDebugUrl = options.target;
defaultConfig.connectedToRemoteBrowser = true;
} else {
const { currentHost, currentPort, browserDebugUrl } =
await launchBrowser(options);
defaultConfig.host = currentHost;
defaultConfig.port = currentPort;
defaultConfig.browserDebugUrl = browserDebugUrl;
defaultConfig.connectedToRemoteBrowser = false;
}
await connect_to_cri();
const description = defaultConfig.device
? `Browser opened with viewport ${defaultConfig.device}`
: "Browser opened";
descEvent.emit("success", description);
if (process.env.TAIKO_EMULATE_NETWORK) {
await module.exports.emulateNetwork(process.env.TAIKO_EMULATE_NETWORK);
}
};
/**
* Closes the browser and along with all of its tabs.
*
* @example
* await closeBrowser()
*
* @returns {Promise<void>}
*/
module.exports.closeBrowser = async () => {
try {
validate();
} catch (error) {
console.warn(`WARNING: ${error.message}`);
return;
}
await waitFor(50); // wait for 50ms to ensure all events are flushed
await _closeBrowser();
targetHandler.clearRegister();
descEvent.emit("success", "Browser closed");
};
const _closeBrowser = async () => {
fetchHandler.resetInterceptors();
await closeConnection(promisesToBeResolvedBeforeCloseBrowser);
};
/**
* Gives CRI client object (a wrapper around Chrome DevTools Protocol).
* Refer https://github.com/cyrus-and/chrome-remote-interface
* Please refer [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) for
* the complete API.
*
* @returns {Object}
*/
module.exports.client = () => getEventProxy(getClient());
function getEventProxy(target) {
if (!target) {
return target;
}
const unsupportedClientMethods = [
"removeListener",
"emit",
"removeAllListeners",
"setMaxListeners",
"off",
];
const handler = {
get: (target, name) => {
if (unsupportedClientMethods.includes(name)) {
throw new Error(`Unsupported action ${name} on client`);
}
return target[name];
},
};
return new Proxy(target, handler);
}
/**
* Allows switching between tabs and windows using URL or page title or Window name.
*
* @example
* # Switch using URL
* await switchTo(/taiko.dev/)
* @example
* # Switch using Title
* await switchTo(/Taiko/)
* @example
* # Switch using Regex URL
* await switchTo(/http(s?):\/\/(www?).google.(com|co.in|co.uk)/)
* @example
* # Switch using wild cards in the Regex
* await switchTo(/Go*gle/)
* @example
* # Switch to a window identifier
* await openBrowser();
* await goto('google.com');
* openIncognitoWindow({ name: "newyorktimes"});
* switchTo(/google.com/);
* switchTo({ name: "newyorktimes"});
* @example
* openTab('https://taiko.dev', {name: 'taiko'})
* openIncognitoWindow({ name: "newyorktimes"});
* switchTo({name: 'taiko'});
* @param {string} arg - Regex (Regular expression) the tab's title/URL or `Object` with
* window name for example `{ name: "windowname"}`
*
* @returns {Promise<void>}
*/
module.exports.switchTo = async (arg) => {
validate();
let targetId;
let message;
if (isObject(arg) && !isRegex(arg) && !isString(arg)) {
targetId = targetHandler.register(arg.name);
if (!targetId) {
throw new Error(
`Could not find window/tab with name ${arg.name} to switch.`,
);
}
message = `Switched to window/tab matching ${arg.name}`;
} else {
if (!isString(arg) && !isRegex(arg)) {
throw new TypeError(
`The "targetUrl" argument must be of type string, regex or identifier. Received type ${typeof arg}`,
);
}
if (isString(arg) && arg.trim() === "") {
throw new Error(
"Cannot switch to tab or window as the targetUrl is empty. Please use a valid string, regex or identifier",
);
}
const targetPattern = isRegex(arg) ? new RegExp(arg) : arg;
const targets = await targetHandler.getCriTargets(targetPattern);
if (targets.matching.length === 0) {
throw new Error(`No tab(s) matching ${targetPattern} found`);
}
targetId = targets.matching[0].targetId;
message = `Switched to tab matching ${targetPattern}`;
}
await targetHandler.switchBrowserContext(targetId);
await connect_to_cri(targetId);
descEvent.emit("success", message);
};
/**
* Add interceptor for the network call. Helps in overriding request or to mock response of a network call.
*
* @example
* # Case 1: Block a specific URL
* await intercept(url)
* @example
* # Case 2: Mock a response
* await intercept(url, {mockObject})
* @example
* # Case 3: Override request
* await intercept(url, (request) => {request.continue({overrideObject})})
* @example
* # Case 4: Redirect always
* await intercept(url, redirectUrl)
* @example
* # Case 5: Mock response based on a request
* await intercept(url, (request) => { request.respond({mockResponseObject}) })
* @example
* # Case 6: Block URL twice
* await intercept(url, undefined, 2)
* @example
* # Case 7: Mock the response only 3 times
* await intercept(url, {mockObject}, 3)
*
* @param {string} requestUrl request URL to intercept
* @param {function|Object} option action to be done after interception. For more examples refer to https://github.com/getgauge/taiko/issues/98#issuecomment-42024186
* @param {number} count number of times the request has to be intercepted . Optional parameter
*
* @returns {Promise<void>}
*/
module.exports.intercept = async (requestUrl, option, count) => {
await fetchHandler.addInterceptor({
requestUrl: requestUrl,
action: option,
count,
});
descEvent.emit("success", `Interceptor added for ${requestUrl}`);
};
/**
* Activates emulation of network conditions.
*
* @example
* # Emulate offline conditions
* await emulateNetwork("Offline")
* @example
* # Emulate slow network conditions
* await emulateNetwork("Good2G")
* @example
* # Emulate precise network conditions
* await emulateNetwork({ offline: false, downloadThroughput: 6400, uploadThroughput: 2560, latency: 500 })
* @example
* # Emulate precise network conditions with any subset of these properties, with default fallbacks of `offline` as true and all numbers as 0
* await emulateNetwork({ downloadThroughput: 6400, uploadThroughput: 2560, latency: 500 })
*
* @param {(string|object)} networkType - 'GPRS','Regular2G','Good2G','Good3G','Regular3G','Regular4G','DSL','WiFi','Offline', {offline: boolean, downloadThroughput: number, uploadThroughput: number, latency: number}
*
* @returns {Promise<void>}
*/
module.exports.emulateNetwork = async (networkType) => {
validate();
await networkHandler.setNetworkEmulation(networkType);
descEvent.emit(
"success",
`Set network emulation with values ${JSON.stringify(networkType)}`,
);
};
/**
* Overrides the values of device screen dimensions according to a predefined list of devices. To provide custom device dimensions, use setViewPort API.
*
* @example
* await emulateDevice('iPhone 6')
*
* @param {string} deviceModel - See [device model](https://docs.taiko.dev/devices) for a list of all device models.
*
* @returns {Promise<void>}
*/
module.exports.emulateDevice = emulateDevice;
async function emulateDevice(deviceModel) {
validate();
await emulationHandler.emulateDevice(deviceModel);
descEvent.emit("success", `Device emulation set to ${deviceModel}`);
}
/**
* Overrides the values of device screen dimensions
*
* @example
* await setViewPort({width:600, height:800})
*
* @param {Object} options - See [chrome devtools setDeviceMetricsOverride](https://chromedevtools.github.io/devtools-protocol/tot/Emulation#method-setDeviceMetricsOverride) for a list of options
*
* @returns {Promise<void>}
*/
module.exports.setViewPort = async (options) => {
validate();
await emulationHandler.setViewport(options);
descEvent.emit(
"success",
`ViewPort is set to width ${options.width} and height ${options.height}`,
);
};
/**
* Changes the timezone of the page. See [`metaZones.txt`](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
* for a list of supported timezone IDs.
* @example
* await emulateTimezone('America/Jamaica')
*/
module.exports.emulateTimezone = async (timezoneId) => {
await emulationHandler.setTimeZone(timezoneId);
descEvent.emit("success", `Timezone set to ${timezoneId}`);
};
/**
* Launches a new tab. If url is provided, the new tab is opened with the url loaded.
* @example
* await openTab('https://taiko.dev')
* @example
* await openTab() # opens a blank tab.
* @example
* await openTab('https://taiko.dev', {name: 'taiko'}) # Tab with identifier
* @param {string} [targetUrl=undefined] - Url of page to open in newly created tab.
* @param {Object} options
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the reload. Default navigation timeout is 5000 milliseconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {string} [options.name] - Tab identifier
* @param {number} [options.navigationTimeout=5000] - Navigation timeout value in milliseconds for navigation after click. Accepts value in milliseconds.
* @param {number} [options.waitForStart=100] - time to wait to check for occurrence of page load events. Accepts value in milliseconds.
* @param {string[]} [options.waitForEvents = []] - Page load events to implicitly wait for. Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
*
* @returns {Promise<void>}
*/
module.exports.openTab = async (targetUrl, options = {}) => {
validate();
const _options = setNavigationOptions(options);
if (isObject(targetUrl) && targetUrl.name) {
_options.name = targetUrl.name;
}
let finalTargetUrl = isString(targetUrl) ? targetUrl : "about:blank";
if (
isString(finalTargetUrl) &&
finalTargetUrl !== "about:blank" &&
!/:\/\//i.test(finalTargetUrl)
) {
finalTargetUrl = `http://${finalTargetUrl}`;
}
if (targetHandler.register(_options.name)) {
throw new Error(
`There is a window or tab already registered with the name '${_options.name}' please use another name.`,
);
}
const createNewTarget = async () => {
await cleanUpListenersOnClient();
const target = await targetHandler.createTarget(finalTargetUrl);
await connect_to_cri(target);
targetHandler.register(_options.name, target);
};
await doActionAwaitingNavigation(_options, createNewTarget);
descEvent.emit("success", `Opened tab with URL ${finalTargetUrl}`);
};
/**
* Opens the specified URL in the browser's window. Adds `http` protocol to the URL if not present.
* @example
* await openIncognitoWindow('https://google.com', { name: 'windowName' }) - Open a incognito window
* @param {string} url - URL to navigate page to.
* @param {Object} options
* @param {string} [options.name] - Window name (required).
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the goto. Default navigationTimeout is 30 seconds to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
* @param {number} [options.navigationTimeout=30000] - Navigation timeout value in milliseconds for navigation after click.
* @param {Object} options.headers - Map with extra HTTP headers.
* @param {number} [options.waitForStart = 100] - time to wait for navigation to start. Accepts value in milliseconds.
*
* @returns {Promise<void>}
*/
module.exports.openIncognitoWindow = async (url, options = {}) => {
validate();
let _url = url;
let _options = options;
if (typeof url === "object") {
_options = url;
_url = "about:blank";
}
_options = setNavigationOptions(_options);
_options.incognito = true;
if (!_options.name) {
throw new TypeError("Window name needs to be provided");
}
if (targetHandler.register(_options.name)) {
throw new Error(
`There is a already a window/tab with name ${_options.name}. Please use another name`,
);
}
if (
_url !== "about:blank" &&
!/^https?:\/\//i.test(_url) &&
!/^file/i.test(_url)
) {
_url = `http://${_url}`;
}
const targetId = await targetHandler.createBrowserContext(_url, _options);
await cleanUpListenersOnClient();
await connect_to_cri(targetId);
targetHandler.register(_options.name, targetId);
if (_url !== "about:blank") {
await doActionAwaitingNavigation(_options, async () => {
await pageHandler.handleNavigation(_url);
});
}
descEvent.emit(
"success",
`Incognito window opened with name ${_options.name}`,
);
};
/**
* Closes the specified browser window.
* @example
* await closeIncognitoWindow('windowName') - Close incognito window
* @param {string} windowName - incognito window name
*/
module.exports.closeIncognitoWindow = async (arg) => {
if (typeof arg !== "string") {
throw new TypeError("Window name needs to be provided");
}
if (!targetHandler.register(arg)) {
console.warn(`Could not find Window with name ${arg} to close.`);
return;
}
const hasClosedActiveBrowserContext = await targetHandler.closeBrowserContext(
targetHandler.register(arg),
);
targetHandler.unregister(arg);
if (hasClosedActiveBrowserContext) {
const promiseReconnect = new Promise((resolve) => {
eventHandler.once("reconnected", resolve);
});
await promiseReconnect;
}
descEvent.emit("success", `Window with name ${arg} closed`);
};
/**
* Closes the given tab with given URL or closes current tab.
*
* @example
* # Closes the current tab.
* await closeTab()
* @example
* # Closes all the tabs with Title 'Open Source Test Automation Framework | Gauge'.
* await closeTab('Open Source Test Automation Framework | Gauge')
* @example
* # Closes all the tabs with URL 'https://gauge.org'.
* await closeTab('https://gauge.org')
* @example
* # Closes all the tabs with Regex Title 'Go*gle'
* await closeTab(/Go*gle/)
* @example
* # Closes all the tabs with Regex URL '/http(s?):\/\/(www?).google.(com|co.in|co.uk)/'
* await closeTab(/http(s?):\/\/(www?).google.(com|co.in|co.uk)/)
*
* @param {string} [targetUrl=undefined] - URL/Page title of the tab to close.
*
* @returns {Promise<void>}
*/
module.exports.closeTab = async (identifier) => {
const { matching, others } = await targetHandler.getCriTargets(identifier);
if (!others.length) {
await _closeBrowser();
descEvent.emit("success", "Closing last target and browser.");
return;
}
if (!matching.length) {
throw new Error(`No tab(s) matching ${identifier} found`);
}
const activeTab = { url: await currentURL(), title: await title() };
let closedTabUrl;
for (const target of matching) {
closedTabUrl = target.url;
await targetHandler.closeTarget(target.targetId);
}
if (
!identifier ||
targetHandler.isMatchingUrl(activeTab, identifier) ||
targetHandler.isMatchingRegex(activeTab, identifier) ||
targetHandler.isMatchingTarget(activeTab, identifier)
) {
await cleanUpListenersOnClient();
await connect_to_cri(others[0].targetId);
}
const message = identifier
? `Closed tab(s) matching ${identifier.name ? identifier.name : identifier}`
: `Closed current tab matching ${closedTabUrl}`;
if (identifier) {
targetHandler.unregister(identifier.name);
}
descEvent.emit("success", message);
};
/**
* Override specific permissions to the given origin
*
* @example
* await overridePermissions('http://maps.google.com',['geolocation']);
*
* @param {string} origin - url origin to override permissions
* @param {Array<string>} permissions - See [chrome devtools permission types](https://chromedevtools.github.io/devtools-protocol/tot/Browser/#type-PermissionType) for a list of permission types.
*
* @returns {Promise<void>}
*/
module.exports.overridePermissions = async (origin, permissions) => {
validate();
await browserHandler.overridePermissions(origin, permissions);
descEvent.emit("success", `Override permissions with ${permissions}`);
};
/**
* Clears all permission overrides for all origins.
*
* @example
* await clearPermissionOverrides()
*
* @returns {Promise<void>}
*/
module.exports.clearPermissionOverrides = async () => {
validate();
await browserHandler.clearPermissionOverrides();
descEvent.emit("success", "Cleared permission overrides");
};
/**
* Sets a cookie with the given cookie data. It may overwrite equivalent cookie if it already exists.
*
* @example
* await setCookie("CSRFToken","csrfToken", {url: "http://the-internet.herokuapp.com"})
* @example
* await setCookie("CSRFToken","csrfToken", {domain: "herokuapp.com"})
*
* @param {string} name - Cookie name.
* @param {string} value - Cookie value.
* @param {Object} options
* @param {string} [options.url=undefined] - sets cookie with the URL.
* @param {string} [options.domain=undefined] - sets cookie with the exact domain.
* @param {string} [options.path=undefined] - sets cookie with the exact path.
* @param {boolean} [options.secure=undefined] - True if cookie to be set is secure.
* @param {boolean} [options.httpOnly=undefined] - True if cookie to be set is http-only.
* @param {string} [options.sameSite=undefined] - Represents the cookie's 'SameSite' status: Refer https://tools.ietf.org/html/draft-west-first-party-cookies.
* @param {number} [options.expires=undefined] - UTC time in seconds, counted from January 1, 1970. eg: 2019-02-16T16:55:45.529Z
*
* @returns {Promise<void>}
*/
module.exports.setCookie = async (name, value, options = {}) => {
validate();
if (options.url === undefined && options.domain === undefined) {
throw new Error(
"At least URL or domain needs to be specified for setting cookies",
);
}
options.name = name;
options.value = value;
const res = await networkHandler.setCookie(options);
if (!res.success) {
throw new Error(`Unable to set ${name} cookie`);
}
descEvent.emit("success", `${name} cookie set successfully`);
};
/**
* Deletes browser cookies with matching name and URL or domain/path pair. If cookie name is not given or empty, all browser cookies are deleted.
*
* @example
* await deleteCookies() # clears all browser cookies
* @example
* await deleteCookies("CSRFToken", {url: "http://the-internet.herokuapp.com"})
* @example
* await deleteCookies("CSRFToken", {domain: "herokuapp.com"})
*
* @param {string} [cookieName=undefined] - Cookie name.
* @param {Object} options
* @param {string} [options.url=undefined] - deletes all the cookies with the given name where domain and path match provided URL. eg: https://google.com
* @param {string} [options.domain=undefined] - deletes only cookies with the exact domain. eg: google.com
* @param {string} [options.path=undefined] - deletes only cookies with the exact path. eg: Google/Chrome/Default/Cookies/..
*
* @returns {Promise<void>}
*/
module.exports.deleteCookies = async (cookieName, options = {}) => {
validate();
if (!cookieName || !cookieName.trim()) {
await networkHandler.clearBrowserCookies();
descEvent.emit("success", "Browser cookies deleted successfully");
} else {
if (options.url === undefined && options.domain === undefined) {
throw new Error(
"At least URL or domain needs to be specified for deleting cookies",
);
}
options.name = cookieName;
await networkHandler.deleteCookies(options);
descEvent.emit("success", `"${cookieName}" cookie deleted successfully`);
}
};
/**
* Resize the browser window
*
* @example
* await resizeWindow({width:600, height:800})
*
* @returns {Promise<void>}
*/
module.exports.resizeWindow = async (options = {}) => {
validate();
if (options.height === undefined || options.width === undefined) {
throw new Error("Please specify the window height and width");
}
const [{ targetId }] = await targetHandler.getFirstAvailablePageTarget();
await browserHandler.setWindowBounds(targetId, options.height, options.width);
descEvent.emit(
"success",
`Window resized to height ${options.height} and width ${options.width}`,
);
};
/**
* Get browser cookies
*
* @example
* await getCookies()
* @example
* await getCookies({urls:['https://the-internet.herokuapp.com']})
*
* @param {Object} options
* @param {Array} [options.urls=undefined] - The list of URLs for which applicable cookies will be fetched
*
* @returns {Promise<Object[]>} - Array of cookie objects
*/
module.exports.getCookies = async (options = {}) => {
validate();
return (await networkHandler.getCookies(options)).cookies;
};
/**
* Overrides the Geolocation Position
*
* @example
* await setLocation({ latitude: 27.1752868, longitude: 78.040009, accuracy:20 })
*
* @param {Object} options Latitude, longitude and accuracy to set the location.
* @param {number} options.latitude - Mock latitude
* @param {number} options.longitude - Mock longitude
* @param {number} options.accuracy - Mock accuracy
*
* @returns {Promise<void>}
*/
module.exports.setLocation = async (options) => {
validate();
await emulationHandler.setLocation(options);
descEvent.emit("success", "Geolocation set");
};
/**
* Opens the specified URL in the browser's tab. Adds `http` protocol to the URL if not present.
* @example
* await goto('https://google.com')
* @example
* await goto('google.com')
* @example
* await goto('example.com',{ navigationTimeout:10000, headers:{'Authorization':'Basic cG9zdG1hbjpwYXNzd29y2A=='}})
* @example
* const response = await goto('gauge.org'); if(response.status.code === 200) {console.log("Success!!")}
* response: {
* redirectedResponse: [
* {
* url: 'http://gauge.org/',
* status: { code: 307, text: 'Internal Redirect' }
* }
* ],
* url: 'https://gauge.org/',
* status: { code: 200, text: '' }
* }
*
* @param {string} url - URL to navigate page to.
* @param {Object} options
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the goto. Default navigationTimeout is 30 seconds to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
* @param {number} [options.navigationTimeout=30000] - Navigation timeout value in milliseconds for navigation after click.
* @param {Object} options.headers - Map with extra HTTP headers.
* @param {number} [options.waitForStart = 100] - time to wait for navigation to start. Accepts value in milliseconds.
*
* @returns {Promise<Object>} response
*/
module.exports.goto = async (
url,
options = { navigationTimeout: defaultConfig.navigationTimeout },
) => {
validate();
const _url = /^about:|:\/\//i.test(url) ? url : `http://${url}`;
if (options.headers) {
await fetchHandler.setHTTPHeaders(options.headers, _url);
}
let response;
await doActionAwaitingNavigation(setNavigationOptions(options), async () => {
response = await pageHandler.handleNavigation(_url);
});
descEvent.emit("success", `Navigated to URL ${_url}`);
return response;
};
/**
* Reloads the page.
* @example
* await reload('https://google.com')
* @example
* await reload('https://google.com', { navigationTimeout: 10000 })
*
* @param {string} url - DEPRECATED URL to reload
* @param {Object} options
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the reload. Default navigation timeout is 30 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
* @param {number} [options.navigationTimeout=30000] - Navigation timeout value in milliseconds for navigation after click.
* @param {number} [options.waitForStart = 100] - time to wait for navigation to start. Accepts value in milliseconds.
* @param {boolean} [options.ignoreCache = false] - Ignore Cache on reload - Default to false
*
* @returns {Promise<void>}
*/
module.exports.reload = async (
url,
options = { navigationTimeout: defaultConfig.navigationTimeout },
) => {
let _options = options;
if (isString(url)) {
console.warn("DEPRECATION WARNING: url is deprecated on reload");
}
if (typeof url === "object") {
_options = Object.assign(url, options);
}
validate();
_options = setNavigationOptions(_options);
await doActionAwaitingNavigation(_options, async () => {
const value = _options.ignoreCache || false;
await pageHandler.reload(value);
});
const windowLocation = (
await runtimeHandler.runtimeEvaluate("window.location.toString()")
).result.value;
descEvent.emit("success", `${windowLocation}reloaded`);
};
/**
* Mimics browser back button click functionality.
* @example
* await goBack()
*
* @param {Object} options
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the goBack. Default navigation timeout is 30 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
* @param {number} [options.navigationTimeout=30000] - Navigation timeout value in milliseconds for navigation after click.
* @param {number} [options.waitForStart = 100] - time to wait for navigation to start. Accepts value in milliseconds.
*
* @returns {Promise<void>}
*/
module.exports.goBack = async (
options = { navigationTimeout: defaultConfig.navigationTimeout },
) => {
validate();
await _go(-1, options);
descEvent.emit("success", "Performed clicking on browser back button");
};
/**
* Mimics browser forward button click functionality.
* @example
* await goForward()
*
* @param {Object} options
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the goForward. Default navigation timeout is 30 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
* @param {number} [options.navigationTimeout=30000] - Navigation timeout value in milliseconds for navigation after click.
* @param {number} [options.waitForStart = 100] - time to wait for navigation to start. Accepts value in milliseconds.
*
* @returns {Promise<void>}
*/
module.exports.goForward = async (
options = { navigationTimeout: defaultConfig.navigationTimeout },
) => {
validate();
await _go(+1, options);
descEvent.emit("success", "Performed clicking on browser forward button");
};
const _go = async (delta, options) => {
const history = await pageHandler.getNavigationHistory();
const entry = history.entries[history.currentIndex + delta];
if (!entry) {
return null;
}
if (
entry.url === "about:blank" &&
!Object.prototype.hasOwnProperty.call(options, "waitForNavigation")
) {
options.waitForNavigation = false;
}
await doActionAwaitingNavigation(setNavigationOptions(options), async () => {
await pageHandler.navigateToHistoryEntry(entry.id);
});
};
/**
* Returns window's current URL.
* @example
* await openBrowser();
* @example
* await goto("www.google.com");
* @example
* await currentURL(); # returns "https://www.google.com/?gws_rd=ssl"
*
* @returns {Promise<string>} - The URL of the current window.
*/
const currentURL = async () => {
validate();
const locationObj = await runtimeHandler.runtimeEvaluate(
"window.location.toString()",
);
return locationObj.result.value;
};
module.exports.currentURL = currentURL;
/**
* Returns page's title.
* @example
* await openBrowser();
* @example
* await goto("www.google.com");
* @example
* await title(); # returns "Google"
*
* @returns {Promise<string>} - The title of the current page.
*/
const title = async () => {
validate();
const result = await runtimeHandler.runtimeEvaluate("document.title");
return result.result.value;
};
module.exports.title = title;
/**
* Fetches an element with the given selector, scrolls it into view if needed, and then clicks in the center of the element. If there's no element matching selector, the method throws an error.
* @example
* await click('Get Started')
* @example
* await click(link('Get Started'))
* @example
* await click({x : 170, y : 567})
* @example
* await click('Get Started', { navigationTimeout: 60000, force: true })
* @example
* await click('Get Started', { navigationTimeout: 60000 }, below('text'))
* @example
* await click('Get Started', { navigationTimeout: 60000, position: 'right' }, below('text'))
* @param {selector|string|Object} selector - A selector to search for element to click / coordinates of the elemets to click on. If there are multiple elements satisfying the selector, the first will be clicked.
* @param {Object} options
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the click. Default navigation timeout is 30 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {number} [options.navigationTimeout=30000] - Navigation timeout value in milliseconds for navigation after click.
* @param {string} [options.button='left'] - `left`, `right`, or `middle`.
* @param {number} [options.clickCount=1] - Number of times to click on the element.
* @param {number} [options.elementsToMatch=10] - Number of elements to loop through to match the element with given selector.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
* @param {number} [options.waitForStart=100] - time to wait for navigation to start. Accepts time in milliseconds.
* @param {boolean} [options.force=false] - Set to true to perform action on hidden/disabled elements.
* @param {string} [options.position='right'] - Available positions right, left, topRight, topLeft, bottomRight, bottomLeft
* @param {relativeSelector[]} args - Proximity selectors
*
* @returns {Promise<void>}
*/
module.exports.click = async (selector, options = {}, ...args) => {
validate();
const { click } = require("./actions/click");
const desc = await click(selector, options, ...args);
descEvent.emit("success", desc);
};
/**
* Fetches an element with the given selector, scrolls it into view if needed, and then double clicks the element. If there's no element matching selector, the method throws an error.
*
* @example
* await doubleClick('Get Started')
* @example
* await doubleClick(button('Get Started'))
* @example
* await doubleClick('Get Started', { waitForNavigation: true, force: true })
* @example
* await doubleClick('Get Started', { waitForNavigation: false }, below('text'))
*
* @param {selector|string} selector - A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be double clicked.
* @param {Object} options
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the click. Default navigation timeout is 30 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {boolean} [options.force=false] - Set to true to perform action on hidden/disabled elements.
* @param {relativeSelector[]} args - Proximity selectors
*
* @returns {Promise<void>}
*/
module.exports.doubleClick = async (selector, options = {}, ...args) => {
validate();
options.clickCount = 2;
const { click } = require("./actions/click");
await click(selector, options, ...args);
descEvent.emit("success", `Double clicked ${description(selector, true)}`);
};
/**
* Fetches an element with the given selector, scrolls it into view if needed, and then right clicks the element. If there's no element matching selector, the method throws an error.
*
* @example
* await rightClick('Get Started')
* @example
* await rightClick(text('Get Started'), { force: true})
*
* @param {selector|string} selector - A selector to search for element to right click. If there are multiple elements satisfying the selector, the first will be clicked.
* @param {Object} options - Click options.
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the click. Default navigation timeout is 30 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {boolean} [options.force=false] - Set to true to perform action on hidden/disabled elements.
* @param {relativeSelector[]} args - Proximity selectors
*
* @returns {Promise<void>}
*/
module.exports.rightClick = async (selector, options = {}, ...args) => {
validate();
options.button = "right";
const { click } = require("./actions/click");
await click(selector, options, ...args);
descEvent.emit("success", `Right clicked ${description(selector, true)}`);
};
/**
* Fetches the source element with given selector and moves it to given destination selector
* or moves for given distance. If there's no element matching selector, the method throws an error.
* Drag and drop of HTML5 draggable does not work as expected, refer https://github.com/getgauge/taiko/issues/279
*
* @example
* await dragAndDrop($("work"),into($('work done')))
* @example
* await dragAndDrop($("work"),{up:10,down:10,left:10,right:10}, { force: true})
*
* @param {selector|string} source - Element to be Dragged
* @param {selector|string|Object} destinationOrDistance - Element for dropping the dragged element
* or an object specifying the drag&drop distance to be moved from position of source element
* @param {boolean} [options.force=false] - Set to true to perform action on hidden/disabled elements.
* @returns {Promise<void>}
*/
module.exports.dragAndDrop = async (source, destination, options = {}) => {
validate();
const { dragAndDrop } = require("./actions/dragAndDrop");
const desc = await dragAndDrop(source, destination, options);
descEvent.emit("success", desc);
};
/**
* Fetches an element with the given selector, scrolls it into view if needed, and then hovers over the center of the element. If there's no element matching selector, the method throws an error.
*
* @example
* await hover('Get Started')
* @example
* await hover(link('Get Started'))
* @example
* await hover(link('Get Started'), { waitForEvents: ['firstMeaningfulPaint'], force: true })
*
* @param {selector|string} selector - A selector to search for element to right click. If there are multiple elements satisfying the selector, the first will be hovered.
* @param {Object} options
* @param {boolean} [options.force=false] - Set to true to perform action on hidden/disabled elements.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
*/
module.exports.hover = async (selector, options = {}) => {
validate();
const { hover } = require("./actions/hover");
const desc = await hover(selector, setNavigationOptions(options));
descEvent.emit("success", desc);
};
/**
* Fetches an element with the given selector and focuses it. If there's no element matching selector, the method throws an error.
*
* @example
* await focus(textBox('Username:'))
* @example
* await focus(textBox('Username:'), { waitForEvents: ['firstMeaningfulPaint'], force: true })
*
* @param {selector|string} selector - A selector of an element to focus. If there are multiple elements satisfying the selector, the first will be focused.
* @param {Object} options
* @param {boolean} [options.force=false] - Set to true to perform action on hidden/disabled elements.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
*/
module.exports.focus = async (selector, options = {}) => {
validate();
const { focus } = require("./actions/focus");
const desc = await focus(selector, setNavigationOptions(options), true);
descEvent.emit("success", desc);
};
/**
* Types the given text into the focused or given element.
* @example
* await write('admin')
* @example
* await write('admin', into(textBox("Username"),{force:true})
*
* @param {string} text - Text to type into the element.
* @param {selector|Element|string} into - A selector of an element to write into.
* @param {Object} options
* @param {number} [options.delay = 0] - Time to wait between key presses in milliseconds.
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the click. Default navigation timeout is 15 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {number} [options.waitForStart=100] - wait for navigation to start. Accepts time in milliseconds.
* @param {number} [options.navigationTimeout=30000] - Navigation timeout value in milliseconds for navigation after click.
* @param {boolean} [options.hideText=false] - Prevent given text from being written to log output.
* @param {boolean} [options.force=false] - Set to true to perform action on hidden/disabled elements.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
*
* @returns {Promise<void>}
*/
module.exports.write = async (text, into, options = { delay: 0 }) => {
validate();
const { write } = require("./actions/write");
const _options = setNavigationOptions(options);
const desc = await write(text, into, _options);
descEvent.emit("success", desc);
};
/**
* Clears the value of given selector. If no selector is given clears the current active element.
*
* @example
* await clear()
* @example
* await clear(textBox({placeholder:'Email'}))
* @example
* await clear(textBox({ placeholder: 'Email' }), { waitForNavigation: true, force: true })
*
* @param {selector} selector - A selector to search for element to clear. If there are multiple elements satisfying the selector, the first will be cleared.
* @param {Object} options - Click options.
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after clear. Default navigation timeout is 30 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {number} [options.waitForStart=100] - wait for navigation to start. Accepts time in milliseconds.
* @param {number} [options.navigationTimeout=30000] - Navigation timeout value in milliseconds for navigation after click.
* @param {boolean} [options.force=false] - Set to true to perform action on hidden/disabled elements.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
*
* @returns {Promise<void>}
*/
module.exports.clear = async (selector, options = {}) => {
validate();
let _selector = selector;
let _options = options;
if (selector && !isSelector(selector) && !isElement(selector)) {
_options = selector;
_selector = undefined;
}
_options = setNavigationOptions(_options);
const { clear } = require("./actions/clear");
const desc = await clear(_selector, _options);
descEvent.emit("success", desc);
};
/**
* Attaches a file to a file input element.
*
* @example
* await attach('c:/abc.txt', to('Please select a file:'))
* @example
* await attach('c:/abc.txt', 'Please select a file:')
* @example
* await attach('c:/abc.txt', 'Please select a file:',{force:true})
*
* @param {string|Array} filepath or filepaths- The path or paths of the file to be attached.
* @param {selector|string} to - The file input element to which to attach the file.
* @param {boolean} [options.force=false] - Set to true to perform action on hidden/disabled elements.
*/
module.exports.attach = async (filepath, to, options = {}) => {
validate();
const { attach } = require("./actions/attach");
const desc = await attach(filepath, to, options);
descEvent.emit("success", desc);
};
/**
* Presses the given keys.
*
* @example
* await press('Enter')
* @example
* await press('a')
* @example
* await press(['Shift', 'ArrowLeft', 'ArrowLeft'])
* @example
* awaitpress('a', { waitForNavigation: false })
*
* @param {string | Array<string> } keys - Name of keys to press. See [USKeyboardLayout](https://github.com/getgauge/taiko/blob/master/lib/data/USKeyboardLayout.js) for a list of all key names.
* @param {Object} options
* @param {string} [options.text = ""] - If specified, generates an input event with this text.
* @param {number} [options.delay=0] - Time to wait between keydown and keyup in milliseconds.
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the click. Default navigation timeout is 30 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {number} [options.waitForStart=100] - wait for navigation to start.
* @param {number} [options.navigationTimeout=30000] - Navigation timeout value in milliseconds for navigation after click.
* @param {string[]} [options.waitForEvents = []] - Events available to wait for ['DOMContentLoaded', 'loadEventFired', 'networkAlmostIdle', 'networkIdle', 'firstPaint', 'firstContentfulPaint', 'firstMeaningfulPaint', 'targetNavigated']
*
* @returns {Promise<void>}
*/
module.exports.press = async (keys, options = {}) => {
validate();
return await _press([].concat(keys), setNavigationOptions(options));
};
async function _press(keys, options) {
await doActionAwaitingNavigation(options, async () => {
for (let i = 0; i < keys.length; i++) {
await inputHandler.down(keys[i], options);
}
if (options?.delay) {
await new Promise((f) => {
setTimeout(f, options.delay);
});
}
const _keys = keys.reverse();
for (let i = 0; i < _keys.length; i++) {
await inputHandler.up(_keys[i]);
}
});
descEvent.emit("success", `Pressed the ${keys.join(" + ")} key`);
}
/**
* Highlights the given element on the page by drawing a red rectangle around it. This is useful for debugging purposes.
*
* @example
* await highlight('Get Started')
* @example
* await highlight(link('Get Started'))
*
* @param {selector|string} selector - A selector of an element to highlight. If there are multiple elements satisfying the selector, the first will be highlighted.
* @param {relativeSelector[]} args - Proximity selectors
*
* @returns {Promise<void>}
*/
module.exports.highlight = highlight;
async function highlight(selector, ...args) {
validate();
if (!defaultConfig.highlightOnAction) {
console.warn("Highlights are disabled. Please enable highlights.");
return;
}
const { highlight } = require("./actions/highlight");
const desc = await highlight(selector, args);
descEvent.emit("success", desc);
}
/**
* Clear all highlights marked using {@link highlight} on the current page.
*
* @example
* await clearHighlights();
*
* @returns {Promise<void>}
*/
module.exports.clearHighlights = async () => {
validate();
const { clearHighlights } = require("./actions/highlight");
const desc = await clearHighlights();
descEvent.emit("success", desc);
};
/**
* Performs the given mouse action on the given coordinates. This is useful in performing actions on canvas.
*
* @example
* await mouseAction('press', {x:0,y:0})
* @example
* await mouseAction('move', {x:9,y:9})
* @example
* await mouseAction('release', {x:9,y:9})
* @example
* await mouseAction($("#elementID"),'press', {x:0,y:0})
* @example
* await mouseAction($(".elementClass"),'move', {x:9,y:9})
* @example
* await mouseAction($("testxpath"),'release', {x:9,y:9})
* @example
* await mouseAction('release', {x:9, y:9}, {navigationTimeout: 30000})
*
* @param {string} action - Action to be performed on the canvas
* @param {Object} coordinates - Coordinates of a point on canvas to perform the action.
* @param {Object} options
* @param {boolean} [options.waitForNavigation=true] - Wait for navigation after the click. Default navigation timeout is 30 seconds, to override pass `{ navigationTimeout: 10000 }` in `options` parameter.
* @param {number} [options