Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Playwright before each for all spec files

I am very new to Playwright. Due to my test suites, I need to login into my application before running each test. Inside a single spec file that is easy, I can simply call test.beforeEach. My issue is: I need to before the login before each test of each spec file.

test.describe('Test', () => {
    //I need to make the code inside this beforeEach a exported 
    //function to call inside the before each of every spec file I have
    test.beforeEach(async ({ page }) => {
        await page.goto('/login');
        await page.click('text=Log in with Google account');
        await page.fill('id=identifierId', LoginAutomationCredentials.USER);
        await page.click('button[jsname="LgbsSe"]');
        await page.fill('input[type="password"]', LoginAutomationCredentials.PASSWORD);
        await page.click('button[jsname="LgbsSe"]');
        const otp = authenticator.generateToken(LoginAutomationCredentials.TOKEN);
        await page.fill('id=totpPin', otp);
        await page.click('button[jsname="LgbsSe"]');
    });

    it('Some description', async ({ page }) => {
        await page.goto('/foo');
        const dateFilter = await page.inputValue('input[placeholder="2011/03/02"]');
        expect(dateFilter).toBe('2021/12/07');
    });
});

I tried simply taking that code and and making it a function inside a separate .ts file and then importing it, but I figured the context is needed in order to do this. This is probably something every tester that uses playwright knows and uses regularly, however, I did not find anything on the subject.

How can I avoid copying the entire code of beforeEach and pasting it to all my spec files? How can I make it a function and call it whenever I want?

like image 350
Pelicer Avatar asked Dec 28 '25 12:12

Pelicer


2 Answers

Use fixtures.

fixture.js:

const base = require('@playwright/test')
const newTest = base.test.extend({
    login: async({page}, use) => {
        await login();
        await use(page); //runs test here
        //logic after test
    }
})
exports.newTest = newTest
exports.expect = newTest.expect

Then in your tests:

const {newTest} = require('fixture.js')
newTest('mytest', async ({login}) => {
    //test logic
    login.goto(''); // using login here since I pass it as page in the fixture.
})
like image 166
Inkssz Avatar answered Dec 31 '25 04:12

Inkssz


There two aproaches:

Use project dependency

Form Playwright 1.31 with project dependency you can set test what will be executed before any other tests and pass i.e. browser storage between tests.

See working and tested example (entering google search page and accepting policies, and then entering this page again with cookies):

playwright.config.ts:

import { PlaywrightTestConfig } from "@playwright/test";
import path from "path";

//here you save session
export const STORAGE_STATE = path.join(__dirname, 'login.json')
    
const config: PlaywrightTestConfig = {
  timeout: 10 * 1000,
  expect: {
    timeout: 3 * 1000,
  },
  testDir: './tests',
  use:{
    baseURL: 'https://google.com',
    trace: 'retain-on-failure',
    video: 'retain-on-failure',
  },
  // here we set main project and dependent one
  projects: [
    {
      name: 'login',
      grep: /@login/
    },
    {
      name: 'depend e2e',
      grep: /@e2e/,
      dependencies: ['login'],
      use: {
        storageState: STORAGE_STATE
      }
    }

  ]
}; 

export default config;

tests/example.spec.ts:

import { test} from '@playwright/test';
import { STORAGE_STATE } from '../playwright.config';
    
test('login to service @login', async({page}) => {
  await page.goto('/');
  // below example is to reject cookies from google
  await page.waitForLoadState('domcontentloaded');
  await page.keyboard.press('Tab');
  await page.keyboard.press('Enter');
  await page.getByRole('menuitem', { name: "English (United States)" }).press('Enter');
  await page.getByRole('button', { name: 'Reject all' }).click();
  
  // Save storage:
  await page.context().storageState({path: STORAGE_STATE})
})

test('logged in test @e2e', async ({page}) => {
  await page.goto('/');
  await page.waitForLoadState('domcontentloaded');
  // BOOM - you are in!
  // Screenshot shows that settings regarding cookies were saved
  await page.screenshot({ path: 'screenshot.png' })
})

Video with example: https://www.youtube.com/watch?v=PI50YAPTAs4&t=286s

In above example I'm using tags to identify tests: https://playwright.dev/docs/test-annotations#tag-tests

Use fixtures

Let test entering google search page and accepting policies

In file fixtures.ts:

import { test as base } from "@playwright/test";

export const test = base.extend({
  page: async ({ baseURL, page }, use) => {
    // We have a few cases where we need our app to know it's running in Playwright.
    // This is inspired by Cypress that auto-injects window.Cypress.
    await page.addInitScript(() => {
      (window as any).Playwright = true;
    });

   await page.goto("/");
   // below example is to reject cookies from google
   await page.waitForLoadState("domcontentloaded");
   await page.keyboard.press("Tab");
   await page.keyboard.press("Enter");
   await page
     .getByRole("menuitem", { name: "English (United States)" })
     .press("Enter");
   await page.getByRole("button", { name: "Reject all" }).click();

    use(page);
  },
});
export { expect } from "@playwright/test";

And then in test use new test object instead importing it form "@playwright/test"

import { test, expect } from "../fixture";

test("logged in test @e2e", async ({ page }) => {
  await page.goto("/");
  await page.waitForLoadState("domcontentloaded");
  // BOOM - you are in!
  // Screenshot shows that it works
  await page.screenshot({ path: "screenshot.png" });
});

Inspired by: https://github.com/microsoft/playwright/issues/9468#issuecomment-1403214587

If you need example with global request intercepting see here: https://stackoverflow.com/a/76234592/1266040

like image 41
pbaranski Avatar answered Dec 31 '25 06:12

pbaranski