Using Playwright fixtures to skip login pages
Tags: playwright, testing, automation
Published: 2023-09-26 16:38:03

One of the first things that you might do with Playwright when you start automating is writing some code to automate logging into your app. It's so incredibly easy to do with `npx playwright codegen` as well, and I've demo'ed this to my team as a rudimentary example while introducing Playwright.

As you begin to scale up your tests, you'll likely find that interacting with the login page results in a lot of wasted clicks and network requests. In fact, we found that hitting `/auth` in every single one of our tests can cause some unintended side effects. Additionally, logging in repeatedly with the same user credentials doesn't coincide with real-world app usage, so why should we do the same in our tests?

Fixtures are very useful in that they act as a hook to your original tests, similarly to having `beforeEach` or `afterEach` hooks everywhere. The major upside of fixtures is that it makes for much cleaner and readable code and provides for consistency across all tests.

Here's what a fixture might look like:

```
import { test as baseTest, type Page } from '@playwright/test'
import { createAuthContext } from './authHelper'

type AuthFixtures = {
  mySiteAuth: Page
}

export const test = baseTest.extend<AuthFixtures>({
  mySiteAuth: async ({ browser }, use) => {
    const { page: authorizedPage, context: authorizedContext } = await createAuthContext(browser)
    await use(authorizedPage)
    await authorizedContext.close()
  }
})
```

Pretty simple but very important stuff going on here: 

1. We're retrieving a page and browser context.
2. The call to `use` yields back to Playwright `test` where all your steps are written.
3. Closing context is important so that browser windows and their pages are closed properly.

You'll want to spend a decent chunk of time figuring out what your function for `createAuthContext` might look like, but in the end, you want it to return a new browser context along with a new page for that context. The browser context itself should take advantage of the `storageState` that Playwright offers. This `storageState` is basically a JSON blob that is written to mimic the exact local storage state of your app. Finally, you'll want to probably add some actual UI login logic if the storage JSON blobs don't exist.

I'll provide a little bit of code below, but keep in mind that this is already well documented by the Playwright team:

```
export const MY_SITE_FILE = 'playwright/.auth/my_site.json'

export const createAuthContext = async (browser: Browser) => {

  const contextOptions: any = { storageState: MY_SITE_FILE }

  const context = await browser.newContext(contextOptions)
  const page = await context.newPage()

  return { page, context }
}
```

Now, all you'll have to do is change up your test signatures. Most of them probably looked like this:

```
import { test, expect } from '@playwright/test'

test('do some things', async ({ page }) => {
  page.doSomeThings()
})
```
And now they'll just look like this:

```
import { test } from './helpers/testFixtures'
import { expect } from '@playwright/test'

test('do some things', async ({ mySiteAuth: page }) => {
  page.doTheSameThings()
})
```

What's awesome here is that you don't need to go and refactor a bunch of individual tests. The most you'll have to do is perhaps remove any calls to your UI login functions, which hopefully is easy to find and remove if you've followed some pretty consistent patterns in your tests.

What I find notable here is the use of `await use(authorizedPage)` almost acts like `yield` in Ruby, which I can definitely appreciate as someone who's loved the language for so long.

Hopefully this helps explain how to have Playwright fixtures working alongside their authentication patterns. I did feel that Playwright's documentation was a teeny bit lacking in this area. I'll also mention that using this type of approach that decouples from any global setup also allows for extending `AuthFixtures` to other sites within the same test suite.

When compared to Selenium WebDriver and how you typically have to set local storage manually through traditional Web APIs, Playwright really does make it a breeze to have already authorized pages at your disposal. Getting right into auth sessions right off the bat really allows for more focused testing.