Skip to content

feat: wdio with devtools protocol #4105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/webdriver.devtools.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: WebDriver - Devtools Tests

on:
push:
branches:
- 3.x
pull_request:
branches:
- '**'

env:
CI: true
# Force terminal colors. @see https://www.npmjs.com/package/colors
FORCE_COLOR: 1

jobs:
build:

runs-on: ubuntu-20.04
strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
- name: npm install
run: |
npm install --legacy-peer-deps
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
- name: start a server
run: "php -S 127.0.0.1:8000 -t test/data/app &"
- name: run unit tests
run: ./node_modules/.bin/mocha test/helper/WebDriver_devtools_test.js --exit
- name: run tests
run: "./bin/codecept.js run -c test/acceptance/codecept.WebDriver.devtools.js --grep @WebDriver --debug"

24 changes: 23 additions & 1 deletion docs/helpers/WebDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Type: [object][16]
- `manualStart` **[boolean][32]?** do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
- `timeouts` **[object][16]?** [WebDriver timeouts][37] defined as hash.
- `highlightElement` **[boolean][32]?** highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
- `devtoolsProtocol` **[boolean][32]?** enable devtools protocol. Default: false. More info: [https://webdriver.io/docs/automationProtocols/#devtools-protocol][38].



Expand Down Expand Up @@ -109,6 +110,25 @@ website][3].
}
```

### Running with devtools protocol

```js
{
helpers: {
WebDriver : {
url: "http://localhost",
browser: "chrome",
devtoolsProtocol: true,
desiredCapabilities: {
chromeOptions: {
args: [ "--headless", "--disable-gpu", "--no-sandbox" ]
}
}
}
}
}
```

### Internet Explorer

Additional configuration params can be used from [IE options][4]
Expand Down Expand Up @@ -2033,7 +2053,7 @@ I.setGeoLocation(121.21, 11.56, 10);

- `latitude` **[number][22]** to set.
- `longitude` **[number][22]** to set
- `altitude` **[number][22]?** (optional, null by default) to set
- `altitude` **[number][22]?** (optional, null by default) to set

Returns **void** automatically synchronized promise through #recorder

Expand Down Expand Up @@ -2475,3 +2495,5 @@ Returns **void** automatically synchronized promise through #recorder
[36]: http://codecept.io/acceptance/#smartwait

[37]: http://webdriver.io/docs/timeouts.html

[38]: https://webdriver.io/docs/automationProtocols/#devtools-protocol
105 changes: 77 additions & 28 deletions lib/helper/WebDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const webRoot = 'body';
* @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
* @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
* @prop {boolean} [devtoolsProtocol=false] - enable devtools protocol. Default: false. More info: https://webdriver.io/docs/automationProtocols/#devtools-protocol.
*/
const config = {};

Expand Down Expand Up @@ -133,6 +134,25 @@ const config = {};
* }
* ```
*
* ### Running with devtools protocol
*
* ```js
* {
* helpers: {
* WebDriver : {
* url: "http://localhost",
* browser: "chrome",
* devtoolsProtocol: true,
* desiredCapabilities: {
* chromeOptions: {
* args: [ "--headless", "--disable-gpu", "--no-sandbox" ]
* }
* }
* }
* }
* }
* ```
*
* ### Internet Explorer
*
* Additional configuration params can be used from [IE options](https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/IE/Options.html)
Expand Down Expand Up @@ -542,6 +562,10 @@ class WebDriver extends Helper {
delete this.options.capabilities.hostname;
delete this.options.capabilities.port;
delete this.options.capabilities.path;
if (this.options.devtoolsProtocol) {
if (!['chrome', 'chromium'].includes(this.options.browser.toLowerCase())) throw Error('The devtools protocol is only working with Chrome or Chromium');
this.options.automationProtocol = 'devtools';
}
this.browser = await webdriverio.remote(this.options);
}
} catch (err) {
Expand Down Expand Up @@ -1043,7 +1067,8 @@ class WebDriver extends Helper {
assertElementExists(res, field, 'Field');
const elem = usingFirstElement(res);
highlightActiveElement.call(this, elem);
return elem.setValue(value.toString());
await elem.clearValue();
await elem.setValue(value.toString());
}

/**
Expand All @@ -1055,6 +1080,10 @@ class WebDriver extends Helper {
assertElementExists(res, field, 'Field');
const elem = usingFirstElement(res);
highlightActiveElement.call(this, elem);
if (this.options.automationProtocol) {
const curentValue = await elem.getValue();
return elem.setValue(curentValue + value.toString());
}
return elem.addValue(value.toString());
}

Expand All @@ -1067,6 +1096,9 @@ class WebDriver extends Helper {
assertElementExists(res, field, 'Field');
const elem = usingFirstElement(res);
highlightActiveElement.call(this, elem);
if (this.options.automationProtocol) {
return elem.setValue('');
}
return elem.clearValue(getElementId(elem));
}

Expand Down Expand Up @@ -1120,7 +1152,7 @@ class WebDriver extends Helper {
const el = usingFirstElement(res);

// Remote Upload (when running Selenium Server)
if (this.options.remoteFileUpload) {
if (this.options.remoteFileUpload && !this.options.automationProtocol) {
try {
this.debugSection('File', 'Uploading file to remote server');
file = await this.browser.uploadFile(file);
Expand Down Expand Up @@ -1498,35 +1530,33 @@ class WebDriver extends Helper {
async seeCssPropertiesOnElements(locator, cssProperties) {
const res = await this._locate(locator);
assertElementExists(res, locator);

const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
const elemAmount = res.length;
let props = [];

let props = await forEachAsync(res, async (el) => {
return forEachAsync(Object.keys(cssProperties), async (prop) => {
const propValue = await this.browser.getElementCSSValue(getElementId(el), prop);
if (isColorProperty(prop) && propValue && propValue.value) {
return convertColorToRGBA(propValue.value);
for (const element of res) {
for (const prop of Object.keys(cssProperties)) {
const cssProp = await this.grabCssPropertyFrom(locator, prop);
if (isColorProperty(prop)) {
props.push(convertColorToRGBA(cssProp));
} else {
props.push(cssProp);
}
return propValue;
});
});

const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
}
}

const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[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) {
const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
if (_acutal !== _expected) return false;
// eslint-disable-next-line eqeqeq
if (val[i] != values[i]) return false;
}
return true;
});
return assert.ok(
chunked.length === elemAmount,
`expected all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`,
);
return equals(`all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount);
}

/**
Expand All @@ -1546,9 +1576,9 @@ class WebDriver extends Helper {
let chunked = chunkArray(attrs, values.length);
chunked = chunked.filter((val) => {
for (let i = 0; i < val.length; ++i) {
const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
if (_acutal !== _expected) return false;
if (_actual !== _expected) return false;
}
return true;
});
Expand Down Expand Up @@ -1925,7 +1955,7 @@ class WebDriver extends Helper {
* {{> resizeWindow }}
*/
async resizeWindow(width, height) {
return this._resizeBrowserWindow(this.browser, width, height);
return this.browser.setWindowSize(width, height);
}

async _resizeBrowserWindow(browser, width, height) {
Expand Down Expand Up @@ -2312,6 +2342,9 @@ class WebDriver extends Helper {
async switchTo(locator) {
this.browser.isInsideFrame = true;
if (Number.isInteger(locator)) {
if (this.options.automationProtocol) {
return this.browser.switchToFrame(locator + 1);
}
return this.browser.switchToFrame(locator);
}
if (!locator) {
Expand Down Expand Up @@ -2453,9 +2486,19 @@ class WebDriver extends Helper {
*
* {{> setGeoLocation }}
*/
async setGeoLocation(latitude, longitude, altitude = null) {
console.log(`setGeoLocation deprecated:
* This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#setgeolocation`);
async setGeoLocation(latitude, longitude) {
if (!this.options.automationProtocol) {
console.log(`setGeoLocation deprecated:
* This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#setgeolocation
* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
return;
}
this.geoLocation = { latitude, longitude };
const puppeteerBrowser = await this.browser.getPuppeteer();
await this.browser.call(async () => {
const pages = await puppeteerBrowser.pages();
await pages[0].setGeolocation({ latitude, longitude });
});
}

/**
Expand All @@ -2465,8 +2508,14 @@ class WebDriver extends Helper {
*
*/
async grabGeoLocation() {
console.log(`grabGeoLocation deprecated:
* This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#getgeolocation`);
if (!this.options.automationProtocol) {
console.log(`grabGeoLocation deprecated:
* This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#getgeolocation
* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
return;
}
if (!this.geoLocation) return 'No GeoLocation is set!';
return this.geoLocation;
}

/**
Expand Down Expand Up @@ -2662,7 +2711,7 @@ async function proceedSeeField(assertType, field, value) {
}
};

const proceedSingle = el => this.browser.getElementAttribute(getElementId(el), 'value').then((res) => {
const proceedSingle = el => el.getValue().then((res) => {
if (res === null) {
throw new Error(`Element ${el.selector} has no value attribute`);
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"test:unit:webbapi:playwright": "mocha test/helper/Playwright_test.js",
"test:unit:webbapi:puppeteer": "mocha test/helper/Puppeteer_test.js",
"test:unit:webbapi:webDriver": "mocha test/helper/WebDriver_test.js",
"test:unit:webbapi:webDriver:devtools": "mocha test/helper/WebDriver_devtools_test.js --exit",
"test:unit:webbapi:testCafe": "mocha test/helper/TestCafe_test.js",
"test:unit:expect": "mocha test/helper/Expect_test.js",
"def": "./runok.js def",
Expand Down
41 changes: 41 additions & 0 deletions test/acceptance/codecept.WebDriver.devtools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const TestHelper = require('../support/TestHelper');

module.exports.config = {
tests: './*_test.js',
timeout: 10000,
output: './output',
helpers: {
WebDriver: {
url: TestHelper.siteUrl(),
browser: 'Chromium',
windowSize: '500x700',
devtoolsProtocol: true,
waitForTimeout: 5000,
capabilities: {
chromeOptions: {
args: ['--headless', '--disable-gpu', '--window-size=500,700'],
},
},
},
ScreenshotSessionHelper: {
require: '../support/ScreenshotSessionHelper.js',
outputPath: './output',
},
Expect: {},
},
include: {},
bootstrap: async () => new Promise(done => {
setTimeout(done, 5000);
}), // let's wait for selenium
mocha: {},
name: 'acceptance',
plugins: {
screenshotOnFail: {
enabled: true,
},
},
gherkin: {
features: './gherkin/*.feature',
steps: ['./gherkin/steps.js'],
},
};
Loading