How to handle opening a new tab in your Playwright test
We thought the docs were wrong...how silly of us
Playwright already shows you how to handle the 2 most common new tab scenarios:
You know the user action that results in the new tab
You don’t know how it will be opened, but you know to expect a new tab
You’ll notice the description above the code mentions that these only work for “pages opened by target=”blank”
links. Same goes for the similar approaches they have for handling popups.
Except, I found out that’s not entirely true.
My team’s scenario at work involved testing a new tab that was opened via the window.open
method directly.
Spoiler: the 2nd approach works for us (context.on(‘page’)
).
But along the way, we found a third way to test a new tab that I thought was pretty interesting.
Let’s see what happened when we tried to use the advice from the documentation first, though.
#1 - The context.waitForEvent(‘page’)
approach
// Wait for a new tab
const pagePromise = context.waitForEvent("page");
// Trigger the new tab
await openNewTabButton.click();
// Wait for the tab to load
const newPage = await pagePromise;
await newPage.waitForLoadState("domcontentloaded");
// Verify the URL
expect(newPage.url()).toContain("/here-be-dragons");
Error: expect(received).toContain(expected) // indexOf
Expected substring: "/here-be-dragons"
Received string: "chrome-error://chromewebdata/" // Nope 😭
#2 - The context.on(‘page’)
approach
// Listen for a new tab to be opened
// When it opens, wait for it to load
// Verify the URL
context.on("page", async (page) => {
await page.waitForLoadState("domcontentloaded");
// This passes! 🎉
expect(page.url()).toContain("/here-be-dragons");
});
// Trigger the new tab
await openNewTabButton.click();
#3 - The page.exposeFunction("open")
approach
On the way to discovering Option #2 was perfectly viable for our situation, we tried this one and found success with it, thought it’s not as “e2e”.
The page.exposeFunction
method allows you to intercept the window.open()
call that creates the new tab, so you can assert on the destination URL without creating the tab.
This is useful if you don’t want to open that new tab for whatever reason.
let newTabUrl: string;
// capture the URL for the new tab before it opens
await page.exposeFunction("open", (url: string, ..._rest: any[]) => {
newTabUrl = url;
});
await openNewTabButton.click();
// This passes! 🎉
await expect(newTabUrl).toContain("/here-be-dragons");
Takeaway
More often than not, you will find that your team’s situation is not the exception.
It’s good to find out what the docs have to say about the scenario you’re testing before going for a custom solution.
We did this, albeit unsuccessfully at first.
If we had succeeded the first time, we wouldn’t have learned about page.exposeFunction
.
To that end, it’s okay to try to come up with your own solution sometimes. You might learn something.