@freelancercom/blue-harvest
Version:
protractor helpers
110 lines (100 loc) • 3.91 kB
text/typescript
import {browser, WebElement} from 'protractor';
import {PositionalLocator} from './locator_types';
import {log} from './logger';
// tslint:disable:no-require-imports "browser_side_scripts.js" is a file
// which is sent to the browser under test via webdriver's execute script.
// It must be uncompiled JavaScript.
const browserSideScripts = require(
'./browser_side_scripts.js');
// tslint:enable:no-require-imports
interface BrowserSidePositionalLocator {
using: 'string'|'regexp'|'css selector';
value: string;
}
export interface BrowserSideOptions {
allowUnseen?: boolean;
allowCovered?: boolean;
wantZero?: boolean;
enabled?: boolean;
disabled?: boolean;
}
/**
* Retrying Find does several things to improve stability for tests. Throws
* an error if the element is not found. Returns the found WebElement, or
* true if the element was not desired and was not found.
*
* NOTE: We are attempting to deprecate all the extra retries here by hooking
* Pantheon up to the standard task-based system that Protractor uses to ask
* Angular when the page is stable.
*
* Current stability measures:
* - Polling retry until the timeout is hit.
* - Make sure the element is visible for two searches at least 0.5 seconds
* apart.
*/
export async function retryingFind(
locatorChain: ReadonlyArray<PositionalLocator>, timeout: number,
description: string,
options: BrowserSideOptions): Promise<WebElement|boolean> {
let failure = 'Failure reason unknown';
let okSince = 0;
const returnTimes: Array<number|string> = [];
// Translate values into the form that BrowserSideFind expects.
const browserSideLocators: BrowserSidePositionalLocator[] =
locatorChain.map((positionalLocator) => {
const loc =
browserSideScripts.browserSideLocator(positionalLocator.locator);
loc.direction = positionalLocator.position;
return loc;
});
try {
return await browser.wait(async () => {
try {
// TODO(ralphj): can we replace this with browser.findElement and use
// the JS locator strategy? Probably not, because it sometimes returns
// a boolean (in the 'not' case).
const response: WebElement|string|true =
<any>(await browser.driver.executeScript(
browserSideScripts.browserSideFind, browserSideLocators,
options));
const returnTime = Date.now();
returnTimes.push(returnTime);
// If the return value is a string, this is an error message, element
// was not found (or was found if unexpected).
if (typeof response === 'string') {
failure = response;
okSince = 0;
return false;
}
// If the return value was a WebElement or true, the script was
// successful.
if (okSince === 0) {
failure = options.wantZero ?
'Element was unseen, but must be unseen for at least 0.5s' :
'Element found, but must remain visible for at least 0.5s';
okSince = returnTime;
}
const ELEMENT_STABILITY_TIMEOUT = 500;
if (returnTime - okSince >= ELEMENT_STABILITY_TIMEOUT) {
return response;
} else {
return false;
}
} catch (executeScriptError) {
failure = executeScriptError.message;
return false;
}
}, timeout);
} catch (waitTimeoutError) {
const truncatedReturnTimes = returnTimes.length < 6 ?
returnTimes :
returnTimes.slice(0, 3).concat('...', returnTimes.slice(-3));
// Log the failure here as well as throwing it, so it shows up in place in
// the test log.
log(failure);
log(`Browser-side find element tried ${returnTimes.length} times at:
[${truncatedReturnTimes.join(', ')}]`);
throw new Error(`Failed to find ${description}:
${failure}`);
}
}