Skip to content
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

wip: Upgrade to webdriverio 9 #100

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
12,472 changes: 3,727 additions & 8,745 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@
"p-retry": "^4.6.2",
"pixelmatch": "^5.3.0",
"pngjs": "^6.0.0",
"puppeteer-core": "^22.15.0",
"wait-on": "^7.2.0",
"webdriverio": "^7.25.2"
"webdriverio": "^9.0.7"
},
"devDependencies": {
"@types/debug": "^4.1.12",
"@types/lodash": "^4.14.186",
"@types/node": "^18.0.0",
"@types/pixelmatch": "^5.2.4",
Expand All @@ -56,8 +58,9 @@
"lint-staged": "^13.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"typescript": "^4.9.5",
"vitest": "^1.5.3"
"typescript": "^5.4.5",
"vitest": "^1.5.3",
"wait-on": "^7.2.0"
},
"exports": {
"./chrome-launcher": "./chrome-launcher.js",
Expand Down
8 changes: 5 additions & 3 deletions src/browsers/browser-creator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { remote, RemoteOptions } from 'webdriverio';
import { RemoteConfig } from 'webdriver';
import { remote } from 'webdriverio';
import merge from 'lodash/merge';

import { BrowserError } from '../exceptions';
Expand All @@ -13,8 +14,8 @@
implicitTimeout: number;
scriptTimeout: number;
baseUrl?: string;
logLevel: RemoteOptions['logLevel'];
logLevel: RemoteConfig['logLevel'];
capabilities?: Record<string, any>;

Check warning on line 18 in src/browsers/browser-creator.ts

View workflow job for this annotation

GitHub Actions / build / build

Unexpected any. Specify a different type
}

const defaultOptions: WebDriverOptions = {
Expand All @@ -27,7 +28,7 @@
};

