I'm trying to take a screenshot of a page that is running webGL based Cesium. If I just take the screenshot, the page will be loaded, but the webGL wont be finished loading.
If I use the built in networkidle0 or networkidle2, the screenshot is never taken. Here is my code.
And here is the website I'm trying to take a picture of: https://www.arelplane.com/#arelenglish
const puppeteer = require('puppeteer');
module.exports = {
takeScreenshot: (userId) => {
(async () => {
const browser = await puppeteer.launch({
headless: false,
args: [
'--headless',
'--hide-scrollbars'
]
});
const page = await browser.newPage();
await page.goto('https://www.arelplane.com/#arelenglish', {"waitUntil" : "networkidle0"});
await page.screenshot({path: 'example.png'});
await browser.close();
})();
return "Successful API call!";
}
}
Your code is saying headless: false, along with an argument called --headless which means headless: true. Puppeteer will get confused.
Jokes aside, here is what I saw on the network tab.
The network tab says it loads at least 66 requests with at least or more than 1.5s for each resource (idk why it loaded slower on my default chrome).
Same from a page load test (click to see the report), which basically says it loads 170+ requests for around 40+ seconds.
The default timeout is 30 seconds, but it loads data for 90+ seconds.
Here is the argument to deal with navigation timeout.
await page.goto('https://www.arelplane.com/#arelenglish', {"waitUntil" : "networkidle0", "timeout": 0}); // timeout: 0 will disable navigation timeout
Either disable the timeout or increase it to say 120 seconds or something around that range. Here is my code,
puppeteer.launch({headless: false}).then(async browser => {
const page = await browser.newPage();
await page.goto('https://www.arelplane.com/#arelenglish', {"waitUntil" : "networkidle0", "timeout": 0});
await page.screenshot({path: "test.png"});
await browser.close();
});
and here is the result,
Related
I'm using Formik to create a form for a React web app. The submission is as the following code.
const submitForm = (values) => {
console.log(JSON.stringify(values, null, 2));
setFormStatus(status.loading);
// handle request.
axios
.put("#", values)
.then(() => {
console.log("Submission Success");
setFormStatus(status.success);
})
.catch(() => {
console.log(`Submission Failure`);
setFormStatus(status.failure);
})
.then(() => {
console.log("Submission CleanUp");
setTimeout(() => {
console.log("Neutralizing Form");
setFormStatus(status.neutral);
// tell to animate.
setSwitchSubmitBtn(!switchSubmitBtn);
}, 2000);
});
};
After the axis request, there will be a 2s delay before I set the status state back to neutral.
However, I'm testing using Jest, waitFor doesn't work as expected. The timeout in submission seems blocked, as no matter how long I wait for the submission, it just won't occur. I found a solution to this by using jest.advanceTimer, but adding the same amount of delay in waitFor doesn't work.
it("INPUT_FORM_TC_008", async () => {
jest.useFakeTimers();
render(<InputForm />);
axios.put.mockImplementation(async () => {
console.log("MOCKED PUT");
return Promise.reject();
});
// click submit Button
user.click(screen.getByTestId("submitBtn"));
await waitFor(() => {
expect(screen.queryByTestId("crossIcon")).not.toBeNull();
});
act(() => {
jest.advanceTimersByTime(2000);
});
await waitFor(() => {
// crossIcon will be unmounted once `status` changes to neutral.
expect(screen.queryByTestId("crossIcon")).toBeNull();
});
});
The following code won't work if I don't use jest.advanceTimer, even I set timeout much longer than the one in submission.
await waitFor(() => {
expect(screen.queryByTestId("crossIcon")).toBeNull();
}, 5000);
I suspect that this is related to the event loop stuff, but I tried to set the timeout to be 20ms in the submission and it works. So I looking for a reason why this happens.
I tried to test a state change using setTimeOut using Jest. I expect after the amount of time I specified in setTimeOut the state should be changed. But using waitFor won't work even set a longer timeout.
The issue is that you are setting a setTimeout in a scope that is immediately exited. This code should still work when it's actually run due to a closure on your function, but in Jest the test will say, "welp, looks like there's no more synchronous code" and finish the test before waiting for the execution.
If you want more information on why setTimeout behaves this way you can check the MDN article here: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
But putting it very simply, it uses something called the "event loop" which is how JS handles asynchronous code while still being a synchronous language. When you use setTimeout, you're simply adding the function execution to the event loop and saying, "Don't execute this code now, run other code until the 2000 ms have gone by, then run this code." Which works fine in your implementation because you're allowing 2000ms to go by. But Jest doesn't wait, it just says "There's no more code to execute and nothing is ready in the event loop, the test must be complete" and cleaning everything up immediately.
You could resolve this by wrapping your setTimeout in a promise, like:
//........
.then(() => {
console.log("Submission CleanUp");
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Neutralizing Form");
resolve(status.neutral);
}, 2000);
})
});
Then have your actual implementation code wait for the promise to resolve and set your formStatus and submissionButton states after the Promise is resolved. This would be a simpler API, would allow for greater abstraction, and would make testing easier since you'd now be able to wait for this execution to finish with a .then() block in your tests as well.
I'm very new to Playwright. I'm trying to follow this https://playwright.dev/docs/auth#reuse-signed-in-state so I can login and then save the session for further tests.
My steps work fine in a test but once I've moved them to global-setup.ts I get a timeout:
import { chromium, FullConfig } from '#playwright/test'
async function globalSetup(config: FullConfig) {
const browser = await chromium.launch()
const page = await browser.newPage()
await page.goto('MYDOMAIN/login');
await page.locator("a[testid='googleSignIn']").click()
await page.locator("input[type='email']").fill('MYEMAIL')
await page.locator(
"button[class='VfPpkd-LgbsSe VfPpkd-LgbsSe-OWXEXe-k8QpJ VfPpkd-LgbsSe-OWXEXe-dgl2Hf nCP5yc AjY5Oe DuMIQc LQeN7 qIypjc TrZEUc lw1w4b']",
).click()
await page.locator("input[type='password']").fill('MYPASSWORD')
await page.locator(
"button[class='VfPpkd-LgbsSe VfPpkd-LgbsSe-OWXEXe-k8QpJ VfPpkd-LgbsSe-OWXEXe-dgl2Hf nCP5yc AjY5Oe DuMIQc LQeN7 qIypjc TrZEUc lw1w4b']",
).click()
// Save signed-in state to 'storageState.json'.
await page.context().storageState({ path: 'storageState.json' })
await browser.close()
}
export default globalSetup
The above steps log me in fine in a test but now I've set my config file to use the saved session storage:
storageState: 'storageState.json',
When I run a test I don't see a browser appear it just waits a minute and then says:
locator.click: Timeout 30000ms exceeded.
=========================== logs =========================== waiting for selector "button[class='VfPpkd-LgbsSe VfPpkd-LgbsSe-OWXEXe-k8QpJ
VfPpkd-LgbsSe-OWXEXe-dgl2Hf nCP5yc AjY5Oe DuMIQc LQeN7 qIypjc TrZEUc
lw1w4b']"
at ../../global-setup.ts:15
13 | await page.locator( 14 | "button[class='VfPpkd-LgbsSe
VfPpkd-LgbsSe-OWXEXe-k8QpJ VfPpkd-LgbsSe-OWXEXe-dgl2Hf nCP5yc AjY5Oe
DuMIQc LQeN7 qIypjc TrZEUc lw1w4b']",
15 | ).click()
| ^
The time out is the step just before I enter my password.
I'm running the tests with this command:
yarn playwright test login-page-tests --headed
Am I doing something wrong?
I wouldn't count on these google login class names. Did you try running something like:
await page.locator('text="Next"').click()
In my App I have two windows: mainWindow and actionWindow. On my mainWindow I use the ipcRenderer.on listener to receive as message from the main process when the actionWindow is closed. The message however doesn't come through.
The mainWindow is used to control actions that take place on the actionWindow (e.g. navigate to an URL, remotely close the window, ...). I want to give the user the power to move and close the actionWindow manually as well, which is why its title bar is visible and usable.
I expose ipcRenderer.invoke for two-way communication and ipcRenderer.on to the mainWindow's renderer via contextBridge in a preload file.
This is what the code looks like (based on vite-electron-builder template)
main process
const mainWindow = new BrowserWindow({
show: false, // Use 'ready-to-show' event to show window
webPreferences: {
nativeWindowOpen: true,
webviewTag: false,
preload: join(__dirname, "../../preload/dist/index.cjs"),
},
});
const actionWindow = new BrowserWindow({
// some props
})
actionWindow.on("close", () => {
console.log("window closed")
mainWindow.webContents.send("closed", { message: "window closed" });
});
preload
contextBridge.exposeInMainWorld("ipcRenderer", {
invoke: ipcRenderer.invoke,
on: ipcRenderer.on,
});
renderer (mainWindow)
window.ipcRenderer.on("closed", () => {
console.log("message received")
// do something
});
I know for a fact that
mainWindow has access to the exposed listeners, since invoke works and the actions it fires on the main process are executed on the actionWindow as supposed + the response also comes back to the renderer.
the close listener on the actionWindow works since I can see the log window closed in my console
message received doesn't appear in my dev tools console
To me this means that either
mainWindow.webContents.send doesn't work -> the message is never sent
window.ipcRenderer.on doesn't work -> the message never reaches its destination
So either my code is buggy or Electron has recently put some restrictions on one of these methods which I'm not aware of.
Any ideas?
If there is a smarter way to do this than IPC I'm also open to that.
Ok after hours of searching, trying and suffering I (almost accidentaly) found a solution to my problem. It really seems to be the case that electron simply doesn't do anything anymore when you call the on method from your renderer.
Studying the docs about contextBridge again I saw that the way I exposed invoke and on to the renderer, was considered bad code. The safer way to do this is expose a function for EVERY ipc channel you want to use. In my case using TypeScript it looks like this:
preload
contextBridge.exposeInMainWorld("ipcRenderer", {
invokeOpen: async (optionsString: string) => {
await ipcRenderer.invoke("open", optionsString);
},
onClose: (callback: () => void) => {
ipcRenderer.on("closed", callback);
},
removeOnClose: (callback: () => void) => {
ipcRenderer.removeListener("closed", callback);
},
});
renderer(mainWindow)
window.ipcRenderer.onClose(() => {
// do sth
});
window.ipcRenderer.invokeOpen(JSON.stringify(someData)).then(() => {
// do sth when response came back
});
NOTE: To prevent memory leaks by creating listeners on every render of the mainWindow you also have to use a cleanup function which is provided with removeOnClose (see preload). How to use this function differs depending on the frontend framework. Using React it looks like this:
const doSth= () => {
console.log("doing something")
...
};
useEffect(() => {
window.ipcRenderer.onClose(doSth);
return () => {
window.ipcRenderer.removeOnClose(doSth);
};
}, []);
Not only is this a safer solution, it actually suddenly works :O
Using the cleanup function we also take care of leaks.
I have a PlaywrightCrawler to scrape Alibaba. But when I add a request to one page like:
https://www.alibaba.com/product-detail/Mono-filament-12-mm-PP-fiber_1600139352513.html?spm=a27aq.industry_category_productlist.dt_3.1.3d733642TkHgZc
This page lasted to loading until timeout and handlePageFunction didn't be called.
Actually, all the content has been loaded completed. I notice that some AJAX runs in the background.
How do I force PlaywrightCrawler to call handlePageFunction even though AJAX didn't complete it?
const crawler = new Apify.PlaywrightCrawler({
requestQueue,
launchContext: {
launchOptions: {
headless: false,
},
},
handlePageFunction,
});
you can change your waitUntil parameter to go to the page as soon as the DOM loads using this:
const crawler = new Apify.PlaywrightCrawler({
requestQueue,
// ...
preNavigationHooks: [async (context, gotoOptions) => {
gotoOptions.waitUntil = 'domcontentloaded';
}],
});
this will fire as soon as the page is ready to be queried by document.querySelectorAll, you may have to wait for certain conditions inside the handlePageFunction before starting to call page methods
I want to automate metamask chrome extension with playwright. I found the code below in the API document. I'm able to load Metemask extension but when I try to click the Get Started button on the metamask home page it shows timeout error waiting for the selector.
I need help to check what is the problem and how to work with backgroundpage
(async () => {
const pathToExtension = require('path').join(__dirname, 'my-extension');
const userDataDir = '/tmp/test-user-data-dir';
const browserContext = await chromium.launchPersistentContext(userDataDir,{
headless: false,
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`
]
});
const backgroundPage = browserContext.backgroundPages()[0];
// Test the background page as you would any other page.
await backgroundPage.click('.btn-primary') // Get Started button
await browserContext.close();
})();
Background page is invisible and doesn't have the button you are trying to click. What you need is to be able to click on elements inside the extension popup window which is currently not supported. Please thumb up this feature request https://github.com/microsoft/playwright/issues/5593 if you need it.