/**
* WebDriver helper.
*
* @module WebDriverHelper
*/
/**
* External dependencies
*/
import { By, Key, promise } from 'selenium-webdriver';
import fs from 'fs-extra';
import path from 'path';
import slug from 'slugs';
import temp from 'temp';
export const defaultWaitMs = 10000; // 10s
function returnFalse() {
return false;
}
function returnTrue() {
return true;
}
/**
* Wait for element, located by `selector`, until present and displayed. Timeout
* occurs after `waitMs` if element located by `selector` is not present and
* displayed.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} selector - Instance of locator, mechanism for locating an element
* on the page. For example `By.css( '#content' )`.
* @param {number} waitMs - How long to wait in millisecond. Defaults to 10000.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* helper.waitTillPresentAndDisplayed( driver, By.css( '#content' ) )
* .then( ... );
*
* @return {Promise} A promise that will be resolved with `true` value if element
* located `selector` is present and displayed, or rejected if
* times out waiting element to present and displayed.
*/
export function waitTillPresentAndDisplayed( driver, selector, waitMs = defaultWaitMs ) {
return driver.wait( function() {
return driver.findElement( selector ).then( function( element ) {
return element.isDisplayed().then( returnTrue, returnFalse );
}, returnFalse );
}, waitMs, `Timed out waiting for element with ${ selector.using } of '${ selector.value }' to be present and displayed` );
}
/**
* Checks whether an element located by `selector` is eventually present and displayed.
* Timeout occurs after `waitMs` if element located by `selector` is not present and
* displayed.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} selector - Instance of locator, mechanism for locating an element
* on the page. For example `By.css( '#content' )`.
* @param {number} waitMs - How long to wait in millisecond. Defaults to 10000.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* helper.isEventuallyPresentAndDisplayed( driver, By.css( '#content' ) )
* .then( ... );
*
* @return {Promise} A promise that will be resolved with `true` value if element
* located `selector` is eventually present and displayed, or
* rejected if times out waiting the element to be present
* and displayed.
*/
export function isEventuallyPresentAndDisplayed( driver, selector, waitMs = defaultWaitMs ) {
return driver.wait( function() {
return driver.findElement( selector ).then( function( element ) {
return element.isDisplayed().then( returnTrue, returnFalse );
}, returnFalse );
}, waitMs ).then( ( shown ) => {
return shown;
}, returnFalse );
}
/**
* Wait for element, located by `selector`, until not present. Timeout occurs
* after `waitMs` if element located by `selector` still present.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} selector - Instance of locator, mechanism for locating an element
* on the page. For example `By.css( '#content' )`.
* @param {number} waitMs - How long to wait in millisecond. Defaults to 10000.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* helper.waitTillNotPresent( driver, By.css( '#content' ) )
* .then( ... );
*
* @return {Promise} A promise that will be resolved with `true` value if element
* located `selector` is eventually not present, or rejected
* if times out waiting the element to be not present.
*/
export function waitTillNotPresent( driver, selector, waitMs = defaultWaitMs ) {
return driver.wait( function() {
return driver.findElement( selector ).then( function( element ) {
return element.isDisplayed().then( returnFalse, returnTrue );
}, returnTrue );
}, waitMs, `Timed out waiting for element with ${ selector.using } of '${ selector.value }' to be not present` );
}
/**
* Wait for the clickable element then click it. Timeout occurs after `waitMs`
* if clickable element located by `selector` is not present.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} selector - Instance of locator, mechanism for locating an element
* on the page. For example `By.css( '#submit' )`.
* @param {number} waitMs - How long to wait in millisecond. Defaults to 10000.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* helper.clickWhenClickable( driver, By.css( 'a.button' ) )
* .then( ... );
*
* @return {Promise} A promise that will be resolved with `true` if element
* located by `selector` is successfully clicked, `false` if
* element is not clickable, or rejected if times out waiting
* clickable element to present and displayed.
*/
export function clickWhenClickable( driver, selector, waitMs = defaultWaitMs ) {
return driver.wait( function() {
return driver.findElement( selector ).then( function( element ) {
return element.click().then( returnTrue, returnFalse );
}, returnFalse );
}, waitMs, `Timed out waiting for element with ${ selector.using } of '${ selector.value }' to be clickable` );
}
/**
* Check the checkbox element located by `selector`.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} selector - Instance of locator, mechanism for locating an element
* on the page. For example `By.css( 'input[type="checkbox"]' )`.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* helper.setCheckbox( driver, By.css( 'input[type="checkbox"]' ) )
* .then( ... );
*
* @return {Promise} A promise that will be resolved with `true` if checkbox
* element located by `selector` is checked.
*/
export function setCheckbox( driver, selector ) {
return driver.findElement( selector ).then( ( checkbox ) => {
return checkbox.getAttribute( 'checked' ).then( ( checked ) => {
if ( checked !== 'true' ) {
return this.clickWhenClickable( driver, selector );
}
return true;
} );
} );
}
/**
* Uncheck the checkbox element located by `selector`.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} selector - Instance of locator, mechanism for locating an element
* on the page. For example `By.css( 'input[type="checkbox"]' )`.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* helper.unsetCheckbox( driver, By.css( 'input[type="checkbox"]' ) )
* .then( ... );
*
* @return {Promise} A promise that will be resolved with `true` if checkbox
* element located by `selector` is unchecked.
*/
export function unsetCheckbox( driver, selector ) {
return driver.findElement( selector ).then( ( checkbox ) => {
checkbox.getAttribute( 'checked' ).then( ( checked ) => {
if ( checked === 'true' ) {
return this.clickWhenClickable( driver, selector );
}
} );
} );
}
/**
* Wait for the element value is cleared. Timeout occurs after `waitMs` if
* the element located by `selector` is not present and displayed.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} selector - Instance of locator, mechanism for locating an element
* on the page. For example `By.css( 'input[name="username"]' )`.
* @param {number} waitMs - How long to wait in millisecond. Defaults to 10000.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* helper.waitForFieldClearable( driver, By.css( 'input[name="username"]' ) )
* .then( ... );
*
* @return {Promise} A promise that will be resolved with `true` if element's value,
* located by `selector`, is successfully cleared, `false` if
* element's value is not cleared, or rejected if times out waiting
* the element to present and displayed.
*/
export function waitForFieldClearable( driver, selector, waitMs = defaultWaitMs ) {
return driver.wait( function() {
return driver.findElement( selector ).then( ( element ) => {
return element.clear().then( function() {
return element.getAttribute( 'value' ).then( ( value ) => {
return value === '';
} );
}, returnFalse );
}, returnFalse );
}, waitMs, `Timed out waiting for element with ${ selector.using } of '${ selector.value }' to be clearable` );
}
/**
* Set the element's value, located by `selector`, with `value`. Timeout occurs
* after `waitMs` if the element located by `selector` is not present and displayed.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} selector - Instance of locator, mechanism for locating an element
* on the page. For example `By.css( 'input[name="username"]' )`.
* @param {string} value - Value to set to the element.
* @param {objec} options - Optional object where `secureValue` is a boolea
* indicating the element's value shouldn't be exposed
* in the log ( e.g input[type="password"] ), `waitMs`
* is time in millisecond to wait for the element
* to be settable.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* helper.setWhenSettable( driver, By.css( 'input[name="username"]' ) )
* .then( ... );
*
* @return {Promise} A promise that will be resolved with `true` if element's value,
* located by `selector`, is successfully set, `false` if
* element's value is not set, or rejected if times out waiting
* the element to present and displayed.
*/
export function setWhenSettable( driver, selector, value, { secureValue = false, waitMs = defaultWaitMs } = {} ) {
const logValue = secureValue === true ? '*********' : value;
const self = this;
return driver.wait( function() {
return driver.findElement( selector ).then( function( element ) {
self.waitForFieldClearable( driver, selector );
return element.sendKeys( value ).then( function() {
return element.getAttribute( 'value' ).then( ( actualValue ) => {
return actualValue === value;
} );
}, returnFalse );
}, returnFalse );
}, waitMs, `Timed out waiting for element with ${ selector.using } of '${ selector.value }' to be settable to: '${ logValue }'` );
}
/**
* Select option with text `optionText` in select element lcoated by `dropdownSelector`.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} dropdownSelector - Instance of locator, mechanism for locating
* an element on the page. For example
* `By.css( 'select[name="country"]' )`.
* @param {string} optionText - Option text.
*
* @return {Promise} A promise that will be resolved to `true` if option can
* be selected, or `false` if not.
*/
export function selectOption( driver, dropdownSelector, optionText ) {
const dropdown = driver.findElement( dropdownSelector );
return dropdown.then( ( select ) => {
return select.click().then( () => {
const option = select.findElement(
By.xpath( `.//option[contains(text(),"${ optionText }")]` )
);
return option.then( ( opt ) => {
return opt.click().then( returnTrue, returnFalse );
}, returnFalse );
}, returnFalse );
}, returnFalse );
}
/**
* Clear cookies and delete localStorage.
*
* @param {WebDriver} driver - Instance of WebDriver.
*
* @return {bool} Returns true once localStorage is cleared.
*/
export function clearCookiesAndDeleteLocalStorage( driver ) {
driver.manage().deleteAllCookies();
return this.deleteLocalStorage( driver );
}
/**
* Empty all keys out of the `localStorage`.
*
* Under the hood invoke `window.localStorage.clear()`.
*
* @param {WebDriver} driver - Instance of WebDriver.
*/
export function deleteLocalStorage( driver ) {
driver.getCurrentUrl().then( ( url ) => {
if ( url.startsWith( 'data:' ) === false && url !== 'about:blank' ) {
return driver.executeScript( 'window.localStorage.clear();' );
}
} );
}
/**
* Scroll up once by pressing page up key.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {number} waitMs - Sleep for `waitMs` after pressing page up.
*/
export function scrollUp( driver, waitMs = 2000 ) {
driver.actions().
sendKeys( Key.PAGE_UP ).
perform();
driver.sleep( waitMs );
}
/**
* Mouse move into element located by `selector`.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {object} selector - Instance of locator, mechanism for locating an element
* on the page. For example `By.css( 'input[name="username"]' )`.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
* const selector = By.css( '#submit' );
*
* helper.mouseMoveTo( driver, selector ).then( () => {
* helper.clickWhenClickable( driver, selector );
* } );
*
* @return {Promise} A promise that will be resolved to `true` if mouse can
* be moved to the element located by `selector`, `false`
* if can not be moved.
*/
export function mouseMoveTo( driver, selector ) {
return driver.actions().
mouseMove( driver.findElement( selector ) ).
perform().then( () => {
return true;
}, () => {
return false;
} );
}
/**
* Scroll down once by pressing page down key.
*
* @param {WebDriver} driver - Instance of WebDriver.
* @param {number} waitMs - Sleep for `waitMs` after pressing page down.
*/
export function scrollDown( driver, waitMs = 2000 ) {
driver.actions().
sendKeys( Key.PAGE_DOWN ).
perform();
driver.sleep( waitMs );
}
/**
* Write image `data` to `dst`.
*
* @param {String|Buffer|Uint8Array} data - Date to write.
* @param {dst} dst - Path of the file where data being
* written into.
*
* @example
*
* import path from 'path';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* driver.takeScreenshot().then( data => {
* const dst = path.resolve( manager.config.screenshotsDir, 'screenshot.png' );
* helper.writeImage( data, dst );
* } );
*
* @return {unefined} Returns value from `fs.writeFileSync` which is `undefined`.
*/
export function writeImage( data, dst ) {
fs.ensureFileSync( dst );
return fs.writeFileSync( dst, data, 'base64' );
}
/**
* Write text `data` to `dst`.
*
* @param {String|Buffer|Uint8Array} content - Date to write.
* @param {dst} dst - Path of the file where data being
* written into.
*
* @example
*
* import path from 'path';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
* const dst = path.resolve( process.cwd(), 'browser-log.txt' );
*
* driver.manage().logs().get( 'browser' ).then( logs => {
* logs.forEach( log => {
* helper.writeText( log.message, dst );
* } );
* } );
*
* @return {unefined} Returns value from `fs.writeFileSync` which is `undefined`.
*/
export function writeText( content, dst ) {
fs.ensureFileSync( dst );
return fs.writeFileSync( dst, content );
}
/**
* Get path of example media where filename is `filename` and media type is `type`.
*
* This function can be used for testing file upload of various where various
* media types is allowed to be uploaded. Under the hood it copies pre-defined
* media in this package into `filename` and returns the file details.
*
* @param {String} filename - Full path of filename where new media is going
* to be created.
* @param {String} type - Media type.
*
* @example
*
* import { By } from 'selenium-webdriver';
* import path from 'path';
* import { WebDriverManager, WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* const manager = new WebDriverManager( 'chrome' );
* const driver = manager.getDriver();
*
* helper.getMediaWithFilename( 'test-upload-image.jpg', 'jpg' ).then( data => {
* driver.findElement( By.css( 'input[type="file"]' ) ).sendKeys( data.file );
* helper.clickWhenClickable( driver, By.css( '#submit' ) );
* } );
*
* @returns {Promise} Promise of file details.
*/
export function getMediaWithFilename( filename, type = 'jpg' ) {
const src = path.resolve( __dirname, `../media/media.${ type }` );
if ( ! fs.existsSync( src ) ) {
throw new Error( `Source media ${ src } does not exist` );
}
const dst = path.resolve( temp.mkdirSync( 'media' ), filename );
const d = promise.defer();
fs.copySync( src, dst );
d.fulfill( {
imageName: filename,
fileName: filename,
file: dst,
} );
return d.promise;
}
/**
* Take a screenshot from `currentTest`.
*
* The best place to use this is in `test.afterEach` hook where all tests
* are captured.
*
* @param {WebDriverManager} manager - Instance of `WebDriverManager`.
* @param {Object} currentTest - Current test.
*
* @example
*
* import test from 'selenium-webdriver/testing';
* import { WebDriverHelper as helper } from 'wp-e2e-webdriver';
*
* test.afterEach( 'Take screenshot', function() {
* return helper.takeScreenshot( global.__MANAGER__, this.currentTest );
* } );
* @return {Promise} A promise that will be resolved with `undefined` once
* screenshot is written to `manager.config.screenshotsDir`.
*/
export function takeScreenshot( manager, currentTest ) {
if ( ! currentTest ) {
return;
}
const driver = manager.getDriver();
const title = slug( currentTest.title );
const state = currentTest.state;
const screenSize = manager.getConfigScreenSize();
const filename = `${ state }-${ screenSize }-${ title }.png`;
return driver.takeScreenshot().then( data => {
const dst = path.resolve( manager.config.screenshotsDir, filename );
return writeImage( data, dst );
} );
}