Reputation: 916
I have a series of Playwright tests that may require me to authenticate into different apps. I have a helpful function that allows me to simply pass a URL and it will set a browser context with a specific storage state. It looks like this:
export const visitWithAuth = async (browser: Browser, url: string) => {
let storageFile = ''
if (url.includes('myFirstSite')) {
storageFile = FIRST_SITE_FILE
} else if (url.includes('mySecondSite')) {
storageFile = SECOND_SITE_FILE
}
if (!storageFile) {
throw new Error('Invalid URL or no matching storage state found.')
}
const context = await browser.newContext({ storageState: storageFile })
const page = await context.newPage()
await page.goto(url)
return { page, context }
}
So in the test, it'll look like this:
test('make sure the thing works really good', async ({ browser }) => {
const { page, context } = await visitWithAuth(browser, 'https://www.myfirstsite.com')
await page.dostuff()
await expect(page.otherstuff).toBeWhatever()
await context.close()
})
What I'm finding is that I need to manually ensure that I have await context.close()
at the end of every single one of my tests. It seems that when test signatures uses the normal test('make sure the thing works really good', async ({ page })
, there exists after
hooks in the trace viewer that closes everything as needed.
Is there a way to simply set some kind of config so that any contexts are closed automatically after a test is done? I was hoping I could simply pass some new options with the call to nextContext
or newPage
but I don't see anything in the documentation.
FWIW, Playwright's documentation also mentions closing contexts manually in their example here: https://playwright.dev/docs/auth#testing-multiple-roles-together
Upvotes: 2
Views: 2276
Reputation: 916
After some research it turns out that the best way to do this is through fixtures. Fixtures act similarly to a hook and allow for actions to happen before and after the test steps that happen in the actual test.
Here's a little code snippet/example that follows the original question:
type AuthFixtures = {
myFirstSite: Page
mySecondSite: Page
}
export const test = baseTest.extend<AuthFixtures>({
myFirstSite: async ({ browser }, use) => {
const { page: authorizedPage, context: authorizedContext } = await createAuthContext(browser, `${process.env.MY_FIRST_SITE_URL}`)
await use(authorizedPage)
await authorizedContext.close()
},
mySecondSite: async ({ browser }, use) => {
const { page: authorizedPage, context: authorizedContext } = await createAuthContext(browser, `${process.env.MY_SECOND_SITE_URL}`)
await use(authorizedPage)
await authorizedContext.close()
}
})
The function 'createAuthContext' is returning a page and context. That means we the call to use(page)
aligns with what happens in the test steps, and in the fixture we can call context.close()
without having to do it in the test itself. This means that we can write a test like this now:
test('do some tests while authed into myFirstSite', async ({ myFirstSite: page }) => {
page.doStuff()
})
For any rubyists out there, await use(page)
almost mimics what yield
does in Ruby.
Upvotes: 0