hasan.in
hasan.in

Reputation: 705

How to compose fixtures in playwright

I want to compose fixtures. The first fixture should always be available (think of it as base class). second fixture will vary in different test files (think of it as derived class) I tried following code and it's working as I was expecting. Is this ok to follow this approach or any better option available?

//baseFixture.js
import { test as base} from '@playwright/test';
interface MyFixtures {
  fixture1: string;
}

export const test = base.extend<MyFixtures>({
  fixture1: "fixture-one"
}, );

//derivedFixture.js

import {test as test1} from 'baseFixture'
interface MyFixtures2 {
  fixture2: string;
}

export const test = test1.extend<MyFixtures2>({
  fixture2: "fixture-two"
}, );


//in test_file.js

import {test} from 'derivedFixture'

  test('should allow me use composed fixture', async ({ page, fixture1, fixture2 }) => {
     console.log(`from first fixture ${fixture1}`)
     console.log(`from second fixture ${fixture2}`)
  });

Upvotes: 0

Views: 9861

Answers (2)

Sergei Voronezhskii
Sergei Voronezhskii

Reputation: 2372

My approach is to use the base fixture as a dependent fixture in a derivative fixture:

import { test as base } from "@playwright/test"
interface MyFixtures1 {
    fixture1: string
}

export const testBase = base.extend<{}, MyFixtures1>({
    fixture1: [
        async ({}, use) => {
            console.log("fixture1 setup once per worker")
            use("one")
            console.log("fixture1 teardown once per worker")
        },
        { scope: "worker" }
    ]
})

interface MyFixtures2 {
    fixture2: string
}

export const test = testBase.extend<MyFixtures2>({
    fixture2: async ({ fixture1 }, use) => {
        console.log("fixture2 setup for each test")
        use(`two-${fixture1}`)
        console.log("fixture2 teardown for each test")
    },
})

test("should allow me use composed fixture", async ({ fixture1, fixture2 }) => {
    console.log(`from first fixture ${fixture1}`)
    console.log(`from second fixture ${fixture2}`)
})

test("should base", async ({ fixture1 }) => {
    console.log(`from first fixture ${fixture1}`)
})

test("should derived", async ({ fixture2 }) => {
    console.log(`from second fixture ${fixture2}`)
})

More info about how to use fixtures in docs

Upvotes: 1

senaid
senaid

Reputation: 650

Seems to me that you are using fixtures like POMs and you are overengineering tests. If it works for you and depending on what you want, then use it. If my assumption is correct instead of passing fixtures to another fixture pass POMs and you can even perform steps here to get that page into certain state and here is example from playwright page:

// my-test.js
const base = require('@playwright/test');
const { TodoPage } = require('./todo-page');
const { SettingsPage } = require('./settings-page');

// Extend base test by providing "todoPage" and "settingsPage".
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
exports.test = base.test.extend({
  todoPage: async ({ page }, use) => {
    // Set up the fixture.
    const todoPage = new TodoPage(page);
    await todoPage.goto();
    await todoPage.addToDo('item1');
    await todoPage.addToDo('item2');

    // Use the fixture value in the test.
    await use(todoPage);

    // Clean up the fixture.
    await todoPage.removeAll();
  },

  settingsPage: async ({ page }, use) => {
    await use(new SettingsPage(page));
  },
});
exports.expect = base.expect;

Then in your test simply pass {todoPage} or {settingsPage} or both:

const { test, expect } = require('./my-test');

test.beforeEach(async ({ settingsPage }) => {
  await settingsPage.switchToDarkMode();
});

test('basic test', async ({ todoPage, page }) => {
  await todoPage.addToDo('something nice');
  await expect(page.locator('.todo-item')).toContainText(['something nice']);
});

Also you can chain your fixtures here and reuse them, for eg. you could pass todoPage to settingsPage fixture:

  settingsPage: async ({ todoPage}, use) => {
    await use(new SettingsPage(page));
  },

This way everything in todoPage will be executed then settingsPage and this is what you pass to your test, and I guess this is what you were trying to achive.

Upvotes: 3

Related Questions