Skip to content

Commit

Permalink
wip: Upgrade to webdriverio 9
Browse files Browse the repository at this point in the history
  • Loading branch information
gethinwebster committed Aug 23, 2024
1 parent 7848e3d commit 4ba1199
Show file tree
Hide file tree
Showing 16 changed files with 563 additions and 585 deletions.
955 changes: 458 additions & 497 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@
"p-retry": "^4.6.2",
"pixelmatch": "^5.3.0",
"pngjs": "^6.0.0",
"puppeteer-core": "^22.15.0",
"wait-on": "^7.2.0",
"webdriverio": "^8.40.3"
"webdriverio": "^9.0.7"
},
"devDependencies": {
"@types/debug": "^4.1.12",
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,7 +14,7 @@ export interface WebDriverOptions {
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
}

Expand Down Expand Up @@ -52,7 +53,8 @@ export default abstract class BrowserCreator {
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 });
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
136 changes: 71 additions & 65 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 { ElementRect } from './types';
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 @@ export default class BasePageObject {
}

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 @@ export default class BasePageObject {
}

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) {
const element = await this.browser.$(selector);
const element = await this.$(selector);
await element.setValue(value);
}

Expand Down Expand Up @@ -164,41 +170,41 @@ export default class BasePageObject {
}

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,12 +224,12 @@ export default class BasePageObject {
}

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

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

/**
Expand All @@ -245,8 +251,8 @@ export default class BasePageObject {
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 { getViewportSize, windowScrollTo } from '../browser-scripts';
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 @@ async function checkDocumentSize(browser: WebdriverIO.Browser) {
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 @@ export async function scrollAndMergeStrategy(browser: WebdriverIO.Browser) {
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 @@ export default async function fullPageScreenshot(browser: WebdriverIO.Browser, f
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
3 changes: 2 additions & 1 deletion src/page-objects/screenshot.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { Browser } from 'webdriverio';
import { scrollToBottom, scrollToRight } from '../browser-scripts';
import { parsePng } from '../image-utils';
import BasePageObject from './base';
import { ElementOffset, ScreenshotCapturingOptions, ScreenshotWithOffset } from './types';
import fullPageScreenshot from './full-page-screenshot';

export default class ScreenshotPageObject extends BasePageObject {
constructor(browser: WebdriverIO.Browser, public readonly forceScrollAndMerge: boolean = false) {
constructor(browser: Browser, public readonly forceScrollAndMerge: boolean = false) {
super(browser);
}

Expand Down
3 changes: 2 additions & 1 deletion src/page-objects/utils.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 dns from 'dns';
import { Browser } from 'webdriverio';
import { WebdriverIoUnsupportedPuppeteerErrorText, WebdriverIoRemotePuppeteerErrorText } from '../exceptions';
import { ViewportSize, ElementRect } from './types';

Expand Down Expand Up @@ -63,7 +64,7 @@ export function calculateIosTopOffset(
return statusBarHeight;
}

export async function getPuppeteer(browser: WebdriverIO.Browser) {
export async function getPuppeteer(browser: Browser) {
// Make sure to favor IPv4 name resolution over IPv6 so that the localhost debugger can be found.
dns.setDefaultResultOrder('ipv4first');

Expand Down
Loading

0 comments on commit 4ba1199

Please sign in to comment.