Skip to content

(@wdio/globals): fix getPuppeteer()#10804

Merged
christian-bromann merged 3 commits intowebdriverio:mainfrom
jan-molak:fix/global-methods
Jul 27, 2023
Merged

(@wdio/globals): fix getPuppeteer()#10804
christian-bromann merged 3 commits intowebdriverio:mainfrom
jan-molak:fix/global-methods

Conversation

@jan-molak
Copy link
Contributor

Proposed changes

At the moment, proxyHandler from @wdio/globals returns fields from global objects without binding them to their receivers:

function proxyHandler (key: SupportedGlobals) {
return {
get: (self: never, prop: any) => {
if (!globals.has(key)) {
throw new Error(GLOBALS_ERROR_MESSAGE)
}
return globals.get(key)[prop]
}
}
}

This means that when the field happens to be a method referencing this (like the getPuppeteer method), it loses the context of its receiver object and doesn't work:

export async function getPuppeteer (this: WebdriverIO.Browser) {
/**
* check if we already connected Puppeteer and if so return
* that instance
*/
if (this.puppeteer?.isConnected()) {
log.debug('Reusing existing puppeteer session')
return this.puppeteer
}
const { headers } = this.options
const caps = (this.capabilities as Capabilities.W3CCapabilities).alwaysMatch || this.capabilities as Capabilities.DesiredCapabilities
/**
* attach to a Selenium 4 CDP Session if it's returned in the capabilities
*/
const cdpEndpoint = caps['se:cdp']
if (cdpEndpoint) {
this.puppeteer = await puppeteer.connect({
browserWSEndpoint: cdpEndpoint,
defaultViewport: null,
headers
}) as any as PuppeteerBrowser
return this.puppeteer
}

For example, the following code sample DOESN'T WORK as getPuppeteer() is not bound to browser and returns an undefined.

import { browser } from '@wdio/globals';

const puppeteer = await browser.getPuppeteer();
// returns undefined

Types of changes

This pull request addresses the issue by binding method calls to their respective receiver objects.

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Checklist

  • I have read the CONTRIBUTING doc
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)
  • I have added proper type definitions for new commands (if appropriate)

Further comments

Reviewers: @webdriverio/project-committers

Copy link
Member

@christian-bromann christian-bromann left a comment

Choose a reason for hiding this comment

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

@jan-molak thanks for raising the PR.

I never stumbled upon this problem. Can we add a unit test to ensure this doesn't fail us again?

@jan-molak
Copy link
Contributor Author

jan-molak commented Jul 26, 2023

Hey @christian-bromann, sure thing, I was looking for a good place to do it but struggled to find it. Are there any existing specs in this area that I could extend, or should I make something new?

@christian-bromann
Copy link
Member

I think packages/webdriverio/tests/commands/browser/getPuppeteer.test.ts is the best place to test the change. I wonder if this bug was the reason why I called browser.getPuppeteer.call(...) instead of just browser.getPuppeteer(...)

@jan-molak
Copy link
Contributor Author

Looks like packages/webdriverio/tests/commands/browser/getPuppeteer.test.ts uses remote to instantiate a browser rather than the global browser object, so doesn't trigger the bug 🤔.

For example, this test works with and without the patch:

    it('should work', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'chrome',
            },
        })
        const pptr = await browser.getPuppeteer()
        expect(typeof pptr).toBe('object')
    })

Are there any tests specifically around @wdio/globals that I could extend?

@christian-bromann
Copy link
Member

I recommend adding a smoke test then. It runs a "fake" session but allows you to import the browser via @wdio/globals. You can just add a test case to the mocha one: tests/mocha/test.ts

@jan-molak
Copy link
Contributor Author

jan-molak commented Jul 26, 2023

Hey @christian-bromann, it looks like adding tests in this area might be a bit challenging, so I'd be grateful if you could suggest a way to proceed.

Adding a test

I started by adding a simple test to tests/mocha/test.ts:

    it('should provide access to Puppeteer', async () => {
        const puppeteer = await global.browser.getPuppeteer()
        expect(puppeteer).toBeDefined()
    })

This failed with:

