Skip to content

Commit

Permalink
feat: wdio with devtools protocol (#4105)
Browse files Browse the repository at this point in the history
  • Loading branch information
kobenguyent authored Jan 12, 2024
1 parent 2bfe7d1 commit b42190f
Show file tree
Hide file tree
Showing 7 changed files with 1,381 additions and 32 deletions.
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",
"test:plugin": "mocha test/plugin/plugin_test.js",
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

0 comments on commit b42190f

Please sign in to comment.