export default abstract class BrowserCreator {
constructor(protected browserName: string, protected options: Record<string, any>) {}

Check warning on line 31 in src/browsers/browser-creator.ts

View workflow job for this annotation

GitHub Actions / build / build

Unexpected any. Specify a different type

protected async setupBrowser(overrides: Partial<WebDriverOptions>) {
const options = merge({}, defaultOptions, overrides);
Expand All @@ -52,7 +53,8 @@
await browser.setTimeout({ implicit: options.implicitTimeout, script: options.scriptTimeout });

if (!browser.isMobile) {
await browser.$('body').then(body => body.moveTo({ xOffset: 0, yOffset: 0 }));
const body = await browser.$('//body');
body.moveTo({ xOffset: 0, yOffset: 0 });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the return of moveTo? Is it a promise?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah good catch! webdriverio 9 does a lot more promise chaining magic under the hood, so it might be that it handles this anyway, but makes sense to make it more explicit

await browser.setWindowSize(options.width, options.height);
}

Expand Down
3 changes: 2 additions & 1 deletion src/browsers/browserstack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { URL } from 'url';
import pRetry, { AbortError } from 'p-retry';
import { Browser } from 'webdriverio';
import BrowserCreator, { WebDriverOptions } from './browser-creator';
import { BrowserStackFullQueueErrorText } from '../exceptions';
import { Capabilities, getCapability } from './capabilities';
Expand Down Expand Up @@ -86,7 +87,7 @@ export type BrowserstackOptions = {
buildName: string;
};
export default class BrowserStackBrowserCreator extends BrowserCreator {
async getBrowser(options: Partial<WebDriverOptions>): Promise<WebdriverIO.Browser> {
async getBrowser(options: Partial<WebDriverOptions>): Promise<Browser> {
const nSecSleepBeforeRetry = this.options.nSecSleepBeforeRetry || N_SEC_SLEEP_BEFORE_RETRY;

return pRetry(
Expand Down
4 changes: 2 additions & 2 deletions src/browsers/capabilities.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import lodash from 'lodash';
import { RemoteOptions } from 'webdriverio';
import { RemoteConfig } from 'webdriver';
import { FatalError } from '../exceptions';

export type Capabilities = RemoteOptions['capabilities'];
export type Capabilities = RemoteConfig['capabilities'];

const defaultCapabilities: Record<string, Capabilities> = {
Chrome: {
Expand Down
3 changes: 2 additions & 1 deletion src/browsers/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
}),
Firefox: mergeCapabilities(defaultCapabilities.Firefox, {
// https://firefox-source-docs.mozilla.org/testing/geckodriver/Capabilities.html
'moz:debuggerAddress': true,
// https://github.com/webdriverio/webdriverio/issues/13053
'moz:debuggerAddress': true as any,

Check warning on line 27 in src/browsers/local.ts

View workflow job for this annotation

GitHub Actions / build / build

Unexpected any. Specify a different type
}),
};

Expand Down
139 changes: 72 additions & 67 deletions src/page-objects/base.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import pRetry from 'p-retry';
import { Browser } from 'webdriverio';

import {
getElementScrollPosition,
Expand All @@ -19,7 +20,12 @@
import { waitForTimerAndAnimationFrame } from './browser-scripts';

export default class BasePageObject {
constructor(protected browser: WebdriverIO.Browser) {}
constructor(protected browser: Browser) {}

private async $(selector: string) {
//workaround for https://github.com/webdriverio/webdriverio/issues/13440
return this.browser.$(selector.replace(/^body/, '//body'));
}

async pause(milliseconds: number) {
await this.browser.pause(milliseconds);
Expand All @@ -30,7 +36,7 @@
}

async setWindowSize({ width, height }: { width: number; height: number }) {
await this.browser.setWindowSize(width, height);
await this.browser.setViewport({ width, height });
}

async spyOnEvents(selector: string, events: string[]) {
Expand All @@ -40,80 +46,80 @@
}

async click(selector: string) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
await element.click();
}

async hoverElement(selector: string, xOffset?: number, yOffset?: number) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
await element.moveTo({ xOffset, yOffset });
}

async buttonDownOnElement(selector: string) {
// buttonDown exists only in JSON Wire protocol
if (this.browser.buttonDown) {
await this.hoverElement(selector);
await this.browser.buttonDown();
} else {
// Clean up all previous actions before stating a new batch. Without this line Safari emits extra "mouseup" events
await this.browser.releaseActions();
const box = await this.getBoundingBox(selector);
const center = getElementCenter(box);
// W3C alternative is `performActions`. All consecutive actions have to be a part of a single call
await this.browser.performActions([
{
type: 'pointer',
id: 'mouse',
parameters: { pointerType: 'mouse' },
actions: [
{ type: 'pointerMove', duration: 0, x: center.x, y: center.y },
{ type: 'pointerDown', button: 0 },
// extra delay to let event listeners to be fired
{ type: 'pause', duration: 10 },
],
},
]);
}
// if (this.browser.buttonDown) {
// await this.hoverElement(selector);
// await this.browser.buttonDown();
// } else {
// Clean up all previous actions before stating a new batch. Without this line Safari emits extra "mouseup" events
await this.browser.releaseActions();
const box = await this.getBoundingBox(selector);
const center = getElementCenter(box);
// W3C alternative is `performActions`. All consecutive actions have to be a part of a single call
await this.browser.performActions([
{
type: 'pointer',
id: 'mouse',
parameters: { pointerType: 'mouse' },
actions: [
{ type: 'pointerMove', duration: 0, x: center.x, y: center.y },
{ type: 'pointerDown', button: 0 },
// extra delay to let event listeners to be fired
{ type: 'pause', duration: 10 },
],
},

Check warning on line 80 in src/page-objects/base.ts

View check run for this annotation

Codecov / codecov/patch

src/page-objects/base.ts#L78-L80

Added lines #L78 - L80 were not covered by tests
]);
// }
}

async buttonUp() {
// buttonUp exists only in JSON Wire protocol
if (this.browser.buttonUp) {
await this.browser.buttonUp();
} else {
// W3C alternative is `performActions`
await this.browser.performActions([
{
type: 'pointer',
id: 'mouse',
parameters: { pointerType: 'mouse' },
actions: [
{ type: 'pointerUp', button: 0 },

// extra delay for Safari to process the event before moving the cursor away
{ type: 'pause', duration: 10 },
// return cursor back to the corner to avoid hover effects on screenshots
{ type: 'pointerMove', duration: 0, x: 0, y: 0 },
],
},
]);
// make sure all controls are properly released to avoid conflicts with further actions
await this.browser.releaseActions();
}
// if (this.browser.buttonUp) {
// await this.browser.buttonUp();
// } else {
// W3C alternative is `performActions`
await this.browser.performActions([
{
type: 'pointer',
id: 'mouse',
parameters: { pointerType: 'mouse' },
actions: [
{ type: 'pointerUp', button: 0 },

// extra delay for Safari to process the event before moving the cursor away
{ type: 'pause', duration: 10 },
// return cursor back to the corner to avoid hover effects on screenshots
{ type: 'pointerMove', duration: 0, x: 0, y: 0 },
],
},
]);
// make sure all controls are properly released to avoid conflicts with further actions
await this.browser.releaseActions();
// }
}

async dragAndDrop(sourceSelector: string, xOffset = 0, yOffset = 0) {
const element = await this.browser.$(sourceSelector);
const element = await this.$(sourceSelector);
await element.dragAndDrop({ x: xOffset, y: yOffset });
}

async getValue(selector: string) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
return element.getValue();
}

async setValue(selector: string, value: number | string | string[]) {
const element = await this.browser.$(selector);
async setValue(selector: string, value: number | string) {
const element = await this.$(selector);
await element.setValue(value);
}

Expand Down Expand Up @@ -164,41 +170,41 @@
}

async isFocused(selector: string) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
return element.isFocused();
}

async isSelected(selector: string) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
return element.isSelected();
}

async isExisting(selector: string) {
const elements = await this.browser.$$(selector);
return elements.length > 0;
const elements = this.browser.$$(selector);
return (await elements.length) > 0;
}

async isDisplayed(selector: string) {
// browser.$ throws an error if element is not found, so we use this method to avoid try/catch
const elements = await this.browser.$$(selector);
if (elements.length === 0) {
const elements = this.browser.$$(selector);
if ((await elements.length) === 0) {

Check warning on line 190 in src/page-objects/base.ts

View check run for this annotation

Codecov / codecov/patch

src/page-objects/base.ts#L190

Added line #L190 was not covered by tests
return false;
}
return elements[0].isDisplayed();
}

async isClickable(selector: string) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
return element.isClickable();
}

async getElementAttribute(selector: string, attributeName: string) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
return element.getAttribute(attributeName);
}

async getElementProperty(selector: string, propertyName: string) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
return element.getProperty(propertyName);
}

