Puppeteer: Docs: clarify how to know when a frame is loaded.

Created on 17 Nov 2017  路  3Comments  路  Source: puppeteer/puppeteer

https://github.com/GoogleChrome/puppeteer/issues/1361 asked how to determine if a frame is loaded.

@aslushnikov posted a suggestion, but it's unclear if that addresses "frame is loaded".

We should clarify in the docs whether frameattached or framenavigated are guaranteed to happen after window.onload. e.g. where do those events fall in relation to the frames load event and when using pptr's waitUntil timings?

good first issue

Most helpful comment

@ebidel, @aslushnikov - I would like to take this one, but I'm not sure what is the right way to determine if a frame is loaded...

@yanivefraim There's no single answer: it's always specific to what exactly webpage is doing and how it's behaving.
Since it's quite an open-ended question, there's not much to put in the documentation.

One more thing - as a test writer, it is hard to work with event emitter. It would be really nice to wrap this one with a promise. For example, instead of writing:
page.on('framenavigated',frame => do something...)

It would be really nice to have:
const frame = await page.frameNavigated()

This is easily achievable with a small helper function:

function once(emitter, event) {
  return new Promise(resolve => emitter.once(event, resolve));
}

const frame = await once(page, 'framenavigated');

This one is about switching to iframes. I feel that iFrame solution is not good enough here. In Selenium/Protractor we could just switch to an iFrame context and continue working inside the iFrame seamlessly. Here it would be much more complicated, I guess.

You can emulate the "switchFrame" semantics with the frames api that we have in puppeteer; just pass the desired frame anywhere as an argument to the functions that do actions. This worked successfully for us so far:

async function doWork(frame) {
  const handle = await frame.waitForSelector('.foo');
  // do some other work
}

let currentFrame = page.mainFrame();
await doWork(currentFrame);
currentFrame = frame.childFrames()[0]; // switch to some other frame
await doWork(currentFrame);
currentFrame = frame.parentFrame(); // switch back to parent

Hope this helps.

All 3 comments

@ebidel, @aslushnikov - I would like to take this one, but I'm not sure what is the right way to determine if a frame is loaded...

For me, framenavigated worked (I got frame.content() inside this event), but we might want to wait until we have data, in order to prevent flakiness...

One more thing - as a test writer, it is hard to work with event emitter. It would be really nice to wrap this one with a promise. For example, instead of writing:
page.on('framenavigated',frame => do something...)

It would be really nice to have:
const frame = await page.frameNavigated()

WDYT? (I will gladly add a PR)

And, last thing, not exactly the same, but related:
https://github.com/ChromeDevTools/devtools-protocol/issues/42

This one is about switching to iframes. I feel that iFrame solution is not good enough here. In Selenium/Protractor we could just switch to an iFrame context and continue working inside the iFrame seamlessly. Here it would be much more complicated, I guess.

@ebidel, @aslushnikov - I would like to take this one, but I'm not sure what is the right way to determine if a frame is loaded...

@yanivefraim There's no single answer: it's always specific to what exactly webpage is doing and how it's behaving.
Since it's quite an open-ended question, there's not much to put in the documentation.

One more thing - as a test writer, it is hard to work with event emitter. It would be really nice to wrap this one with a promise. For example, instead of writing:
page.on('framenavigated',frame => do something...)

It would be really nice to have:
const frame = await page.frameNavigated()

This is easily achievable with a small helper function:

function once(emitter, event) {
  return new Promise(resolve => emitter.once(event, resolve));
}

const frame = await once(page, 'framenavigated');

This one is about switching to iframes. I feel that iFrame solution is not good enough here. In Selenium/Protractor we could just switch to an iFrame context and continue working inside the iFrame seamlessly. Here it would be much more complicated, I guess.

You can emulate the "switchFrame" semantics with the frames api that we have in puppeteer; just pass the desired frame anywhere as an argument to the functions that do actions. This worked successfully for us so far:

async function doWork(frame) {
  const handle = await frame.waitForSelector('.foo');
  // do some other work
}

let currentFrame = page.mainFrame();
await doWork(currentFrame);
currentFrame = frame.childFrames()[0]; // switch to some other frame
await doWork(currentFrame);
currentFrame = frame.parentFrame(); // switch back to parent

Hope this helps.

@yanivefraim,

I found this works nicely:

 function waitForIFrameLoad(page, iframeSelector, timeout = 10000) {
     // if pageFunction returns a promise, $eval will wait for its resolution
    return this.page.$eval(
      iFrameSelector,
      (el, timeout) => {
        const p = new Promise((resolve, reject) => {
          el.onload = () => {
            resolve()
          }
          setTimeout(() => {
            reject(new Error("Waiting for iframe load has timed out"))
          }, timeout)
        })
        return p
      },
      timeout,
    )
}

const iFrameLoaded = waitForIFrameLoad(page, '#myframe')
doSomethingToTriggerIFrameLoad()
await iFrameLoaded
Was this page helpful?
0 / 5 - 0 ratings

Related issues

mityok picture mityok  路  3Comments

vonGameTheory picture vonGameTheory  路  3Comments

denniscieri picture denniscieri  路  3Comments

sradu picture sradu  路  3Comments

bricss picture bricss  路  3Comments