UNPKG

wp-e2e-webdriver

Version:

Webdriver manager and helper for WP end-to-end tests

564 lines (529 loc) 20.1 kB
/** * 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 {object} 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 {object} 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 {object} 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 {object} 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 {object} 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 {object} 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 {object} 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 {object} 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 {object} 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 {object} 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 {object} driver - Instance of WebDriver. * * @return {boolean} 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 {object} 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 {object} 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 {object} 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 {object} 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 {string} 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 {undefined} 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 {string} 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 {undefined} 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 {object} 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 ); } ); }