[0-0] TypeError in "Mocha smoke test.should provide access to Puppeteer"
TypeError: caps.browserVersion.split is not a function
    at Browser.getPuppeteer (file:///Users/jan/Projects/jan-molak/webdriverio/packages/webdriverio/build/commands/browser/getPuppeteer.js:95:59)
    at Browser.wrapCommandFn (file:///Users/jan/Projects/jan-molak/webdriverio/packages/wdio-utils/build/shim.js:72:38)
    at async Context.<anonymous> (file:///Users/jan/Projects/jan-molak/webdriverio/tests/mocha/test.ts:117:27)

🐛 #2 Mock config issue

I tracked it down to this line, which defines the browser version as number instead of
string

I changed that as follows:

- browserVersion: 64.0,
+ browserVersion: '79.0',

This is to avoid triggering this clause that prevents using Puppeteer with anything older than v79.0:

if (caps.browserName?.toLowerCase() === 'firefox') {
if (!caps.browserVersion) {
throw new Error('Can\'t find "browserVersion" in capabilities')
}
const majorVersion = parseInt(caps.browserVersion.split('.').shift() || '', 10)
if (majorVersion >= 79) {

This allowed me to get further, but resulted in the following error message:

[0-0] Error in "Mocha smoke test.should provide access to Puppeteer"
Error: Could't find a websocket url within returned capabilities to connect to! Make sure you have "moz:debuggerAddress" set to `true` in your Firefox capabilities
    at Browser.getPuppeteer (file:///Users/jan/Projects/jan-molak/webdriverio/packages/webdriverio/build/commands/browser/getPuppeteer.js:113:23)
    at Browser.wrapCommandFn (file:///Users/jan/Projects/jan-molak/webdriverio/packages/wdio-utils/build/shim.js:72:38)
    at async Context.<anonymous> (file:///Users/jan/Projects/jan-molak/webdriverio/tests/mocha/test.ts:117:27)

🐛 #3 Error message issue

Setting moz:debuggerAddress to true, as per the error message causes URL parsing error, so the error message should be adjusted at

'Make sure you have "moz:debuggerAddress" set to `true` in your Firefox capabilities'

🐛 #4 Can't use Puppeteer with mock WebdriverIO

Setting moz:debuggerAddress to an actual debugger URL, like http://localhost:1234 allows me to pass the configuration stage, but fails since Puppeteer connection is not mocked out by wdio-service-mock and of course there's no actual browser involved:

[0-0] system in "Mocha smoke test.should provide access to Puppeteer"
FetchError: Failed to fetch browser webSocket URL from http://localhost:1234/json/version: request to http://localhost:1234/json/version failed, reason: connect ECONNREFUSED 127.0.0.1:1234

Next steps?

I suppose we could reuse Nock instance in

this.scope = nock(`http://${host}:${port}`, { 'encodedQueryParams': true })
to pretend to be a debugger URL, but I'm not sure what requests/responses we'd need to mock out for that, and if it's even a good idea.

The mock might need to be quite involved to fool puppeteer.connect({...}) at

this.puppeteer = await puppeteer.connect({

@jan-molak jan-molak changed the title (@wdio/globals): fix method calls on global objects so that they're correctly bound (@wdio/globals): fix getPuppeteer() Jul 26, 2023
@christian-bromann
Copy link
Member

Dang .. I guess testing that this.puppeteer is accessible is the only way to test it. Then I suggest to add a full e2e test. I suggest the best place would be https://github.com/webdriverio/webdriverio/blob/main/e2e/wdio/headless/test.e2e.ts then.

@jan-molak
Copy link
Contributor Author

jan-molak commented Jul 26, 2023

@christian-bromann - I've added a new e2e test file as modifying test.e2e.ts makes Husky pre-commit checks complain about unrecognised Mocha global functions, window, and them like. I'm guessing that the pre-commit check was introduced after the test.e2e.ts, so they're a bit out of sync.

Anyway, hope the test is OK.

Copy link
Member

@christian-bromann christian-bromann left a comment

Choose a reason for hiding this comment

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

Let's import Browser directly if possible

Copy link
Member

@christian-bromann christian-bromann left a comment

Choose a reason for hiding this comment

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

Awesome, lgtm 👍

@christian-bromann christian-bromann merged commit 56fd189 into webdriverio:main Jul 27, 2023
@christian-bromann christian-bromann added the PR: Bug Fix 🐛 PRs that contain bug fixes label Jul 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: Bug Fix 🐛 PRs that contain bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants