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

puppeteer error "Cannot find context with specified id undefined" #914

Closed
othree opened this Issue Feb 6, 2018 · 8 comments

Comments

Projects
None yet
5 participants
@othree
Contributor

othree commented Feb 6, 2018

What are you trying to achieve?

  1. open a page
  2. click a link by click
  3. see text from new page

What do you get instead?

Got this error and test failed.

Account - basic flow: login, navigation to pages --
 test
 • I am on page "https://github.com/"
 • I click "About"
 • I see "is how people build software"
 ✖ FAILED in 3314ms


-- FAILURES:

  1) Account - basic flow: login, navigation to pages
       test :
     Protocol error (Runtime.callFunctionOn): Cannot find context with specified id undefined

  Scenario Steps:

  - I.see("is how people build software") at Test.Scenario (account-basic_test.js:10:5)
  - I.click("About") at Test.Scenario (account-basic_test.js:9:5)
  - I.amOnPage("https://github.com/") at Test.Scenario (account-basic_test.js:8:5)



  Run with --verbose flag to see NodeJS stacktrace```

But the captured failed screenshot is correct.

> Provide test source code if related

```js
  Scenario('test ', (I) => {
    I.amOnPage('https://github.com/');
    I.click('About');
    I.see('is how people build software');
  });

Details

  • CodeceptJS version: 1.14
  • NodeJS Version: 9.4
  • Operating System: macOS
  • puppeteer
  • Configuration file:
{
  "tests": "./*_test.js",
  "timeout": 10000,
  "output": "./output",
  "helpers": {
    "puppeteer": {
      "disableScreenshots": true,
      "waitForAction": 200,
      "show": true
    }
  },
  "include": {
    "I": "./steps_file.js"
  },
  "bootstrap": false,
  "mocha": {},
  "name": "web-test-runner"
}

PS. It related to the value of waitForAction. If change it to larger. error will not happen.

More Information

This issue is related to GoogleChrome/puppeteer#1325.
And I think latest comment is correct. The problem is because the click promise resolved when click triggered. But at that moment, navigation is not complete. No context is ready for new action.

@DavertMik

This comment has been minimized.

Member

DavertMik commented Feb 6, 2018

Is there a way to detect that navigation is complete in Puppeteer?

@othree

This comment has been minimized.

Contributor

othree commented Feb 6, 2018

There is a page.waitForNavigation method mentioned in GoogleChrome/puppeteer#1325 , I didn't tested yet. And it will have another problem, some click didn't trigger navigation. Or maybe codecept can expose it to outside.

@reubenmiller

This comment has been minimized.

Collaborator

reubenmiller commented Feb 7, 2018

I've just spent a bit of time looking into this and it is slightly more complex than I thought. Though I found some useful information on the puppeter repo.

It looks like that you have to call the .click and page.waitForNavigation at the same time like so:

await Promise.all([
  page.click('a'),
  page.waitForNavigation()
]);

The reason that it needs to be called at the same time is that apparently .waitForFunction needs to see the new page trigger and the load events occur. So just seeing the page load event is not enough.

So our problem is that we don't know which links will cause a new page to load. And if the above code is used and the link does not change the url, then it will throw a timeout error.

Now we could check the button if it is link like, however that does not cater for the scenario if there are event handlers on it which cause a redirect somewhere. So here are some options.

Option 1: Auto detect url changes and wait if one is detected (a little bit hacky)

However I have been playing around with the idea with using the targetchanged and load events, and recording the url has changed and loaded state information. So the scenario would play out like this:

  1. Click a link using I.click (wait for ~500 ms or enough time for the targetchanged event to trigger).
  2. targetchanged event triggers, and sets a urlChanged flag
  3. (in click function): Wait for the urlChanged flag to be reset (also with timeout breakout)
  4. load event triggers, reset the urlChanged flag.
  5. (in click function): See that the urlChanged flag has been reset and break out of the wait loop.

Option 2: Split the I.click function into navigation and non-navigation functions

  • I.click - Don't wait for any navigation before returning
  • I.clickLink - Click buttons/links/a which will cause a navigation,

Option 3: The user must wait for the page to load themselves

Maybe using I.waitInUrl or something similar to that...

Thoughts?

@othree

This comment has been minimized.

Contributor

othree commented Feb 8, 2018

I am using a work around similar to option 2.

    navByClick (text, locator) {
      this.click(text, locator);
      this.wait(2);
    },

Not a good one, but works for my test.

And I think the final solution for this issue need to wait until puppeteer solves 1325.
So add a method might become unnecessary in the future.
Maybe it will depend on how codeceptjs decide to add a new helper method or not.

@othree

This comment has been minimized.

Contributor

othree commented Feb 12, 2018

I have update my work around by extend helper:

/* global codecept_helper */

'use strict';

let Helper = codecept_helper;

class MyPuppeteer extends Helper {
  async navByClick (text, locator) {
    await this.helpers['Puppeteer'].click(text, locator);

    const page = this.helpers['Puppeteer'].page;
    await Promise.race([page.waitForNavigation({waitUntil: 'networkidle0'}), this.helpers['Puppeteer'].wait(4)]);
  }
}

module.exports = MyPuppeteer;

And helper section in codecept.json:

  "helpers": {
    "Puppeteer": {
      "disableScreenshots": false,
      "waitForAction": 800,
      "show": true
    },
    "MyPuppeteer": {
      "require": "./test/my_puppeteer.js"
    }
  },

Promise.race is due to GoogleChrome/puppeteer#1936 ...

@DavertMik

This comment has been minimized.

Member

DavertMik commented Feb 17, 2018

Should be fixed in #936

@RonHD

This comment has been minimized.

RonHD commented May 2, 2018

I just ran into the same problem with Puppeteer 1.3.0 (not 0.13.0 as in some comments on #1325). I already had await page.waitForNavigation(); following the await btn.click();. The await Promise.all ... solution in @reubenmiller 's Feb. 7 comment resolved it. I haven't checked if it's necessary, but I still have the additional waitForNavigation() after it.

@tomholub

This comment has been minimized.

tomholub commented May 4, 2018

I am seeing something similar on 1.3.0-next.1525301631811

(node:10626) UnhandledPromiseRejectionWarning: Error: Protocol error (Runtime.evaluate): Cannot find context with specified id undefined
    at Promise (/home/luke/git/flowcrypt-browser/test/node_modules/puppeteer/lib/Connection.js:200:56)
    at new Promise (<anonymous>)
    at CDPSession.send (/home/luke/git/flowcrypt-browser/test/node_modules/puppeteer/lib/Connection.js:199:12)
    at ExecutionContext.evaluateHandle (/home/luke/git/flowcrypt-browser/test/node_modules/puppeteer/lib/ExecutionContext.js:67:77)
    at _documentPromise._contextPromise.then (/home/luke/git/flowcrypt-browser/test/node_modules/puppeteer/lib/FrameManager.js:346:38)
    at <anonymous>

There is no navigation, just looking for elements in a loop:

for (let i = 0; i < selectors.length; i++) {
    let elements = this._is_xpath(selectors[i]) ? await page.$x(selectors[i]) : await page.$$(selectors[i]);
    // ...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment