1

I am trying to make a function that opens the browser and logs into the page using basic auth. I would like the function to return the page so I can pick it up in the test and continue to use it.

When I pass browser to the function I need to create a new context inside the function so I can login with basic auth.

Creating a new browser context in the function works fine to open the page and login.

The problem is that I cannot return the new page from the function. The page that is returned from the function has no intellisense and fails when I attempt to use it in the normal way --- such as doing: page.locator('#id1').click() ---> test fails

// Custom Open Browser Login Function
export async function openBrowserAndLogin(browser, username, password){

  const context = await browser.newContext({
    httpCredentials:{
      username: username,
      password: password
    },
  })

  const page = await context.newPage()
  await page.goto('websiteurl.com')
  return page
}


// Test
import { test } from '@playwright/test';
import {openBrowserAndLogin} from '../customfunctions.openBrowser.mjs'

test('login and do stuff', async ({ browser }) => {
  
  const page = openBrowserAndLogin(browser,'user1', 'secretpassword')

  page.locator('#account').click() // no methods exist on this page object???? Test Fail
  
})

So basically I am importing a function into the test. I pass the browser to the function. The function uses browser to create a new context, logs into application, and returns a new page.

The page returned from the function is dead.

Does anyone know a way to return a page from a context created inside an imported function?

Or if there is some other way to accomplish this that would be helpful too.

  • 1
    `test('login and do stuff', async ({ page }) => {` This is how you should get your page. I've noticed too that playwright takes some liberties with how it parses javascript so these small surprises aren't uncommon. The way I do it is that my "utilities" all expect a `page` as an argument, and for pre-test stuff like login, I use `test.use` so setup a config before each test. If you call `test.use({ httpCredentials })`, before your test, the page it gives you will already have these params. – Sheraff Feb 18 '23 at 01:28

2 Answers2

3

openBrowserAndLogin is not a good pattern. This abandons the reference to the browser context object so you can never close it, thereby leaking memory and potentially hanging the process (unless the test suite ungracefully terminates it for you).

Instead, prefer to let Playwright manage the page:

test('login and do stuff', async ({ page }) => {
//                                  ^^^^

Now you can add credentials with Playwright's config or test.use:

import {test} from "@playwright/test"; // ^1.30.0

test.describe("with credentials", () => {
  test.use({
    httpCredentials: {
      username: "user1",
      password: "secretpassword"
    }
  });

  test("login and do stuff", async ({page}) => {
    await page.goto("https://example.com/");
    await page.locator("#account").click();
  });
});

Notice that I've awaited page.locator('#account').click().

Another option is to use fixtures.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • Thank you for your help. The problem I had with using the config to pass in the username and password is that the application I work on has a lot of security roles (each role corresponding to a username for testing purposes) and I have to assign a specific username for each test I write. – Scott Littleton Feb 18 '23 at 18:21
3

You’re just missing await.

openBrowserAndLogin is an async function, so it’s returning a promise, which wouldn’t have the page methods itself. You need to unwrap it first, like so:

const page = await openBrowserAndLogin(browser,'user1', 'secretpassword')

That being said, I would definitely recommend doing the auth in global setup with storageState if you need the same login for every test and then just use the page fixture, or you could always override the page fixture or add your own or something similar. There are other potential ways too. But for what you have, just that small piece was missing.

Note that it’s also good practice to close your context if you manually create one.

David R
  • 493
  • 2
  • 8
  • 1
    `ggorlen`’s point on just using the config or test.use for the http creds is also a great recommendation for your use case/method of auth, and basically would use part of what you already have in the code. – David R Feb 18 '23 at 05:22
  • 1
    The problem I had with using the config to pass in the username and password is that the application I work on has a lot of security roles (each role corresponding to a username for testing purposes) and I have to assign a specific username for each test I write. I was hoping to be able to create a function that does what the test.describe({}) block does but it looks like that isn't really possible without the issues you brought up. So I think I will have to just add the httpCredentials to the beginning of each test. Thank you for your help. – Scott Littleton Feb 18 '23 at 18:27
  • Ah okay, I was wondering if maybe you had a specific situation/use case like that. But the code you had I believe would’ve worked, just was missing `await` to have the code wait for the promise(s) the method(s) returned (click, and really most Playwright methods, also returns a promise). – David R Feb 18 '23 at 21:28