[Question] Difficulty Handling Authentication Across Multiple Tabs in Different Applications
Hi there,
I'm currently working with Playwright to test a scenario that involves two separate web applications. The test begins in my application, where the user is already authenticated. From there, the user clicks a button that opens QuickBooks Online (QBO) in a new tab. The user should also be authenticated in QBO, but I'm having trouble ensuring this.
Here's a simplified version of my test:
// spec.file.ts
import { test } from "./fixtures";
test.describe("Multitab", () => {
test.use({ storageState: ".auth/AutoTest.json" });
test("Two applications - two different tabs", async ({ context, page }) => {
await page.goto("https://myapp.com/some-path");
const pagePromise = context.waitForEvent("page");
await page.getByText("Open new tab in QBO").click();
const newPage = await pagePromise;
await newPage.waitForLoadState();
const qboPage = new QBOPage(newPage);
await qboPage.headerTitle.waitFor();
});
});
And this is the fixture file where I'm creating the QBOPage:
// fixture.ts
import { test as base } from "@playwright/test";
import { QBOPage } from "./index";
type MyFixtures = {
qboPage: QBOPage;
};
export const test = base.extend<MyFixtures>({
qboPage: async ({ page }, use) => {
const qboPage = new QBOPage(page);
await use(new QBOPage(page));
},
});
export { expect } from "@playwright/test";
In the test, .auth/AutoTest.json is a storage state file that I've generated for my application. I also have a separate storage state file for QBO, .auth/QBO.json, but I'm not sure how to apply it to the QBO page.
I've tried opening the QBO page in a new context with the QBO storage state, but this doesn't seem to work, as the new tab opens in the same context as the original page. I've also tried merging the two storage state files into one and using that in the test.use call, but it doesn't seem to solve my problem since the storage states are incompatible and have conflicts with the keys.
I'd appreciate any guidance on how to handle this situation. Specifically, I'm looking for a way to ensure that when the QBO page opens, it is already authenticated, just like the original page in my application.
Thanks for your help!
I've also tried merging the two storage state files into one and using that in the test.use call, but it doesn't seem to solve my problem since the storage states are incompatible and have conflicts with the keys.
This is the intended solution. Since your actual user has both your app and QBO opened in the same context (two tabs in the same window?), and successfully logged in both, you should do the same with your storage state. Tell us more about storage state conflicts you have - what exactly is happening there?
@dgozman Thanks for confirming that was the intended solution. Finally got it working, the problems I was running into with the storage states were because I was overwriting the keys instead of merging them. This is what I ended up doing to merge the separate storage states:
// mergeStorageStates.js
const fs = require("fs");
function mergeStorageStateFiles(file1, file2, outputFile) {
const obj1 = JSON.parse(fs.readFileSync(file1, "utf8"));
const obj2 = JSON.parse(fs.readFileSync(file2, "utf8"));
const mergedObject = {
cookies: [...obj1.cookies, ...obj2.cookies],
origins: [...obj1.origins, ...obj2.origins],
};
fs.writeFileSync(outputFile, JSON.stringify(mergedObject, null, 2), "utf8");
}
// Example usage
const file1 = ".auth/AutoTest.json";
const file2 = ".auth/QBO.json";
const outputFile = ".auth/AutoQBO.json";
mergeStorageStateFiles(file1, file2, outputFile);
The spec.file.ts stayed the same, just had to import the QBOPage and use the merged storage state file .auth/AutoQBO.json:
// spec.file.ts
import { QBOPage } from "../../pages/fixtures/index";
import { test } from "./fixtures";
test.describe("Multitab", () => {
test.use({ storageState: ".auth/AutoQBO.json" });
test("Two applications - two different tabs", async ({ context, page }) => {
await page.goto("https://myapp.com/some-path");
const pagePromise = context.waitForEvent("page");
await page.getByText("Open new tab in QBO").click();
const newPage = await pagePromise;
await newPage.waitForLoadState();
qboPage = new QBOPage(newPage);
await qboPage.headerTitle.waitFor();
});
});
All that being said, I don't see a reason to create a QBOPage fixture now, is that correct? Since I'm creating the page when the new tab is opened inside the test.
This is what I ended up doing to merge the separate storage states:
Looks great! However, I wonder if you could just prepare the storage state where both accounts have been logged in? Like if you are logging in via browser/api request, you can just log in twice in the same page on different urls, and then export storageState. Depending on what else you do with storage states, it might be a bit easier.
All that being said, I don't see a reason to create a QBOPage fixture now, is that correct? Since I'm creating the page when the new tab is opened inside the test.
Yes, new QBOPage(newPage) is exactly what you need. Overall, fixture for POM is just a way to make code a bit nicer, all the real work happens in the POM itself.
It looks like we can close this issue now, since everything works?
Ahh yes, I'll consider saving the storage state after logging in to both apps depending on how the project/tests end up structured (i'm still working on the poc for pitching the transition to playwright). The main reason I avoided doing that is the QBO login takes about 1 min to complete (2fa and email forwarding make it slow) and I didn't want to slow down the other tests that don't interact with QBO.
And yes I'll close the issue, thanks for helping out, really appreciate it!