playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Feature] global beforeEach/beforeAll hooks

Open Tomsk666 opened this issue 3 years ago • 31 comments

Instead of having the same test.beforeEach hook defined in every file, how do I only define the beforeEach hook in a single file (say hooks.js) at a global level, so then each file can use that hook? In other words define my hooks in a single file once, but hooks are then used by all of my tests in all my spec files? Thanks

Tomsk666 avatar Oct 13 '21 14:10 Tomsk666

Similarly to test fixture you can do it in a separate file and then import test from that file to your tests:

// testWithBeforeEach.ts
import { test } from '@playwright/test'

test.beforeEach(() => {
    console.log('beforeEach');
})

export default test
// mytest-1.spec.ts
import { test } from './testWithBeforeEach'

test('should work 1', async ({ page }) => {
})
// mytest2.spec.ts
import { test } from './testWithBeforeEach'

test('should work 2', async ({ page }) => {
})

yury-s avatar Oct 13 '21 16:10 yury-s

Hi @yury-s I'm using JavaScript not ts, and seem to keep getting an error:

Using config at C:\Users\tom\OneDrive\Documents\projects\vs-code\Playwright-test-training\playwright.config.js Error: tests\hooks_ex.spec.js: JavaScript files must end with .mjs to use import. at errorWithFile (C:\Users\tom\OneDrive\Documents\projects\vs-code\Playwright-test-training\node_modules@playwright\test\lib\test\util.js:191:10) at Loader._requireOrImport (C:\Users\tom\OneDrive\Documents\projects\vs-code\Playwright-test-training\node_modules@playwright\test\lib\test\loader.js:219:145) at Loader.loadTestFile (C:\Users\tom\OneDrive\Documents\projects\vs-code\Playwright-test-training\node_modules@playwright\test\lib\test\loader.js:130:18)
at Runner._run (C:\Users\tom\OneDrive\Documents\projects\vs-code\Playwright-test-training\node_modules@playwright\test\lib\test\runner.js:219:59)

My hooks.js (now renamed to hooks.mjs but still ge the same error) is:

import { test } from '@playwright/test'

test.beforeEach(async ({ page }) => {
  await page.goto('/webdriver2/sdocs/auth.php');
  await expect(page).toHaveTitle('Automated Tools Test Site');
});

export default test

and main test file hooks_ex.spec.js contains:

const { expect } = require('@playwright/test');
import { test } from './hooks.mjs'

test.describe('Security Tests', () => {

  test('Login Sanity Test @smoke', async ({ page }) => {
    const locator = page.locator('body');
    await expect(locator).toContainText('not Logged in');
    await page.fill('id=username', 'edgewords');
    await page.fill('id=password', 'edgewords123');
    await page.click('text=Submit');
    const logOut = page.locator('text=Log Out');
    await expect(logOut).toBeVisible({ timeout: 10000 });

    await page.close();
  });

any chance you could help? many thanks

Tomsk666 avatar Oct 14 '21 16:10 Tomsk666

As the error says you need to rename hooks_ex.spec.js to use .mjs extension.

yury-s avatar Oct 14 '21 16:10 yury-s

You also should not mix require with import, this worked locally:

// hooks_ex.spec.mjs
import { expect } from '@playwright/test';
import test from './hooks.mjs'

test.describe('Security Tests', () => {

  test('Login Sanity Test @smoke', async ({ page }) => {
    const locator = page.locator('body');
    await expect(locator).toContainText('not Logged in');
    await page.fill('id=username', 'edgewords');
    await page.fill('id=password', 'edgewords123');
    await page.click('text=Submit');
    const logOut = page.locator('text=Log Out');
    await expect(logOut).toBeVisible({ timeout: 10000 });

    await page.close();
  });
  
});

yury-s avatar Oct 14 '21 16:10 yury-s

Brilliant, thanks @yury-s that's fixed it, all working.

Tomsk666 avatar Oct 14 '21 16:10 Tomsk666

@Tomsk666 sorry for misleading you, this will not work as I thought, if you run with --workers=1 the hooks will only run in the test file that happens to be executed first, the rest will run without the hooks.

The best way to achieve what you need today will likely be something like this:

import { sharedBeforeEachFunction } from './hooks.mjs'

test.beforeEach(sharedBeforeEachFunction);

test('Login Sanity Test @smoke', async ({ page }) => {
});

Unfortunately this requires calling test.beforeEach in each test file.

yury-s avatar Oct 14 '21 19:10 yury-s

Another way (more convoluted but also more idiomatic to playwright) to do this is to define your beforeAll/Each methods as fixtures that run automatically. The documentation covering this is quite scarce at the moment but if it works for you we can add some examples there.

// fixtures.js
const base = require('@playwright/test');

module.exports = base.test.extend({
  sharedBeforeAll: [ async ({}, use) => {
    console.log('Before All');
    await use();
    console.log('After All');
  }, { scope: 'worker', auto: true } ],  // starts automatically for every worker - we pass "auto" for that.

  sharedBeforeEach: [ async ({}, use) => {
    console.log('Before Each');
    await use();
    console.log('After Each');
  }, { scope: 'test', auto: true } ],  // starts automatically for every test - we pass "auto" for that.
});

The tests files will look like this:

// @ts-check
const test = require('./fixtures');

test('basic test 1', async ({ page }) => {
});

This is not completely equivalent to beforeAll() in every file since it will run once per worker process (see this doc to learn mode about playwright test runner model) but it may be even better for you depending on the use case.

yury-s avatar Oct 14 '21 20:10 yury-s

Thanks @yury-s I'll try some of these options out & see how I get on. Maybe we could add it as a feature request, as many other frameworks like Mocha, Cucumber, Cypress have global hooks. It'd be great just to put all the hooks in one file say called hooks.js, then in the Playwright config be able to have a parameter like globalHooks: './hooks.js' that would then mean all tests would use the global before/afterEach. Even better would be that if you then defined a local beforeEach in a test file, then that would have precendece over the global, so you had both options. As you can tell I'm not a developer, so no idea how hard this would be to implement. Anyway thanks for your help, really enjoying & impressed with Playwright.

Tomsk666 avatar Oct 15 '21 09:10 Tomsk666

Maybe we could add it as a feature request,

Changed this issue to a feature request, let's see if there is enough upvotes to implement it.

yury-s avatar Oct 15 '21 18:10 yury-s

I will support the global hook feature. global hook is easier to use than the fixture pattern. I am willing to contribute this feature.

F3n67u avatar Oct 16 '21 01:10 F3n67u

@yury-s that would be great to have this feature, I have tried using a beforeAll in a separate file as you recommended but if the test fails the retry will not call this beforeAll because the retry is only for the current spec file. The same problem happened adding in a Playwright fixture

cunhabruno avatar Oct 30 '21 10:10 cunhabruno

Waiting for any update on this feature request 💯

DamayantiRathore avatar Nov 24 '21 09:11 DamayantiRathore

@yury-s Any plan to implement this feature...it would be great to have global hooks for easier handling

saurabh4888 avatar May 24 '22 06:05 saurabh4888

Up

lifeart avatar Sep 20 '22 18:09 lifeart

Is there a plan to implement this feature? It would be super helpful

skozin-tns avatar Nov 10 '22 16:11 skozin-tns

this is really needed

liranelisha avatar Dec 19 '22 05:12 liranelisha

We had some trouble implementing a global beforeEach, using the initial suggestion here. It only ran before some early tests (but not once per worker, perhaps once per core regardless of worker-setting?).

However it looks like we have now found a solution based on the ideas here:

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 
    })

    // Here we can enable logging of all requests, which is useful to see sometimes.
    // We also block some unnecessary third-party requests to speed up the tests.
    await page.route('**/*', route => { 
      ...
    })

    use(page)
  },
});

And in our tests we import test from the file above instead of from '@playwright/test'. This is of course easy to forget, so a configurable way to do the same thing would be even better.

Krisell avatar Jan 25 '23 07:01 Krisell

Have to agree with @Krisell here. For some reason, my auto fixture for beforeEach only runs one time, instead on each test unless I run the suite in debug mode...not sure why that makes a difference but it does.

Here's my example:

const seedTest = base.extend<{ dbSeed: void }>({
  dbSeed: [async ({  }, use) => {

    execSync("npx prisma db seed");

    await use();

  }, { auto: true, scope: "test" }]
})

Where I have two tests here using the extended fixture:

seedTest.describe("Links", () => {

  seedTest.beforeEach(async ({ page }) => {

    await page.goto("/workspaces/links");

  })

  seedTest("creates a track link setting and sets as default", async ({ page }) => {
    
    // test code
    
  })

  seedTest("deletes a track link setting", async ({ page }) => {

   // test code

  })

})

Which results in my dbSeed command only running once unless I run the tests in debug mode, then it runs each time 🤔


Also just want to say that the above solution works for now but would love to use my own auto fixture instead of extending page!

uncvrd avatar Feb 27 '23 07:02 uncvrd

Any updates on this? Would love this feature

sbousamra avatar Jun 30 '23 00:06 sbousamra

+1 for this

taramk avatar Jul 20 '23 18:07 taramk

+1

aaron-hays avatar Aug 03 '23 19:08 aaron-hays

+1

anhskrttt avatar Aug 24 '23 08:08 anhskrttt

+1

JanMosigItemis avatar Sep 25 '23 09:09 JanMosigItemis

yury-s This is a highly recommended feature for all tests, Please add in the feature quickly. UPVOTES (1000)

iburahim786 avatar Oct 15 '23 21:10 iburahim786

This feature would help a lot, hope it will be implemented soon.

fmagaldea avatar Oct 18 '23 16:10 fmagaldea

It would be great if you implement global beforeEach and afterEach.

aMerkato avatar Dec 04 '23 12:12 aMerkato

+1

BaneVredniMrav avatar Dec 25 '23 21:12 BaneVredniMrav

+1

Studigrad avatar Jan 17 '24 12:01 Studigrad

Following this, I was able to get essentially the equivalent of a beforeEach and afterEach for each test case (even with just 1 worker), so thought I'd post it here for others. Hope it helps!

SilverMaiden avatar Mar 12 '24 21:03 SilverMaiden

+1

samlader avatar Apr 15 '24 10:04 samlader

+1

LuisFloresSerna avatar Jun 14 '24 18:06 LuisFloresSerna