Expand All @@ -218,13 +224,12 @@
}

async getText(selector: string) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
return element.getText();
}

async getElementsText(selector: string) {
const elements = await this.browser.$$(selector);
return Promise.all(elements.map(async element => element.getText()));
return this.browser.$$(selector).map(element => element.getHTML());
}

/**
Expand All @@ -246,8 +251,8 @@
if (!shouldSwitch) {
return callback();
}
const iframeEl = await this.browser.$(iframeSelector);
await this.browser.switchToFrame(iframeEl);
const iframeEl = await this.$(iframeSelector);
await this.browser.switchToFrame(await iframeEl.getElement());
await callback();
// go back to top
await this.browser.switchToFrame(null);
Expand Down
3 changes: 2 additions & 1 deletion src/page-objects/events-spy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { Browser } from 'webdriverio';
import { getEvents, initEventsSpy, resetEventsSpy } from '../browser-scripts';

export default class EventsSpy {
constructor(private browser: WebdriverIO.Browser, private selector: string, private events: string[]) {}
constructor(private browser: Browser, private selector: string, private events: string[]) {}

async init() {
await this.browser.execute(initEventsSpy, this.selector, this.events);
Expand Down
11 changes: 6 additions & 5 deletions src/page-objects/full-page-screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import mergeImages from '../image-utils/merge';
import { waitForTimerAndAnimationFrame } from './browser-scripts';
import { calculateIosTopOffset, getPuppeteer } from './utils';
import { Browser } from 'webdriverio';

const MAX_SCREENSHOT_HEIGHT = 16000;

async function scroll(browser: WebdriverIO.Browser, topOffset: number) {
async function scroll(browser: Browser, topOffset: number) {

Check warning on line 12 in src/page-objects/full-page-screenshot.ts

View check run for this annotation

Codecov / codecov/patch

src/page-objects/full-page-screenshot.ts#L12

Added line #L12 was not covered by tests
await browser.execute(windowScrollTo, topOffset, 0);
}

async function checkDocumentSize(browser: WebdriverIO.Browser) {
async function checkDocumentSize(browser: Browser) {
const viewPortSize = await browser.execute(getViewportSize);

if (viewPortSize.pageHeight > MAX_SCREENSHOT_HEIGHT) {
Expand All @@ -23,7 +24,7 @@
return viewPortSize;
}

export async function scrollAndMergeStrategy(browser: WebdriverIO.Browser) {
export async function scrollAndMergeStrategy(browser: Browser) {
const { width, height, pageHeight, screenWidth, screenHeight, pixelRatio } = await checkDocumentSize(browser);
let offset = 0;
const screenshots: string[] = [];
Expand Down Expand Up @@ -51,7 +52,7 @@
return mergeImages(screenshots, width * pixelRatio, height * pixelRatio, lastImageOffset * pixelRatio, offsetTop);
}

export default async function fullPageScreenshot(browser: WebdriverIO.Browser, forceScrollAndMerge: boolean = false) {
export default async function fullPageScreenshot(browser: Browser, forceScrollAndMerge: boolean = false) {
const puppeteer = await getPuppeteer(browser);
if (puppeteer && !forceScrollAndMerge) {
// casting due to mismatch in NodeJS types of EventEmitter
Expand All @@ -61,7 +62,7 @@
return scrollAndMergeStrategy(browser);
}

export async function puppeteerStrategy(browser: WebdriverIO.Browser, puppeteer: PuppeteerBrowser): Promise<string> {
export async function puppeteerStrategy(browser: Browser, puppeteer: PuppeteerBrowser): Promise<string> {
const image = await browser.call(async () => {
// Assuming only one page open
const [current] = await puppeteer.pages();
Expand Down
Loading
Loading