Skip to content

Instantly share code, notes, and snippets.

@reubenmiller
Last active January 28, 2018 17:19
Show Gist options
  • Save reubenmiller/f070176d4efca6ab63c2b761ee94ec10 to your computer and use it in GitHub Desktop.
Save reubenmiller/f070176d4efca6ab63c2b761ee94ec10 to your computer and use it in GitHub Desktop.
WebDriverIO.js with modified resizeWindow function
let webdriverio;
const requireg = require('requireg');
const Helper = require('../helper');
const stringIncludes = require('../assert/include').includes;
const { urlEquals, equals } = require('../assert/equal');
const empty = require('../assert/empty').empty;
const truth = require('../assert/truth').truth;
const {
xpathLocator, fileExists, clearString, decodeUrl, chunkArray,
} = require('../utils');
const ElementNotFound = require('./errors/ElementNotFound');
const assert = require('assert');
const path = require('path');
const webRoot = 'body';
const Locator = require('../locator');
let withinStore = {};
/**
* WebDriverIO helper which wraps [webdriverio](http://webdriver.io/) library to
* manipulate browser using Selenium WebDriver or PhantomJS.
*
* WebDriverIO requires [Selenium Server and ChromeDriver/GeckoDriver to be installed](http://codecept.io/quickstart/#prepare-selenium-server).
*
* ### Configuration
*
* This helper should be configured in codecept.json
*
* * `url` - base url of website to be tested
* * `browser` - browser in which perform testing
* * `restart` (optional, default: true) - restart browser between tests.
* * `smartWait`: (optional) **enables [SmartWait](http://codecept.io/acceptance/#smartwait)**; wait for additional milliseconds for element to appear. Enable for 5 secs: "smartWait": 5000
* * `disableScreenshots` (optional, default: false) - don't save screenshot on failure
* * `uniqueScreenshotNames` (optional, default: false) - option to prevent screenshot override if you have scenarios with the same name in different suites
* * `keepBrowserState` (optional, default: false) - keep browser state between tests when `restart` set to false.
* * `keepCookies` (optional, default: false) - keep cookies between tests when `restart` set to false.
* * `windowSize`: (optional) default window size. Set to `maximize` or a dimension in the format `640x480`.
* * `waitForTimeout`: (option) sets default wait time in *ms* for all `wait*` functions. 1000 by default;
* * `desiredCapabilities`: Selenium's [desired
* capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities)
* * `manualStart` (optional, default: false) - do not start browser before a test, start it manually inside a helper
* with `this.helpers["WebDriverIO"]._startBrowser()`
* * `timeouts`: [WebDriverIO timeouts](http://webdriver.io/guide/testrunner/timeouts.html) defined as hash.
*
* Example:
*
* ```json
* {
* "helpers": {
* "WebDriverIO" : {
* "smartWait": 5000,
* "browser": "chrome",
* "restart": false,
* "windowSize": "maximize",
* "timeouts": {
* "script": 60000,
* "page load": 10000
* }
* }
* }
* }
* ```
*
* Additional configuration params can be used from [webdriverio
* website](http://webdriver.io/guide/getstarted/configuration.html).
*
* ### Headless Chrome
*
* ```json
* {
* "helpers": {
* "WebDriverIO" : {
* "url": "http://localhost",
* "browser": "chrome",
* "desiredCapabilities": {
* "chromeOptions": {
* "args": [ "--headless", "--disable-gpu", "--window-size=800,600" ]
* }
* }
* }
* }
* }
* ```
*
* ### Connect through proxy
*
* CodeceptJS also provides flexible options when you want to execute tests to Selenium servers through proxy. You will
* need to update the `helpers.WebDriverIO.desiredCapabilities.proxy` key.
*
* ```js
* {
* "helpers": {
* "WebDriverIO": {
* "desiredCapabilities": {
* "proxy": {
* "proxyType": "manual|pac",
* "proxyAutoconfigUrl": "URL TO PAC FILE",
* "httpProxy": "PROXY SERVER",
* "sslProxy": "PROXY SERVER",
* "ftpProxy": "PROXY SERVER",
* "socksProxy": "PROXY SERVER",
* "socksUsername": "USERNAME",
* "socksPassword": "PASSWORD",
* "noProxy": "BYPASS ADDRESSES"
* }
* }
* }
* }
* }
* ```
* For example,
*
* ```js
* {
* "helpers": {
* "WebDriverIO": {
* "desiredCapabilities": {
* "proxy": {
* "proxyType": "manual",
* "httpProxy": "http://corporate.proxy:8080",
* "socksUsername": "codeceptjs",
* "socksPassword": "secret",
* "noProxy": "127.0.0.1,localhost"
* }
* }
* }
* }
* }
* ```
*
* Please refer to [Selenium - Proxy Object](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities) for more
* information.
*
* ### Cloud Providers
*
* WebDriverIO makes it possible to execute tests against services like `Sauce Labs` `BrowserStack` `TestingBot`
* Check out their documentation on [available parameters](http://webdriver.io/guide/usage/cloudservices.html)
*
* Connecting to `BrowserStack` and `Sauce Labs` is simple. All you need to do
* is set the `user` and `key` parameters. WebDriverIO automatically know which
* service provider to connect to.
*
* ```js
* {
* "helpers":{
* "WebDriverIO": {
* "url": "YOUR_DESIRED_HOST",
* "user": "YOUR_BROWSERSTACK_USER",
* "key": "YOUR_BROWSERSTACK_KEY",
* "desiredCapabilities": {
* "browserName": "chrome",
*
* // only set this if you're using BrowserStackLocal to test a local domain
* // "browserstack.local": true,
*
* // set this option to tell browserstack to provide addition debugging info
* // "browserstack.debug": true,
* }
* }
* }
* }
* ```
*
* ### Multiremote Capabilities
*
* This is a work in progress but you can control two browsers at a time right out of the box.
* Individual control is something that is planned for a later version.
*
* Here is the [webdriverio docs](http://webdriver.io/guide/usage/multiremote.html) on the subject
*
* ```js
* {
* "helpers": {
* "WebDriverIO": {
* "multiremote": {
* "MyChrome": {
* "desiredCapabilities": {
* "browserName": "chrome"
* }
* },
* "MyFirefox": {
* "desiredCapabilities": {
* "browserName": "firefox"
* }
* }
* }
* }
* }
* }
* ```
*
*
* ## Access From Helpers
*
* Receive a WebDriverIO client from a custom helper by accessing `browser` property:
*
* ```js
* this.helpers['WebDriverIO'].browser
* ```
*/
class WebDriverIO extends Helper {
constructor(config) {
super(config);
webdriverio = requireg('webdriverio');
this.options = {
smartWait: 0,
waitForTimeout: 1000, // ms
desiredCapabilities: {},
restart: true,
uniqueScreenshotNames: false,
disableScreenshots: false,
fullPageScreenshots: true,
manualStart: false,
keepCookies: false,
keepBrowserState: false,
deprecationWarnings: false,
timeouts: {
script: 1000, // ms
},
};
this._validateConfig(config);
}
_validateConfig(config) {
// set defaults
this.root = webRoot;
this.isRunning = false;
// override defaults with config
Object.assign(this.options, config);
this.options.baseUrl = this.options.url || this.options.baseUrl;
this.options.desiredCapabilities.browserName = this.options.browser || this.options.desiredCapabilities.browserName;
this.options.waitForTimeout /= 1000; // convert to seconds
if (!this.options.desiredCapabilities.platformName && (!this.options.url || !this.options.browser)) {
throw new Error(`
WebDriverIO requires at url and browser to be set.
Check your codeceptjs config file to ensure these are set properly
{
"helpers": {
"WebDriverIO": {
"url": "YOUR_HOST"
"browser": "YOUR_PREFERRED_TESTING_BROWSER"
}
}
}
`);
}
}
static _checkRequirements() {
try {
requireg('webdriverio');
} catch (e) {
return ['webdriverio'];
}
}
static _config() {
return [{
name: 'url',
message: 'Base url of site to be tested',
default: 'http://localhost',
}, {
name: 'browser',
message: 'Browser in which testing will be performed',
default: 'chrome',
}];
}
_beforeSuite() {
if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
this.debugSection('Session', 'Starting singleton browser session');
return this._startBrowser();
}
}
async _startBrowser() {
if (this.options.multiremote) {
this.browser = webdriverio.multiremote(this.options.multiremote).init();
} else {
this.browser = webdriverio.remote(this.options).init();
}
await this.browser;
this.isRunning = true;
if (this.options.timeouts) {
await this.defineTimeout(this.options.timeouts);
}
if (this.options.windowSize === 'maximize') {
await this.resizeWindow('maximize');
} else if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
const dimensions = this.options.windowSize.split('x');
await this.resizeWindow(dimensions[0], dimensions[1]);
}
return this.browser;
}
async _before() {
this.context = this.root;
if (this.options.restart && !this.options.manualStart) return this._startBrowser();
if (!this.isRunning && !this.options.manualStart) return this._startBrowser();
return this.browser;
}
async _after() {
if (!this.isRunning) return;
if (this.options.restart) {
this.isRunning = false;
return this.browser.end();
}
if (this.options.keepBrowserState) return;
if (!this.options.keepCookies && this.options.desiredCapabilities.browserName) {
this.debugSection('Session', 'cleaning cookies and localStorage');
await this.browser.deleteCookie();
}
await this.browser.execute('localStorage.clear();').catch((err) => {
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
});
await this.closeOtherTabs();
return this.browser;
}
_afterSuite() {
}
_finishTest() {
if (!this.options.restart && this.isRunning) return this.browser.end();
}
async _failed(test) {
if (Object.keys(withinStore).length !== 0) await this._withinEnd();
if (!this.options.disableScreenshots) {
let fileName = clearString(test.title);
if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`);
if (this.options.uniqueScreenshotNames) {
const uuid = test.uuid || test.ctx.test.uuid;
fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`;
} else {
fileName += '.failed.png';
}
await this.saveScreenshot(fileName, this.options.fullPageScreenshots).catch((err) => {
if (err &&
err.type &&
err.type === 'RuntimeError' &&
err.message &&
(err.message.indexOf('was terminated due to') > -1 || err.message.indexOf('no such window: target window already closed') > -1)
) {
this.isRunning = false;
}
});
}
}
async _withinBegin(locator) {
const frame = isFrameLocator(locator);
const client = this.browser;
if (frame) {
if (Array.isArray(frame)) {
withinStore.frame = frame.join('>');
return client
.frame(null)
.then(() => frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()));
}
withinStore.frame = frame;
return this.switchTo(frame);
}
withinStore.elFn = this.browser.element;
withinStore.elsFn = this.browser.elements;
this.context = locator;
const res = await client.element(withStrictLocator.call(this, locator));
this.browser.element = function (l) {
return this.elementIdElement(res.value.ELEMENT, l);
};
this.browser.elements = function (l) {
return this.elementIdElements(res.value.ELEMENT, l);
};
return this.browser;
}
async _withinEnd() {
if (withinStore.frame) {
withinStore = {};
return this.switchTo(null);
}
this.context = this.root;
this.browser.element = withinStore.elFn;
this.browser.elements = withinStore.elsFn;
withinStore = {};
}
/**
* Get elements by different locator types, including strict locator
* Should be used in custom helpers:
*
* ```js
* this.helpers['WebDriverIO']._locate({name: 'password'}).then //...
* ```
*/
async _locate(locator, smartWait = false) {
if (!this.options.smartWait || !smartWait) return this.browser.elements(withStrictLocator.call(this, locator));
this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${locator} in ${this.options.smartWait}`);
await this.defineTimeout({ implicit: this.options.smartWait });
const els = await this.browser.elements(withStrictLocator.call(this, locator));
await this.defineTimeout({ implicit: 0 });
return els;
}
/**
* Find a checkbox by providing human readable text:
*
* ```js
* this.helpers['WebDriverIO']._locateCheckable('I agree with terms and conditions').then // ...
* ```
*/
async _locateCheckable(locator) {
return findCheckable.call(this, locator, this.browser.elements.bind(this)).then(res => res.value);
}
/**
* Find a clickable element by providing human readable text:
*
* ```js
* this.helpers['WebDriverIO']._locateClickable('Next page').then // ...
* ```
*/
async _locateClickable(locator) {
return findClickable.call(this, locator, this.browser.elements.bind(this)).then(res => res.value);
}
/**
* Find field elements by providing human readable text:
*
* ```js
* this.helpers['WebDriverIO']._locateFields('Your email').then // ...
* ```
*/
async _locateFields(locator) {
return findFields.call(this, locator).then(res => res.value);
}
/**
* Set [WebDriverIO timeouts](http://webdriver.io/guide/testrunner/timeouts.html) in realtime.
* Appium: support only web testing
* Timeouts are expected to be passed as object:
*
* ```js
* I.defineTimeout({ script: 5000 });
* I.defineTimeout({ implicit: 10000, pageLoad: 10000, script: 5000 });
* ```
*/
async defineTimeout(timeouts) {
if (timeouts.implicit) {
await this.browser.timeouts('implicit', timeouts.implicit);
}
if (timeouts['page load']) {
await this.browser.timeouts('page load', timeouts['page load']);
}
// both pageLoad and page load are accepted
// see http://webdriver.io/api/protocol/timeouts.html
if (timeouts.pageLoad) {
await this.browser.timeouts('page load', timeouts.pageLoad);
}
if (timeouts.script) {
await this.browser.timeouts('script', timeouts.script);
}
return this.browser; // return the last response
}
/**
* {{> ../webapi/amOnPage }}
* Appium: support only web testing
*/
amOnPage(url) {
return this.browser.url(url).url((err, res) => {
if (err) throw err;
this.debugSection('Url', res.value);
});
}
/**
* {{> ../webapi/click }}
* Appium: support
*/
async click(locator, context = null) {
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementIdClick';
const locateFn = prepareLocateFn.call(this, context);
const res = await findClickable.call(this, locator, locateFn);
if (context) {
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`);
} else {
assertElementExists(res, locator, 'Clickable element');
}
return this.browser[clickMethod](res.value[0].ELEMENT);
}
/**
* {{> ../webapi/doubleClick }}
* Appium: support only web testing
*/
async doubleClick(locator, context = null) {
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementIdClick';
const locateFn = prepareLocateFn.call(this, context);
const res = await findClickable.call(this, locator, locateFn);
if (context) {
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`);
} else {
assertElementExists(res, locator, 'Clickable element');
}
const elem = res.value[0];
return this.browser.moveTo(elem.ELEMENT).doDoubleClick();
}
/**
* Performs right click on an element matched by CSS or XPath.
* Appium: support, but in apps works as usual click
*/
async rightClick(locator) {
/**
* just press button if no selector is given
*/
if (locator === undefined) {
return this.browser.buttonPress('right');
}
const res = await this._locate(locator, true);
assertElementExists(res, locator, 'Clickable element');
const elem = res.value[0];
if (this.browser.isMobile) return this.browser.touchClick(elem.ELEMENT);
return this.browser.moveTo(elem.ELEMENT).buttonPress('right');
}
/**
* {{> ../webapi/fillField }}
* Appium: support
*/
async fillField(field, value) {
const res = await findFields.call(this, field);
assertElementExists(res, field, 'Field');
const elem = res.value[0];
return this.browser.elementIdClear(elem.ELEMENT).elementIdValue(elem.ELEMENT, value);
}
/**
* {{> ../webapi/appendField }}
* Appium: support, but it's clear a field before insert in apps
*/
async appendField(field, value) {
const res = await findFields.call(this, field);
assertElementExists(res, field, 'Field');
const elem = res.value[0];
return this.browser.elementIdValue(elem.ELEMENT, value);
}
/**
* {{> ../webapi/clearField}}
* Appium: support
*/
async clearField(field) {
const res = await findFields.call(this, field);
assertElementExists(res, field, 'Field');
const elem = res.value[0];
return this.browser.elementIdClear(elem.ELEMENT);
}
/**
* {{> ../webapi/selectOption}}
*/
async selectOption(select, option) {
const res = await findFields.call(this, select);
assertElementExists(res, select, 'Selectable field');
const elem = res.value[0];
let commands = [];
if (!Array.isArray(option)) {
option = [option];
}
// select options by visible text
option.forEach(opt => commands.push(this.browser.elementIdElements(elem.ELEMENT, Locator.select.byVisibleText(xpathLocator.literal(opt)))));
let els = await this.browser.unify(commands, { extractValue: true });
commands = [];
const clickOptionFn = (el) => {
if (el[0]) el = el[0];
if (el && el.ELEMENT) commands.push(this.browser.elementIdClick(el.ELEMENT));
};
if (els.length) {
els.forEach(clickOptionFn);
return this.browser.unify(commands);
}
// select options by value
option.forEach(opt => commands.push(this.browser.elementIdElements(elem.ELEMENT, Locator.select.byValue(xpathLocator.literal(opt)))));
els = await this.browser.unify(commands, { extractValue: true });
if (els.length === 0) {
throw new ElementNotFound(select, `Option ${option} in`, 'was found neither by visible text not by value');
}
commands = [];
els.forEach(clickOptionFn);
return this.browser.unify(commands);
}
/**
* {{> ../webapi/attachFile }}
* Appium: not tested
*/
async attachFile(locator, pathToFile) {
const file = path.join(global.codecept_dir, pathToFile);
if (!fileExists(file)) {
throw new Error(`File at ${file} can not be found on local system`);
}
const el = await findFields.call(this, locator);
this.debug(`Uploading ${file}`);
const res = await this.browser.uploadFile(file);
assertElementExists(el, locator, 'File field');
return this.browser.elementIdValue(el.value[0].ELEMENT, res.value);
}
/**
* {{> ../webapi/checkOption }}
* Appium: not tested
*/
async checkOption(field, context = null) {
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementIdClick';
const locateFn = prepareLocateFn.call(this, context);
const res = await findCheckable.call(this, field, locateFn);
assertElementExists(res, field, 'Checkable');
const elem = res.value[0];
const isSelected = await this.browser.elementIdSelected(elem.ELEMENT);
if (isSelected.value) return Promise.resolve(true);
return this.browser[clickMethod](elem.ELEMENT);
}
/**
* {{> ../webapi/uncheckOption }}
* Appium: not tested
*/
async uncheckOption(field, context = null) {
const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementIdClick';
const locateFn = prepareLocateFn.call(this, context);
const res = await findCheckable.call(this, field, locateFn);
assertElementExists(res, field, 'Checkable');
const elem = res.value[0];
const isSelected = await this.browser.elementIdSelected(elem.ELEMENT);
if (!isSelected.value) return Promise.resolve(true);
return this.browser[clickMethod](elem.ELEMENT);
}
/**
* {{> ../webapi/grabTextFrom }}
* Appium: support
*/
async grabTextFrom(locator) {
const res = await this._locate(locator, true);
assertElementExists(res, locator);
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdText(el.ELEMENT)));
return this.browser.unify(commands, {
extractValue: true,
}).then(selected => selected);
}
/**
* Retrieves the innerHTML from an element located by CSS or XPath and returns it to test.
* Resumes test execution, so **should be used inside a generator with `yield`** operator.
* Appium: support only web testing
*
*
* ```js
* let postHTML = yield I.grabHTMLFrom('#post');
* ```
*/
async grabHTMLFrom(locator) {
return this.browser.getHTML(withStrictLocator.call(this, locator)).then(html => html);
}
/**
* {{> ../webapi/grabValueFrom }}
* Appium: support only web testing
*/
async grabValueFrom(locator) {
const res = await this._locate(locator, true);
assertElementExists(res, locator);
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdAttribute(el.ELEMENT, 'value')));
return this.browser.unify(commands, {
extractValue: true,
}).then(selected => selected);
}
/**
* Grab CSS property for given locator
*
* ```js
* I.grabCssPropertyFrom('h3', 'font-weight');
* ```
*/
async grabCssPropertyFrom(locator, cssProperty) {
const res = await this._locate(locator, true);
assertElementExists(res, locator);
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdCssProperty(el.ELEMENT, cssProperty)));
return this.browser.unify(commands, {
extractValue: true,
}).then(selected => selected);
}
/**
* {{> ../webapi/grabAttributeFrom }}
* Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
*/
async grabAttributeFrom(locator, attr) {
const res = await this._locate(locator, true);
assertElementExists(res, locator);
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdAttribute(el.ELEMENT, attr)));
return this.browser.unify(commands, {
extractValue: true,
}).then(selected => selected);
}
/**
* {{> ../webapi/seeInTitle }}
* Appium: support only web testing
*/
async seeInTitle(text) {
const title = await this.browser.getTitle();
return stringIncludes('web page title').assert(text, title);
}
/**
* Checks that title is equal to provided one.
*
* ```js
* I.seeTitleEquals('Test title.');
* ```
*/
async seeTitleEquals(text) {
const title = await this.browser.getTitle();
return assert.equal(title, text, `expected web page title to be ${text}, but found ${title}`);
}
/**
* {{> ../webapi/dontSeeInTitle }}
* Appium: support only web testing
*/
async dontSeeInTitle(text) {
const title = await this.browser.getTitle();
return stringIncludes('web page title').negate(text, title);
}
/**
* {{> ../webapi/grabTitle }}
* Appium: support only web testing
*/
async grabTitle() {
const title = await this.browser.getTitle();
this.debugSection('Title', title);
return title;
}
/**
* {{> ../webapi/see }}
* Appium: support with context in apps
*/
async see(text, context = null) {
return proceedSee.call(this, 'assert', text, context);
}
/**
* Checks that text is equal to provided one.
*
* ```js
* I.seeTextEquals('text', 'h1');
* ```
*/
async seeTextEquals(text, context = null) {
return proceedSee.call(this, 'assert', text, context, true);
}
/**
* {{> ../webapi/dontSee }}
* Appium: support with context in apps
*/
async dontSee(text, context = null) {
return proceedSee.call(this, 'negate', text, context);
}
/**
* {{> ../webapi/seeInField }}
* Appium: support only web testing
*/
async seeInField(field, value) {
return proceedSeeField.call(this, 'assert', field, value);
}
/**
* {{> ../webapi/dontSeeInField }}
* Appium: support only web testing
*/
async dontSeeInField(field, value) {
return proceedSeeField.call(this, 'negate', field, value);
}
/**
* {{> ../webapi/seeCheckboxIsChecked }}
* Appium: not tested
*/
async seeCheckboxIsChecked(field) {
return proceedSeeCheckbox.call(this, 'assert', field);
}
/**
* {{> ../webapi/dontSeeCheckboxIsChecked }}
* Appium: not tested
*/
async dontSeeCheckboxIsChecked(field) {
return proceedSeeCheckbox.call(this, 'negate', field);
}
/**
* {{> ../webapi/seeElement }}
* Appium: support
*/
async seeElement(locator) {
const res = await this._locate(locator, true);
if (!res.value || res.value.length === 0) {
return truth(`elements of ${locator}`, 'to be seen').assert(false);
}
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdDisplayed(el.ELEMENT)));
const selected = await this.browser.unify(commands, { extractValue: true });
return truth(`elements of ${locator}`, 'to be seen').assert(selected);
}
/**
* {{> ../webapi/dontSeeElement}}
* Appium: support
*/
async dontSeeElement(locator) {
const res = await this._locate(locator, false);
if (!res.value || res.value.length === 0) {
return truth(`elements of ${locator}`, 'to be seen').negate(false);
}
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdDisplayed(el.ELEMENT)));
const selected = await this.browser.unify(commands, { extractValue: true });
return truth(`elements of ${locator}`, 'to be seen').negate(selected);
}
/**
* {{> ../webapi/seeElementInDOM }}
* Appium: support
*/
async seeElementInDOM(locator) {
const res = await this.browser.elements(withStrictLocator.call(this, locator));
return empty('elements').negate(res.value);
}
/**
* {{> ../webapi/dontSeeElementInDOM }}
* Appium: support
*/
async dontSeeElementInDOM(locator) {
const res = await this.browser.elements(withStrictLocator.call(this, locator));
return empty('elements').assert(res.value);
}
/**
* {{> ../webapi/seeInSource }}
* Appium: support
*/
async seeInSource(text) {
const source = await this.browser.getSource();
return stringIncludes('HTML source of a page').assert(text, source);
}
/**
* {{> ../webapi/seeInSource }}
* Appium: support
*/
async grabSource() {
return this.browser.getSource();
}
/**
* Get JS log from browser. Log buffer is reset after each request.
*
* ```js
* let logs = yield I.grabBrowserLogs();
* console.log(JSON.stringify(logs))
* ```
*/
async grabBrowserLogs() {
return this.browser.log('browser').then(res => res.value);
}
/**
* Get current URL from browser.
*
* ```js
* let url = yield I.grabBrowserUrl();
* console.log(`Current URL is [${url}]`);
* ```
*/
async grabBrowserUrl() {
const res = await this.browser.url();
return res.value;
}
/**
* {{> ../webapi/dontSeeInSource }}
* Appium: support
*/
async dontSeeInSource(text) {
const source = await this.browser.getSource();
return stringIncludes('HTML source of a page').negate(text, source);
}
/**
* asserts that an element appears a given number of times in the DOM
* Element is located by label or name or CSS or XPath.
* Appium: support
*
* ```js
* I.seeNumberOfElements('#submitBtn', 1);
* ```
*/
async seeNumberOfElements(selector, num) {
const res = await this._locate(withStrictLocator.call(this, selector));
return assert.equal(res.value.length, num, `expected number of elements (${selector}) is ${num}, but found ${res.value.length}`);
}
/**
* asserts that an element is visible a given number of times
* Element is located by CSS or XPath.
*
* ```js
* I.seeNumberOfVisibleElements('.buttons', 3);
* ```
*/
async seeNumberOfVisibleElements(locator, num) {
const res = await this.grabNumberOfVisibleElements(locator);
return assert.equal(res, num, `expected number of visible elements (${locator}) is ${num}, but found ${res}`);
}
/**
* Checks that all elements with given locator have given CSS properties.
*
* ```js
* I.seeCssPropertiesOnElements('h3', { 'font-weight': "bold"});
* ```
*/
async seeCssPropertiesOnElements(locator, cssProperties) {
const res = await this._locate(locator);
assertElementExists(res, locator);
const elemAmount = res.value.length;
const commands = [];
res.value.forEach((el) => {
Object.keys(cssProperties).forEach((prop) => {
commands.push(this.browser.elementIdCssProperty(el.ELEMENT, prop));
});
});
let props = await this.browser.unify(commands, { extractValue: true });
const values = Object.keys(cssProperties).map(key => cssProperties[key]);
if (!Array.isArray(props)) props = [props];
let chunked = chunkArray(props, values.length);
chunked = chunked.filter((val) => {
for (let i = 0; i < val.length; ++i) {
if (val[i] !== values[i]) return false;
}
return true;
});
return assert.ok(
chunked.length === elemAmount,
`Not all elements (${locator}) have CSS property ${JSON.stringify(cssProperties)}`,
);
}
/**
* Checks that all elements with given locator have given attributes.
*
* ```js
* I.seeAttributesOnElements('//form', {'method': "post"});
* ```
*/
async seeAttributesOnElements(locator, attributes) {
const res = await this._locate(locator);
assertElementExists(res, locator);
const elemAmount = res.value.length;
const commands = [];
res.value.forEach((el) => {
Object.keys(attributes).forEach((attr) => {
commands.push(this.browser.elementIdAttribute(el.ELEMENT, attr));
});
});
let attrs = await this.browser.unify(commands, { extractValue: true });
const values = Object.keys(attributes).map(key => attributes[key]);
if (!Array.isArray(attrs)) attrs = [attrs];
let chunked = chunkArray(attrs, values.length);
chunked = chunked.filter((val) => {
for (let i = 0; i < val.length; ++i) {
if (val[i] !== values[i]) return false;
}
return true;
});
return assert.ok(
chunked.length === elemAmount,
`Not all elements (${locator}) have attributes ${JSON.stringify(attributes)}`,
);
}
/**
* Grab number of visible elements by locator
*
* ```js
* I.grabNumberOfVisibleElements('p');
* ```
*/
async grabNumberOfVisibleElements(locator) {
const res = await this._locate(locator);
assertElementExists(res, locator);
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdDisplayed(el.ELEMENT)));
let selected = await this.browser.unify(commands, { extractValue: true });
if (!Array.isArray(selected)) selected = [selected];
selected = selected.filter(val => val === true);
return selected.length;
}
/**
* {{> ../webapi/seeInCurrentUrl }}
* Appium: support only web testing
*/
async seeInCurrentUrl(url) {
const res = await this.browser.url();
return stringIncludes('url').assert(url, decodeUrl(res.value));
}
/**
* {{> ../webapi/dontSeeInCurrentUrl }}
* Appium: support only web testing
*/
async dontSeeInCurrentUrl(url) {
const res = await this.browser.url();
return stringIncludes('url').negate(url, decodeUrl(res.value));
}
/**
* {{> ../webapi/seeCurrentUrlEquals }}
* Appium: support only web testing
*/
async seeCurrentUrlEquals(url) {
const res = await this.browser.url();
return urlEquals(this.options.url).assert(url, decodeUrl(res.value));
}
/**
* {{> ../webapi/dontSeeCurrentUrlEquals }}
* Appium: support only web testing
*/
async dontSeeCurrentUrlEquals(url) {
const res = await this.browser.url();
return urlEquals(this.options.url).negate(url, decodeUrl(res.value));
}
/**
* {{> ../webapi/executeScript }}
* Appium: support only web testing
*
* Wraps [execute](http://webdriver.io/api/protocol/execute.html) command.
*/
executeScript(fn) {
return this.browser.execute.apply(this.browser, arguments).then(res => res.value);
}
/**
* {{> ../webapi/executeAsyncScript }}
* Appium: support only web testing
*/
executeAsyncScript(fn) {
return this.browser.executeAsync.apply(this.browser, arguments).then(res => res.value);
}
/**
* Scrolls to element matched by locator.
* Extra shift can be set with offsetX and offsetY options
* Appium: support only web testing
*
* ```js
* I.scrollTo('footer');
* I.scrollTo('#submit', 5,5);
* ```
*/
async scrollTo(locator, offsetX = 0, offsetY = 0) {
if (typeof locator === 'number' && typeof offsetX === 'number') {
offsetY = offsetX;
offsetX = locator;
locator = null;
}
if (locator) {
const res = await this._locate(withStrictLocator.call(this, locator), true);
if (!res.value || res.value.length === 0) {
return truth(`elements of ${locator}`, 'to be seen').assert(false);
}
const elem = res.value[0];
if (this.browser.isMobile) return this.browser.touchScroll(elem.ELEMENT, offsetX, offsetY);
const location = await this.browser.elementIdLocation(elem.ELEMENT);
assertElementExists(location, 'Failed to receive', 'location');
return this.browser.execute((x, y) => window.scrollTo(x, y), location.value.x + offsetX, location.value.y + offsetY);
}
if (this.browser.isMobile) return this.browser.touchScroll(locator, offsetX, offsetY);
return this.browser.execute((x, y) => window.scrollTo(x, y), offsetX, offsetY);
}
/**
* {{> ../webapi/moveCursorTo}}
* Appium: support only web testing
*/
async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
let hasOffsetParams = true;
if (typeof offsetX !== 'number' && typeof offsetY !== 'number') {
hasOffsetParams = false;
}
const res = await this._locate(withStrictLocator.call(this, locator), true);
assertElementExists(res, locator);
const elem = res.value[0];
if (!this.browser.isMobile) {
return this.browser.moveTo(elem.ELEMENT, offsetX, offsetY);
}
const size = await this.browser.elementIdSize(elem.ELEMENT);
assertElementExists(size, 'Failed to receive', 'size');
const location = await this.browser.elementIdLocation(elem.ELEMENT);
assertElementExists(size, 'Failed to receive', 'location');
let x = location.value.x + size.value.width / 2;
let y = location.value.y + size.value.height / 2;
if (hasOffsetParams) {
x = location.value.x + offsetX;
y = location.value.y + offsetY;
}
return this.browser.touchMove(x, y);
}
/**
* {{> ../webapi/saveScreenshot}}
* Appium: support
*/
async saveScreenshot(fileName, fullPage = false) {
const outputFile = path.join(global.output_dir, fileName);
if (!fullPage) {
this.debug(`Screenshot has been saved to ${outputFile}`);
return this.browser.saveScreenshot(outputFile);
}
const { width, height } = await this.browser.execute(() => ({
height: document.body.scrollHeight,
width: document.body.scrollWidth,
}));
await this.browser.windowHandleSize(width, height);
this.debug(`Screenshot has been saved to ${outputFile}`);
return this.browser.saveScreenshot(outputFile);
}
/**
* {{> ../webapi/setCookie}}
* Appium: support only web testing
*
* Uses Selenium's JSON [cookie
* format](https://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object).
*/
async setCookie(cookie) {
return this.browser.setCookie(cookie);
}
/**
* {{> ../webapi/clearCookie}}
* Appium: support only web testing
*/
async clearCookie(cookie) {
return this.browser.deleteCookie(cookie);
}
/**
* {{> ../webapi/seeCookie}}
* Appium: support only web testing
*/
async seeCookie(name) {
const res = await this.browser.getCookie(name);
return truth(`cookie ${name}`, 'to be set').assert(res);
}
/**
* {{> ../webapi/dontSeeCookie}}
* Appium: support only web testing
*/
async dontSeeCookie(name) {
const res = await this.browser.getCookie(name);
return truth(`cookie ${name}`, 'to be set').negate(res);
}
/**
* {{> ../webapi/grabCookie}}
* Appium: support only web testing
*/
async grabCookie(name) {
return this.browser.getCookie(name);
}
/**
* Accepts the active JavaScript native popup window, as created by window.alert|window.confirm|window.prompt.
* Don't confuse popups with modal windows, as created by [various
* libraries](http://jster.net/category/windows-modals-popups). Appium: support only web testing
*/
async acceptPopup() {
return this.browser.alertText().then(function (res) {
if (res !== null) {
return this.alertAccept();
}
});
}
/**
* Dismisses the active JavaScript popup, as created by window.alert|window.confirm|window.prompt.
* Appium: support only web testing
*/
async cancelPopup() {
return this.browser.alertText().then(function (res) {
if (res !== null) {
return this.alertDismiss();
}
});
}
/**
* Checks that the active JavaScript popup, as created by `window.alert|window.confirm|window.prompt`, contains the
* given string. Appium: support only web testing
*/
async seeInPopup(text) {
return this.browser.alertText().then((res) => {
if (res === null) {
throw new Error('Popup is not opened');
}
stringIncludes('text in popup').assert(text, res);
});
}
/**
* Grab the text within the popup. If no popup is visible then it will return null
*
* ```js
* await I.grabPopupText();
* ```
*/
async grabPopupText() {
return this.browser.alertText()
.catch(() => null); // Don't throw an error
}
/**
* {{> ../webapi/pressKey }}
*
* To make combinations with modifier and mouse clicks (like Ctrl+Click) press a modifier, click, then release it.
* Appium: support, but clear field before pressing in apps:
*
* ```js
* I.pressKey('Control');
* I.click('#someelement');
* I.pressKey('Control');
* ```
*
*/
async pressKey(key) {
let modifier;
const modifiers = ['Control', 'Command', 'Shift', 'Alt'];
if (Array.isArray(key) && modifiers.indexOf(key[0]) > -1) {
modifier = key[0];
}
await this.browser.keys(key);
if (!modifier) return true;
return this.browser.keys(modifier); // release modifier
}
/**
* {{> ../webapi/resizeWindow }}
* Appium: not tested in web, in apps doesn't work
*/
async resizeWindow(width, height) {
if (width !== 'maximize') {
return this.browser.windowHandleSize({ width, height });
}
const { screenWidth, screenHeight } = await this.browser.execute(function() {
return {
screenHeight: window.screen.height,
screenWidth: window.screen.width
};
}).then((res) => res.value);
return this.browser.windowHandleSize({ width: screenWidth, height: screenHeight });
}
/**
* Drag an item to a destination element.
* Appium: not tested
*
* ```js
* I.dragAndDrop('#dragHandle', '#container');
* ```
*/
async dragAndDrop(srcElement, destElement) {
const client = this.browser;
if (client.isMobile) {
let res = await this._locate(withStrictLocator.call(this, srcElement), true);
assertElementExists(res, srcElement);
let elem = res.value[0];
let location = await this.browser.elementIdLocation(elem.ELEMENT);
assertElementExists(location, `Failed to receive (${srcElement}) location`);
res = await this.browser.touchDown(location.value.x, location.value.y);
if (res.state !== 'success') throw new Error(`Failed to touch button down on (${srcElement})`);
res = await this._locate(withStrictLocator.call(this, destElement), true);
assertElementExists(res, destElement);
elem = res.value[0];
location = await this.browser.elementIdLocation(elem.ELEMENT);
assertElementExists(location, `Failed to receive (${destElement}) location`);
res = await this.browser.touchMove(location.value.x, location.value.y);
if (res.state !== 'success') throw new Error(`Failed to touch move to (${destElement})`);
return this.browser.touchUp(location.value.x, location.value.y);
}
let res = await this.moveCursorTo(withStrictLocator.call(this, srcElement));
if (res.state !== 'success') throw new Error(`Unable to move cursor to (${srcElement})`);
res = await this.browser.buttonDown();
if (res.state !== 'success') throw new Error(`Failed to press button down on (${srcElement})`);
res = await this.moveCursorTo(withStrictLocator.call(this, destElement));
if (res.state !== 'success') throw new Error(`Unable to move cursor to (${destElement})`);
return this.browser.buttonUp();
}
/**
* Close all tabs expect for one.
* Appium: support web test
*
* ```js
* I.closeOtherTabs();
* ```
*/
async closeOtherTabs() {
const client = this.browser;
const handles = await this.browser.getTabIds();
const mainHandle = handles[0];
let p = Promise.resolve();
handles.shift();
handles.forEach((handle) => {
p = p.then(() => client.switchTab(handle).then(() => client.close(mainHandle)));
});
return p;
}
/**
* {{> ../webapi/wait }}
* Appium: support
*/
async wait(sec) {
return this.browser.pause(sec * 1000);
}
/**
* {{> ../webapi/waitForEnabled }}
* Appium: support
*/
async waitForEnabled(locator, sec = null) {
const client = this.browser;
const aSec = sec || this.options.waitForTimeout;
return client.waitUntil(async () => {
const res = await this.browser.elements(withStrictLocator.call(this, locator));
if (!res.value || res.value.length === 0) {
return false;
}
const commands = [];
res.value.forEach(el => commands.push(client.elementIdEnabled(el.ELEMENT)));
const selected = await client.unify(commands, { extractValue: true });
if (Array.isArray(selected)) {
return selected.filter(val => val === true).length > 0;
}
return selected;
}, aSec * 1000, `element (${locator}) still not enabled after ${aSec} sec`);
}
/**
* {{> ../webapi/waitForElement }}
* Appium: support
*/
async waitForElement(locator, sec = null) {
const aSec = sec || this.options.waitForTimeout;
return this.browser.waitUntil(async () => {
const res = await this.browser.elements(withStrictLocator.call(this, locator));
return res.value && res.value.length;
}, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
}
/**
* {{> ../webapi/waitUntilExists }}
* Appium: support
*/
async waitUntilExists(locator, sec = null) {
return this.waitForStalenessOf(locator, sec);
}
/**
* Waiting for the part of the URL to match the expected. Useful for SPA to understand that page was changed.
*
* ```js
* I.waitInUrl('/info', 2);
* ```
*/
async waitInUrl(urlPart, sec = null) {
const client = this.browser;
const aSec = sec || this.options.waitForTimeout;
let currUrl = '';
return client
.waitUntil(function () {
return this.url().then((res) => {
currUrl = decodeUrl(res.value);
return currUrl.indexOf(urlPart) > -1;
});
}, aSec * 1000).catch((e) => {
if (e.type === 'WaitUntilTimeoutError') {
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
} else {
throw e;
}
});
}
/**
* Waits for the entire URL to match the expected
*
* ```js
* I.waitUrlEquals('/info', 2);
* I.waitUrlEquals('http://127.0.0.1:8000/info');
* ```
*/
async waitUrlEquals(urlPart, sec = null) {
const aSec = sec || this.options.waitForTimeout;
const baseUrl = this.options.url;
if (urlPart.indexOf('http') < 0) {
urlPart = baseUrl + urlPart;
}
let currUrl = '';
return this.browser.waitUntil(function () {
return this.url().then((res) => {
currUrl = decodeUrl(res.value);
return currUrl === urlPart;
});
}, aSec * 1000).catch((e) => {
if (e.type === 'WaitUntilTimeoutError') {
throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`);
} else {
throw e;
}
});
}
/**
* {{> ../webapi/waitForText }}
* Appium: support
*/
async waitForText(text, sec = null, aContext = null) {
const aSec = sec || this.options.waitForTimeout;
const context = aContext || this.root;
return this.browser.waitUntil(
async () => {
const res = await this.browser.elements(withStrictLocator.call(this, context));
if (!res.value || res.value.length === 0) return false;
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdText(el.ELEMENT)));
const selected = await this.browser.unify(commands, { extractValue: true });
if (Array.isArray(selected)) {
return selected.filter(part => part.indexOf(text) >= 0).length > 0;
}
return selected.indexOf(text) >= 0;
}, aSec * 1000,
`element (${context}) is not in DOM or there is no element(${context}) with text "${text}" after ${aSec} sec`,
);
}
/**
* Waits for the specified value to be in value attribute
*
* ```js
* I.waitForValue('//input', "GoodValue");
* ```
*
* @param field input field
* @param value expected value
* @param sec seconds to wait, 1 sec by default
*/
async waitForValue(field, value, sec = null) {
const client = this.browser;
const aSec = sec || this.options.waitForTimeout;
return client.waitUntil(
async () => {
const res = await findFields.call(this, field);
if (!res.value || res.value.length === 0) return false;
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdAttribute(el.ELEMENT, 'value')));
const selected = await this.browser.unify(commands, { extractValue: true });
if (Array.isArray(selected)) {
return selected.filter(part => part.indexOf(value) >= 0).length > 0;
}
return selected.indexOf(value) >= 0;
}, aSec * 1000,
`element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
);
}
/**
* {{> ../webapi/waitForVisible }}
* Appium: support
*/
async waitForVisible(locator, sec = null) {
const aSec = sec || this.options.waitForTimeout;
return this.browser.waitUntil(async () => {
const res = await this.browser.elements(withStrictLocator.call(this, locator));
if (!res.value || res.value.length === 0) return false;
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdDisplayed(el.ELEMENT)));
const selected = await this.browser.unify(commands, { extractValue: true });
if (Array.isArray(selected)) {
return selected.filter(val => val === true).length > 0;
}
return selected;
}, aSec * 1000, `element (${locator}) still not visible after ${aSec} sec`);
}
/**
* Waits for a specified number of elements on the page
*
* ```js
* I.waitNumberOfVisibleElements('a', 3);
* ```
*/
async waitNumberOfVisibleElements(locator, num, sec = null) {
const aSec = sec || this.options.waitForTimeout;
return this.browser.waitUntil(async () => {
const res = await this.browser.elements(withStrictLocator.call(this, locator));
if (!res.value || res.value.length === 0) return false;
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdDisplayed(el.ELEMENT)));
let selected = await this.browser.unify(commands, { extractValue: true });
if (!Array.isArray(selected)) selected = [selected];
return selected.length === num;
}, aSec * 1000, `The number of elements ${locator} is not ${num} after ${aSec} sec`);
}
/**
* {{> ../webapi/waitForInvisible }}
* Appium: support
*/
async waitForInvisible(locator, sec = null) {
const aSec = sec || this.options.waitForTimeout;
return this.browser.waitUntil(async () => {
const res = await this.browser.elements(withStrictLocator.call(this, locator));
if (!res.value || res.value.length === 0) return false;
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdDisplayed(el.ELEMENT)));
const selected = await this.browser.unify(commands, { extractValue: true });
if (Array.isArray(selected)) {
return selected.filter(val => val === false).length > 0;
}
return !selected;
}, aSec * 1000, `element (${locator}) still visible after ${aSec} sec`);
}
/**
* {{> ../webapi/waitToHide }}
* Appium: support
*/
async waitToHide(locator, sec = null) {
return this.waitForInvisible(locator, sec);
}
/**
* {{> ../webapi/waitForStalenessOf }}
* Appium: support
*/
async waitForStalenessOf(locator, sec = null) {
const aSec = sec || this.options.waitForTimeout;
return this.browser.waitUntil(async () => {
const res = await this.browser.elements(withStrictLocator.call(this, locator));
if (!res.value || res.value.length === 0) {
return true;
}
return false;
}, aSec * 1000, `element (${locator}) still attached to the DOM after ${aSec} sec`);
}
/**
* {{> ../webapi/waitUntil }}
* Appium: support
*/
async waitUntil(fn, sec = null, timeoutMsg = null) {
const aSec = sec || this.options.waitForTimeout;
return this.browser.waitUntil(fn, aSec, timeoutMsg);
}
/**
* Switches frame or in case of null locator reverts to parent.
* Appium: support only web testing
*/
async switchTo(locator) {
if (!locator) {
return this.browser.frame(null);
} else if (Number.isInteger(locator)) {
return this.browser.frame(locator);
}
const res = await this._locate(withStrictLocator.call(this, locator), true);
assertElementExists(res, locator);
return this.browser.frame(res.value[0]);
}
/**
* Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
*
* ```js
* I.switchToNextTab();
* I.switchToNextTab(2);
* ```
*/
async switchToNextTab(num = 1, sec = null) {
const aSec = sec || this.options.waitForTimeout;
const client = this.browser;
return client
.waitUntil(function () {
return this.getTabIds().then(function (handles) {
return this.getCurrentTabId().then(function (current) {
if (handles.indexOf(current) + num + 1 <= handles.length) {
return this.switchTab(handles[handles.indexOf(current) + num]);
} return false;
});
});
}, aSec * 1000, `There is no ability to switch to next tab with offset ${num}`);
}
/**
* Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
*
* ```js
* I.switchToPreviousTab();
* I.switchToPreviousTab(2);
* ```
*/
async switchToPreviousTab(num = 1, sec = null) {
const aSec = sec || this.options.waitForTimeout;
const client = this.browser;
return client
.waitUntil(function () {
return this.getTabIds().then(function (handles) {
return this.getCurrentTabId().then(function (current) {
if (handles.indexOf(current) - num > -1) return this.switchTab(handles[handles.indexOf(current) - num]);
return false;
});
});
}, aSec * 1000, `There is no ability to switch to previous tab with offset ${num}`);
}
/**
* Close current tab
*
* ```js
* I.closeCurrentTab();
* ```
*/
async closeCurrentTab() {
const client = this.browser;
return client.close();
}
/**
* Open new tab and switch to it
*
* ```js
* I.openNewTab();
* ```
*/
async openNewTab() {
const client = this.browser;
return client.newWindow('about:blank');
}
/**
* {{> ../webapi/refreshPage }}
*/
async refreshPage() {
const client = this.browser;
return client.refresh();
}
/**
* Scroll page to the top
*
* ```js
* I.scrollPageToTop();
* ```
*/
scrollPageToTop() {
const client = this.browser;
return client.execute(() => {
window.scrollTo(0, 0);
});
}
/**
* Scroll page to the bottom
*
* ```js
* I.scrollPageToBottom();
* ```
*/
scrollPageToBottom() {
const client = this.browser;
return client.execute(() => {
const body = document.body;
const html = document.documentElement;
window.scrollTo(0, Math.max(
body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight,
));
});
}
/**
* placeholder for ~ locator only test case write once run on both Appium and WebDriverIO
*/
runOnIOS(caps, fn) {
}
/**
* placeholder for ~ locator only test case write once run on both Appium and WebDriverIO
*/
runOnAndroid(caps, fn) {
}
/**
* placeholder for ~ locator only test case write once run on both Appium and WebDriverIO
*/
runInWeb(fn) {
return fn();
}
}
async function proceedSee(assertType, text, context, strict = false) {
let description;
if (!context) {
if (this.context === webRoot) {
context = this.context;
description = 'web page';
} else {
description = `current context ${this.context}`;
context = './/*';
}
} else {
description = `element ${context}`;
}
const smartWaitEnabled = assertType === 'assert';
const res = await this._locate(withStrictLocator.call(this, context), smartWaitEnabled);
if (!res.value || res.value.length === 0) throw new ElementNotFound(context);
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdText(el.ELEMENT)));
const selected = await this.browser.unify(commands, { extractValue: true });
if (strict) return equals(description)[assertType](text, selected);
return stringIncludes(description)[assertType](text, selected);
}
async function findClickable(locator, locateFn) {
locator = new Locator(locator);
if (!locator.isFuzzy()) return locateFn(locator.simplify(), true);
if (locator.isAccessibilityId()) return locateFn(withAccessiblitiyLocator.call(this, locator.value), true);
let els;
const literal = xpathLocator.literal(locator.value);
els = await locateFn(Locator.clickable.narrow(literal));
if (els.value.length) return els;
els = await locateFn(Locator.clickable.wide(literal));
if (els.value.length) return els;
els = await locateFn(Locator.clickable.self(literal));
if (els.value.length) return els;
return locateFn(locator.value); // by css or xpath
}
async function findFields(locator) {
locator = new Locator(locator);
if (!locator.isFuzzy()) return this._locate(locator.simplify(), true);
if (locator.isAccessibilityId()) return this._locate(withAccessiblitiyLocator.call(this, locator.value), true);
const literal = xpathLocator.literal(locator.value);
let els = await this._locate(Locator.field.byText(literal));
if (els.value.length) return els;
els = await this._locate(Locator.field.byName(literal));
if (els.value.length) return els;
return this._locate(locator.value); // by css or xpath
}
async function proceedSeeField(assertType, field, value) {
const res = await findFields.call(this, field);
assertElementExists(res, field, 'Field');
const proceedMultiple = (fields) => {
let commands = [];
fields.forEach(el => commands.push(this.browser.elementIdSelected(el.ELEMENT)));
this.browser.unify(commands).then(() => {
commands = [];
fields.forEach((el) => {
if (el.value === false) return;
commands.push(this.browser.elementIdAttribute(el.ELEMENT, 'value'));
});
return this.browser.unify(commands, {
extractValue: true,
}).then(val => stringIncludes(`fields by ${field}`)[assertType](value, val));
});
};
const proceedSingle = el => this.browser.elementIdAttribute(el.ELEMENT, 'value').then(res => stringIncludes(`fields by ${field}`)[assertType](value, res.value));
const tag = await this.browser.elementIdName(res.value[0].ELEMENT);
if (tag.value === 'select') {
return proceedMultiple(res.value);
}
if (tag.value === 'input') {
return this.browser.elementIdAttribute(res.value[0].ELEMENT, 'type').then((type) => {
if (type.value === 'checkbox' || type.value === 'radio') {
return proceedMultiple(res.value);
}
return proceedSingle(res.value[0]);
});
}
return proceedSingle(res.value[0]);
}
async function proceedSeeCheckbox(assertType, field) {
const res = await findFields.call(this, field);
assertElementExists(res, field, 'Field');
const commands = [];
res.value.forEach(el => commands.push(this.browser.elementIdSelected(el.ELEMENT)));
const selected = await this.browser.unify(commands, { extractValue: true });
return truth(`checkable field ${field}`, 'to be checked')[assertType](selected);
}
async function findCheckable(locator, locateFn) {
let els;
locator = new Locator(locator);
if (!locator.isFuzzy()) return locateFn(locator.simplify(), true);
if (locator.isAccessibilityId()) return locateFn(withAccessiblitiyLocator.call(this, locator.value), true);
const literal = xpathLocator.literal(locator.value);
els = await locateFn(Locator.checkable.byText(literal));
if (els.value.length) return els;
els = await locateFn(Locator.checkable.byName(literal));
if (els.value.length) return els;
return locateFn(locator.value); // by css or xpath
}
function withStrictLocator(locator) {
locator = new Locator(locator);
if (locator.isAccessibilityId()) return withAccessiblitiyLocator.call(this, locator.value);
return locator.simplify();
}
function isFrameLocator(locator) {
locator = new Locator(locator);
if (locator.isFrame()) return locator.value;
return false;
}
function withAccessiblitiyLocator(locator) {
if (this.isWeb === false) {
return `accessibility id:${locator.slice(1)}`;
}
return `[aria-label="${locator.slice(1)}"]`;
// hook before webdriverio supports native ~ locators in web
}
function assertElementExists(res, locator, prefix, suffix) {
if (!res.value || res.value.length === 0) {
throw new ElementNotFound(locator, prefix, suffix);
}
}
function prepareLocateFn(context) {
if (!context) return this._locate.bind(this);
let el;
return (l) => {
if (el) return this.browser.elementIdElements(el, l);
return this._locate(context, true).then((res) => {
assertElementExists(res, context, 'Context element');
return this.browser.elementIdElements(el = res.value[0].ELEMENT, l);
});
};
}
module.exports = WebDriverIO;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment