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

feat: ability to send headers when connect to browser using puppeteer #9386

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 10 additions & 9 deletions e2e/element.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,27 +287,28 @@ describe('elements', () => {
})

it('should support deep selectors', async () => {
await browser.navigateTo('https://www.chromestatus.com/feature/5191745052606464')
const headerSlot = await browser.findElement('shadow', '.details__header')
await browser.navigateTo('https://polymer-library.polymer-project.org/3.0/api/elements/array-selector')
await new Promise((resolve) => setTimeout(resolve, 1000))
const headerSlot = await browser.findElement('shadow', 'section[anchor-id="elementBase-properties"]')
expect(
await browser.getElementAttribute(headerSlot[ELEMENT_KEY], 'role')
).toBe('button')
(await browser.getElementText(headerSlot[ELEMENT_KEY])).trim()
).toContain('Properties')
})

it('can fetch shadow elements', async () => {
await browser.navigateTo('https://www.chromestatus.com/feature/5191745052606464')
const element = await browser.findElement('tag name', 'chromedash-toast')
await browser.navigateTo('https://polymer-library.polymer-project.org/3.0/api/elements/array-selector')
await new Promise((resolve) => setTimeout(resolve, 1000))
const element = await browser.findElement('tag name', 'pw-footer')
const shadowRoot = await browser.getElementShadowRoot(
element['element-6066-11e4-a52e-4f735466cecf']
)
console.log(shadowRoot['shadow-6066-11e4-a52e-4f735466cecf'])
const elementRef = await browser.findElementFromShadowRoot(
shadowRoot['shadow-6066-11e4-a52e-4f735466cecf'],
'css selector',
'#msg'
'.copyright'
)
expect(await browser.getElementText(elementRef[ELEMENT_KEY]))
.toBe('Welcome to chromestatus.com!')
.toContain('Brought to you by The Polymer Project')
})
})

Expand Down
16 changes: 14 additions & 2 deletions e2e/wdio/headless/test.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,27 @@ describe('main suite 1', () => {
it('should allow to check for PWA', async () => {
await browser.url('https://webdriver.io')
await browser.pause(100)
expect((await browser.checkPWA()).passed).toBe(true)
expect((await browser.checkPWA([
'isInstallable',
'splashScreen',
'themedOmnibox',
'contentWith',
'viewport',
'appleTouchIcon',
'maskableIcon'
])).passed).toBe(true)
})

it('should also detect non PWAs', async () => {
await browser.url('https://json.org')
expect((await browser.checkPWA()).passed).toBe(false)
})

it('should allow to do performance tests', async () => {
/**
* fails due to "Unable to identify the main resource"
* https://github.com/webdriverio/webdriverio/issues/8541
*/
it.skip('should allow to do performance tests', async () => {
Comment on lines +27 to +31
Copy link
Member

Choose a reason for hiding this comment

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

Is this failing for v7 as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, with the same error. So I found that this test skipped in v8 and skip it as well for v7.

Copy link
Member

Choose a reason for hiding this comment

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

👍

await browser.enablePerformanceAudits()
await browser.url('http://json.org')
const metrics = await browser.getMetrics()
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@wdio/utils": "7.26.0",
"chrome-launcher": "^0.15.0",
"edge-paths": "^2.1.0",
"puppeteer-core": "^13.1.3",
"puppeteer-core": "^19.4.0",
"query-selector-shadow-dom": "^1.0.0",
"ua-parser-js": "^1.0.1",
"uuid": "^9.0.0"
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools/src/commands/elementSendKeys.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'path'
import { UNICODE_CHARACTERS } from '@wdio/utils'
import type { KeyInput } from 'puppeteer-core'
import type { ElementHandle, KeyInput } from 'puppeteer-core'

import { getStaleElementError } from '../utils'
import type DevToolsDriver from '../devtoolsdriver'
Expand All @@ -20,7 +20,7 @@ export default async function elementSendKeys (
this: DevToolsDriver,
{ elementId, text }: { elementId: string, text: string }
) {
const elementHandle = await this.elementStore.get(elementId)
const elementHandle = await this.elementStore.get(elementId) as any as ElementHandle<HTMLInputElement>

if (!elementHandle) {
throw getStaleElementError(elementId)
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools/src/commands/performActions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { keyDefinitions, KeyInput } from 'puppeteer-core/lib/cjs/puppeteer/common/USKeyboardLayout'
import { _keyDefinitions, KeyInput } from 'puppeteer-core/lib/cjs/puppeteer/common/USKeyboardLayout.js'
import type { Keyboard, Mouse } from 'puppeteer-core/lib/cjs/puppeteer/common/Input'

import getElementRect from './getElementRect'
Expand Down Expand Up @@ -81,7 +81,7 @@ export default async function performActions(
* for special characters like emojis 😉 we need to
* send in the value as text because it is not unicode
*/
if (!keyDefinitions[singleAction.value as unknown as KeyInput]) {
if (!_keyDefinitions[singleAction.value as unknown as KeyInput]) {
await page.keyboard.sendCharacter(singleAction.value as unknown as KeyInput)
skipChars.push(singleAction.value)
continue
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools/src/commands/status.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path'

const puppeteerPath = require.resolve('puppeteer-core')
const puppeteerPkg = require(`${path.dirname(puppeteerPath)}/package.json`)
const puppeteerPkg = require(path.join(path.dirname(puppeteerPath), '..', '..', '..', 'package.json'))

/**
* The Status command returns information about whether a remote end is in a state
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools/src/commands/switchToFrame.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/api/Page'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/Frame'
import type { ElementReference } from '@wdio/protocols'

import { ELEMENT_KEY } from '../constants'
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools/src/commands/switchToParentFrame.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/api/Page'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/Frame'
import type DevToolsDriver from '../devtoolsdriver'

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/devtools/src/devtoolsdriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import path from 'path'
import { v4 as uuidv4 } from 'uuid'

import logger from '@wdio/logger'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/api/Browser'
import type { Dialog } from 'puppeteer-core/lib/cjs/puppeteer/common/Dialog'
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page'
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/api/Page'
import type { Target } from 'puppeteer-core/lib/cjs/puppeteer/common/Target'
import type { CommandEndpoint } from '@wdio/protocols'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/Frame'

import ElementStore from './elementstore'
import { validate, sanitizeError } from './utils'
Expand Down
6 changes: 3 additions & 3 deletions packages/devtools/src/elementstore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ElementHandle } from 'puppeteer-core/lib/cjs/puppeteer/common/JSHandle'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import type { ElementHandle } from 'puppeteer-core/lib/cjs/puppeteer/common/ElementHandle'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/Frame'

export default class ElementStore {
private _index = 0
Expand All @@ -9,7 +9,7 @@ export default class ElementStore {
set (elementHandle: ElementHandle) {
const index = `ELEMENT-${++this._index}`
this._elementMap.set(index, elementHandle)
const frame = elementHandle.executionContext().frame()
const frame = elementHandle.executionContext()['_world']?.frame()
if (frame) {
let elementIndexes = this._frameMap.get(frame)
if (!elementIndexes) {
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { webdriverMonad, devtoolsEnvironmentDetector } from '@wdio/utils'
import { validateConfig } from '@wdio/config'
import type { CommandEndpoint } from '@wdio/protocols'
import type { Options, Capabilities } from '@wdio/types'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/api/Browser'

import DevToolsDriver from './devtoolsdriver'
import launch from './launcher'
Expand Down Expand Up @@ -82,7 +82,7 @@ export default class DevTools {
if (vendorCapPrefix) {
Object.assign(params.capabilities, {
[vendorCapPrefix]: Object.assign(
{ debuggerAddress: (browser as any)._connection.url().split('/')[2] },
{ debuggerAddress: browser.wsEndpoint().split('/')[2] },
params.capabilities[vendorCapPrefix]
)
})
Expand Down
14 changes: 7 additions & 7 deletions packages/devtools/src/launcher.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { launch as launchChromeBrowser } from 'chrome-launcher'
import puppeteer, { PuppeteerNodeLaunchOptions } from 'puppeteer-core'
import puppeteer, { PuppeteerNodeLaunchOptions, KnownDevices, Puppeteer, ConnectOptions } from 'puppeteer-core'
import logger from '@wdio/logger'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/api/Browser'
import type { Capabilities } from '@wdio/types'
import { QueryHandler } from 'query-selector-shadow-dom/plugins/puppeteer'

Expand All @@ -26,7 +26,7 @@ import type { ExtendedCapabilities, DevToolsOptions } from './types'

const log = logger('devtools')

const DEVICE_NAMES = Object.values(puppeteer.devices).map((device) => device.name)
const DEVICE_NAMES = Object.keys(KnownDevices)

/**
* launches Chrome and returns a Puppeteer browser instance
Expand All @@ -51,7 +51,7 @@ async function launchChrome (capabilities: ExtendedCapabilities) {
let headless = (chromeOptions as any).headless || devtoolsOptions.headless

if (typeof mobileEmulation.deviceName === 'string') {
const deviceProperties = Object.values(puppeteer.devices).find(device => device.name === mobileEmulation.deviceName)
const deviceProperties = KnownDevices[mobileEmulation.deviceName as keyof typeof KnownDevices]

if (!deviceProperties) {
throw new Error(`Unknown device name "${mobileEmulation.deviceName}", available: ${DEVICE_NAMES.join(', ')}`)
Expand Down Expand Up @@ -199,16 +199,16 @@ function launchBrowser (capabilities: ExtendedCapabilities, browserType: 'edge'
function connectBrowser (connectionUrl: string, capabilities: ExtendedCapabilities) {
const connectionProp = connectionUrl.startsWith('http') ? 'browserURL' : 'browserWSEndpoint'
const devtoolsOptions = capabilities['wdio:devtoolsOptions']
const options: puppeteer.ConnectOptions = {
const options: ConnectOptions = {
[connectionProp]: connectionUrl,
...devtoolsOptions
}
return puppeteer.connect(options) as unknown as Promise<Browser>
}

export default async function launch (capabilities: ExtendedCapabilities) {
puppeteer.unregisterCustomQueryHandler('shadow')
puppeteer.registerCustomQueryHandler('shadow', QueryHandler)
Puppeteer.unregisterCustomQueryHandler('shadow')
Puppeteer.registerCustomQueryHandler('shadow', QueryHandler as any)
const browserName = capabilities.browserName?.toLowerCase()

/**
Expand Down
14 changes: 7 additions & 7 deletions packages/devtools/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import logger from '@wdio/logger'
import { commandCallStructure, isValidParameter, getArgumentType, canAccess } from '@wdio/utils'
import { WebDriverProtocol, CommandParameters, CommandPathVariables, ElementReference } from '@wdio/protocols'
import type { Logger } from '@wdio/logger'
import type { ElementHandle } from 'puppeteer-core/lib/cjs/puppeteer/common/JSHandle'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/common/Browser'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/common/Page'
import type { ElementHandle } from 'puppeteer-core/lib/cjs/puppeteer/common/ElementHandle'
import type { Browser } from 'puppeteer-core/lib/cjs/puppeteer/api/Browser'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/Frame'
import type { Page } from 'puppeteer-core/lib/cjs/puppeteer/api/Page'

import cleanUp from './scripts/cleanUpSerializationSelector'
import { ELEMENT_KEY, SERIALIZE_PROPERTY, SERIALIZE_FLAG, ERROR_MESSAGES, PPTR_LOG_PREFIX } from './constants'
Expand Down Expand Up @@ -110,10 +110,10 @@ export async function findElement (
await waitForFn.call(context, value, { timeout: implicitTimeout })
}

let element
let element: ElementHandle<Element> | null = null
try {
element = using === 'xpath'
? (await context.$x(value))[0]
? (await context.$x(value))[0] as ElementHandle<Element>
: await context.$(value)
} catch (err: any) {
/**
Expand Down Expand Up @@ -151,7 +151,7 @@ export async function findElements (
}

const elements = using === 'xpath'
? await context.$x(value)
? await context.$x(value) as ElementHandle<Element>[]
: await context.$$(value)

if (elements.length === 0) {
Expand Down
60 changes: 31 additions & 29 deletions packages/devtools/tests/__mocks__/puppeteer-core.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
const sendMock = jest.fn()
const listenerMock = jest.fn()

const devices = [{
name: 'Nexus 6P',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 412,
height: 732,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: false
}
}, {
name: 'Nexus 6P landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 732,
height: 412,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: true
export const KnownDevices = {
'Nexus 6P': {
name: 'Nexus 6P',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 412,
height: 732,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: false
}
},
'Nexus 6P landscape' : {
name: 'Nexus 6P landscape',
userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
viewport: {
width: 732,
height: 412,
deviceScaleFactor: 3.5,
isMobile: true,
hasTouch: true,
isLandscape: true
}
}
}]
devices['Nexus 6P'] = devices[0]
devices['Nexus 6P landscape'] = devices[0]

}
class CDPSessionMock {
send = sendMock
on = listenerMock
Expand Down Expand Up @@ -68,7 +68,12 @@ class PuppeteerMock {
getActivePage = jest.fn().mockImplementation(() => page)
pages = jest.fn().mockReturnValue(Promise.resolve([page, page2]))
userAgent = jest.fn().mockImplementation(() => 'MOCK USER AGENT')
_connection = { _transport: { _ws: { addEventListener: jest.fn() } } }
wsEndpoint = jest.fn().mockReturnValue('ws://some/path/to/cdp')
}

export class Puppeteer {
static registerCustomQueryHandler = jest.fn()
static unregisterCustomQueryHandler = jest.fn()
}

export default {
Expand All @@ -78,9 +83,6 @@ export default {
PuppeteerMock,
sendMock,
listenerMock,
devices,
registerCustomQueryHandler: jest.fn(),
unregisterCustomQueryHandler: jest.fn(),
launch: jest.fn().mockImplementation(
() => Promise.resolve(new PuppeteerMock())),
connect: jest.fn().mockImplementation(
Expand Down
4 changes: 1 addition & 3 deletions packages/devtools/tests/devtools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ jest.mock('../src/launcher', () => jest.fn().mockImplementation((capabilities) =
off: jest.fn(),
setDefaultTimeout: jest.fn()
}])),
_connection: {
url: () => 'ws://localhost:49375/devtools/browser/c4b017ea-f476-4026-a699-bc5d4858cfe1'
},
wsEndpoint: jest.fn().mockReturnValue('ws://localhost:49375/devtools/browser/c4b017ea-f476-4026-a699-bc5d4858cfe1'),
userAgent: jest.fn().mockReturnValue(Promise.resolve('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'))
}
}))
Expand Down
8 changes: 5 additions & 3 deletions packages/devtools/tests/elementStore.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ElementStore from '../src/elementstore'
import type { ElementHandle } from 'puppeteer-core/lib/cjs/puppeteer/common/JSHandle'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/FrameManager'
import type { ElementHandle } from 'puppeteer-core/lib/cjs/puppeteer/common/ElementHandle'
import type { Frame } from 'puppeteer-core/lib/cjs/puppeteer/common/Frame'

const elementHandleFactory = (
{ isConnected = true, frame = Symbol() }: { isConnected?: boolean, frame?: symbol } = {}
Expand All @@ -10,7 +10,7 @@ const elementHandleFactory = (
return cb({ isConnected })
},
executionContext() {
return { frame: () => frame }
return { _world: { frame: () => frame } }
}
})

Expand Down Expand Up @@ -44,6 +44,8 @@ test('should clear elements of a specific frame', async () => {
store.set(elementHandle1)
const elementHandle2 = elementHandleFactory({ frame: frame2 }) as any as ElementHandle
store.set(elementHandle2)
console.log("store['_frameMap'].size:", store['_frameMap'].size)

expect(store['_frameMap'].size).toBe(2)

store.clear(frame1 as any as Frame)
Expand Down