*Updates 22.6.2022 / Reproduced the problem with another site that uses OAuth, when the globalSetup should do stuff on the OAuth domain, it fails 21.6.2022 / Trace.zip shows that the url to OAuth login is correct, but the screenshot shows an empty white page
What I'm trying to achieve:
I'm trying to use cached authentication with my Playwright TypeScript test project. Following their docs availaable here - https://playwright.dev/docs/test-auth.
This way before each test is run, the login in global-setup.ts
is done once and then cached for future tests.
The issue:
Test fails with page.click: Timeout 30000ms exceeded.
When I use $env:PWDEBUG=1
there is no issue. If I use 0
, the issue appears.
Quick workflow:
Main login page in domain abc.com
Click "login by email"
it goes to a different domain with the email login form (< [placeholder="Email Address"] exists here)
What I noticed: I took screenshots and logs, and it seems that with DEBUG=0, it never goes to the page where the 'missing' element should be. In the workflow #1, a click is performed to a button "login with email", but then the email login form page isn't visible for some reason. I tried adding more timeout, more waiting for loads etc. but nothing works. Note: the login page after clicking goes to a different domain for the login if it matters. But it doesn't matter when DEBUG is 1..
Also everything works well if I just use beforeEach, but then again it's not what Playwright instructs in their docs.
npx playwright test
Running 3 tests using 1 worker
page.click: Timeout 30000ms exceeded.
=========================== logs ===========================
waiting for selector "[placeholder="Email Address"]"
============================================================
at ..\global-setup.ts:19
18 | // Click [placeholder="Email Address"]
> 19 | await page.click('[placeholder="Email Address"]');
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://website.myapp.fi/app/');
// Have tried this without waitForNavigation with no difference in output
await Promise.all([
page.waitForNavigation(),
page.locator('div[role="button"]:has-text("email")').click(),
]);
// Click [placeholder="Email Address"]
await page.click('[placeholder="Email Address"]');
await page.locator('[placeholder="Email Address"]').fill('email..');
// Click [placeholder="Password"]
await page.click('[placeholder="Password"]');
await page.locator('[placeholder="Password"]').fill('password..');
// Click button:has-text("Sign in")
await page.click('button:has-text("Sign in")');
// Select company
await page.click('.b-number-cell');
await page.waitForLoadState('networkidle');
// Save signed-in state to 'storageState.json'.
await page.context().storageState({ path: 'storageState.json' });
await browser.close();
}
export default globalSetup;
EDIT: added playwright.config.ts
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
const config: PlaywrightTestConfig = {
globalSetup: require.resolve('./global-setup'),
testDir: './tests',
timeout: 30 * 1000,
expect: {
timeout: 5000
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
actionTimeout: 0,
trace: 'on-first-retry',
// Tell all tests to load signed-in state from 'storageState.json'.
storageState: 'storageState.json'
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
],
};
export default config;
I solved the issue by using one separate login test to start the test suite instead of using globalSetup.
The login saves cookies, localStorage and sessionStorage:
test.describe('Login suite', () => {
test("that login works", async ({ page, context }) => {
// ...
// The login routine comes here first, then cache needed values after they are complete
// Save cookies and localstorage to a file, which we can use later in the tests to be logged in automatically
await context.storageState({ path: 'state.json' });
// Get session storage and store as env variable
const sessionStorage = await page.evaluate(() => JSON.stringify(sessionStorage));
process.env.SESSION_STORAGE = sessionStorage;
// Log sessionStorage and localStorage after it has been stored for debugging purposes
const localStorage = await page.evaluate(() =>
JSON.stringify(window.localStorage)
);
});
});
Each test then use cookies and localStorage with "test.use({ storageState: 'state.json' });" and sessionStorage is populated from env variable. context.storageState does not save sessionStorage, so that is why we have to use a different approach to it.
test.describe('Test suite', () => {
test.use({ storageState: 'state.json' });
test("test", async ({ page, context }) => {
// Set session storage in a new context
const sessionStorage = process.env.SESSION_STORAGE;
await context.addInitScript(storage => {
console.log(window.location.hostname);
if (window.location.hostname === 'mysite.eu') {
const entries = JSON.parse(storage);
for (const [key, value] of Object.entries(entries)) {
window.sessionStorage.setItem(key, value);
}
}
}, sessionStorage);
// ...
// The test routine comes here
});
});