I want to submit a login form, then redefine Page.loadEventFired again and do something else when the new page loads. Something like this:
Page.loadEventFired(() => {
Promise.resolve()
.then(function() {
return Runtime.evaluate(/* expression here will submit login form */)
})
.then(function (response) {
// probably not necessary once things are working
console.log('response:', response);
})
.then(/* redefine Page.loadEventFired here */)
.then(function() {
return Page.navigate({
url: 'second link after login',
});
})
// I'm not sure where we should finish logic so we can handle a load event again but
// something happens in between here and I take a screenshot after:
... Page.loadEventFired...
.then(Page.captureScreenshot)
.then(screenshot)
.then(function() {
client.close();
});
});
Looked around for an example and couldn't find one.
You can manage events as explained in #93 or use a single listener but keep a state variable. Assuming that the login form redirects to another page (thus triggering a third Page.loadEventFired), you'll need something like:
async function doStuff(client) {
const {Page} = client;
let state = 0;
Page.loadEventFired(() => {
switch (state) {
case 0:
console.log('login page loaded, now submit the form');
state++;
break;
case 1:
console.log('login performed, now load the screenshot page');
state++;
break;
case 2:
console.log('take the screenshot then close');
break;
}
});
await Page.navigate({url: loginUrl});
}
Probably the former approach will result in a more elegant code, it really depends.
@cyrus-and thanks, I'll try this out when I get off from work today and close the issue if no other questions.
I went with the switch-case method because removing the listeners required me to unbind within Page.loadEventFired, and re-define it within the parent Page.loadEventFired. After a few of these, you'd be nested in pretty deep. Let me know if I'm doing this wrong
Page.loadEventFired(() => {
// do something
client.removeListener('Page.loadEventFired');
Page.loadEventFired(() => {
// do something else
client.removeListener('Page.loadEventFired');
Page.loadEventFired(() => {
// do something else again
})
Page.navigate('third page')
})
Page.navigate('second page)
});
Page.navigate('first page')
You can flatten the code like this:
const CDP = require('chrome-remote-interface');
CDP(async (client) => {
const {Network, Page} = client;
const start = () => {
console.log('stage 0');
client.once('Page.loadEventFired', stage1);
Page.navigate({url: 'https://example.com'});
};
const stage1 = () => {
console.log('stage 1');
client.once('Page.loadEventFired', stage2);
Page.navigate({url: 'https://example.com/1'});
};
const stage2 = () => {
console.log('stage 2');
client.once('Page.loadEventFired', stage3);
Page.navigate({url: 'https://example.com/2'});
};
const stage3 = () => {
console.log('stage 3');
client.once('Page.loadEventFired', end);
Page.navigate({url: 'https://example.com/3'});
};
const end = () => {
client.close();
console.log('done');
};
Page.enable().then(start);
}).on('error', (err) => {
console.error(err);
});
Notice the use of once, which is equivalent to use on then removeListener.
I plan to add a Promise interface for once events which should allow to cope with these use cases in a more elegant way. I'll write here when it's done.
Here we go, since [v0.20.0] you can also write:
const CDP = require('chrome-remote-interface');
CDP(async (client) => {
const {Network, Page} = client;
try {
await Page.enable();
console.log('stage 0');
await Page.navigate({url: 'https://example.com'});
await Page.loadEventFired();
console.log('stage 1');
await Page.navigate({url: 'https://example.com/1'});
await Page.loadEventFired();
console.log('stage 2');
await Page.navigate({url: 'https://example.com/2'});
await Page.loadEventFired();
console.log('stage 3');
await Page.navigate({url: 'https://example.com/3'});
await Page.loadEventFired();
console.log('done');
} catch (err) {
console.error(err);
}
client.close();
}).on('error', (err) => {
console.error(err);
});
Any feedback is welcome.
That's a lot more elegant than the switch-case method. Thanks.
I know this is closed and all but I found it plenty useful. After trying some of the approaches mentioned here, I went with using a function hoisted as a state variable. In doing so I can await multiple page loads in a single function by resolving a Promise by the page load.
const CDP = require('chrome-remote-interface');
let pageLoad = clickSomeActionThenSubmitForm;
function clickSomeActionThenSubmitForm(Runtime) {
const clickSomeAction = 'document.querySelector(\'a[href="#someAction"]\').click()';
const setSomeInfo = 'document.querySelector("input#someinfo").value = "info"';
const submit = 'document.querySelector(\'form[action*="/smilesAndJoy"] button\').click()';
return Runtime
.evaluate({ expression: clickSomeAction })
.then(() => { return new Promise(resolve => { pageLoad = resolve; }) })
.then(() => { return Runtime.evaluate({expression: setSomeInfo })})
.then(() => { return Runtime.evaluate({expression: submit })})
.then(() => { return new Promise(resolve => { pageLoad = resolve; }) });
}
...
Page.loadEventFired(() => { pageLoad(Runtime) });
@jmrnilsson this is good information, thanks.
Got a question related to this one, hope you can find the time to answer.
I was implementing something similar to this to handle pages that trigger a reload the first time a user visits a website thus has no cookies for it. That's the case of behance for example, where they trigger a page reload with JavaScript (so there's no 301, just a 200 and after some time I guess some JS code triggers a reload).
I was wondering if you know of a way to keep the cookies between the first request and the other. I still get a "Promise was collected" in the first request but I can ignore that and try to execute the same promise in the second request if only I could tell Chrome not to reset everything.
Thanks!
I was wondering if you know of a way to keep the cookies between the first request and the other.
@fracasula that should be the default behavior, cookies are not dropped between subsequent requests. Anyway this is the recommended version.
The "Promise was collected" error is discussed in #116 and this FAQ entry.
I see. Maybe I'm not getting any cookie because the first request fails with a "Promise was collected" error. Can't think of a way of making Chrome just wait till the website triggers the JS reload itself though 馃
Anyway thanks for your help, much appreciated.
@fracasula if you are able to come up with a minimal example feel free to file a new issue. Requests don't fail with a "Promise was collected" error; Runtime.evaluate does.
@cyrus-and I reproduced this in a simple script and opened a new issue here https://github.com/cyrus-and/chrome-remote-interface/issues/231
Most helpful comment
Here we go, since [v0.20.0] you can also write:
Any feedback is welcome.