3

Personally I see playwright as a tool that goes into the direction of system/end-to-end tests. So I used playwright + jest to build-up user journeys and integrated them in a CI/CD process.

Since playwright created it's own test runner with useful features like taking videos and trace on failures, it would make sense to switch from jest to @playwright/test. On their home page playwright recommends to use test fixtures so I definitly want to include them in the switch.

In the following I will take amazon as an example.

With playwright + jest the first thing that I did was to create a function for generic setup of the environment:

function setupSuite({ browserOptions, userConfig }){
  // create playwright browser and use browserOptions as overrides
  // create page from browser
  // register self-implemented trace and screenshot reporters to page
  // go to baseUrl of current environment (e.g. local, branch etc..)
  // click on cookie banner, since it's blocking the UI
  // create a user based on userConfig (e.g. user has amazon prime user? payment options? etc.)
  // return { browser, page, user, ... }
}

And of course a function to clean up everything again:

function teardownSuite(suite){
  // close browser
  // delete user
  // etc..
}

Then I would use a file for each user journey. In case of amazon a user journey could be the successful processing of an order:

describe("Successful Order", () => {
  let suite

  beforeAll(async () => {
    const userConfig = { isPrime: false, paymentOptions: [ "paypal", "visa" ] }
    suite = await setupBrowser({ userConfig })
    
    // I actually extracted that logic in a function to be able to use it in other tests too,
    // but just want to make clear whats happening here
    const { page, user } = suite
    await page.fill(".login-username-input", user.username)
    await page.fill(".login-password-input", user.password)
    await page.click(".login-submit-button")
  })

  afterAll(() => teardownSuite(suite))

  test("search for toothbrush with suggestions", async () => {
     const { page } = suite
     await page.fill(".search-input", "tooth")
     await page.click("text='toothbrush")
     // hit enter
     // do some assertions to check if the search was really successful
  })
  
  test("click on first item and add to chart", async () => {
     // page actions and assertions
  })

  test("go back, click on second item and add to chart", async () => {
     // page actions and assertions
  })

  test("go to chart and pay", async () => {
     // page actions and assertions
  })

  test("check order confirmation mail", async () => {
     // page actions and assertions
  })
})

As you can see I split up my test in logical parts to make it more readable and also to see at which step (test block) it failed.

What would be the best way to migrate this to @playwright/test + fixtures?

  • How would you migrate setupSuite / teardownSuite ? Yes you could use a fixture, but setupSuite expects arguments like the userConfig . Is it possible to have parameterized fixtures?
  • How would you structure tests with fixtures? If you want to simulate complete user journey the tests are getting bigger than just testing a login for example. A test block would then have a lot of lines without the possibility to structure them.
  • Is it possible to setup a page so it's shared accross all tests? The beforeAll hook doesn't receive any page and the each test block always receives its own page. This means that there is no connection between test blocks. If you create a page manually in beforeAll and use the same page instance in every test it would probably be a bad practice and video and tracing would probably not work.. so what can be done here?
  • Are user journey like tests actually bad? I feel like they can't be combined well with the fixture approach of playwright as mentioned in the docs. Fixtures in playwright feel like very data-driven which doesn't really fit to end-to-end testing IMO.
ysfaran
  • 5,189
  • 3
  • 21
  • 51

1 Answers1

0

How would you migrate setupSuite / teardownSuite ? Yes you could use a fixture, but setupSuite expects arguments like the userConfig . Is it possible to have parameterized fixtures?

Playwright Test doesn't natively support parameterized fixtures. But you can accomplish this by creating dynamic fixtures. With dynamic fixtures, you can pass configuration data (like userConfig) that will influence the setup of the fixtures.

Below is an example of how you might structure this in your test file:

const test = require('@playwright/test');

// Define a fixture that depends on the 'browser' fixture provided by Playwright Test.
// This fixture reads test-level options, so you can customize the behavior per test.
test.extend({
  setupSuite: async ({ browser, testInfo }, use) => {
    const userConfig = testInfo.userData || {};
    // create playwright browser and use browserOptions as overrides
    // create page from browser
    // register self-implemented trace and screenshot reporters to page
    // go to baseUrl of current environment (e.g. local, branch etc..)
    // click on cookie banner, since it's blocking the UI
    // create a user based on userConfig (e.g. user has amazon prime user? payment options? etc.)
    const suite = { /* browser, page, user, ... */ };
    await use(suite);
    // teardownSuite logic goes here, e.g. close browser, delete user, etc..
  },
});

test('my test', async ({ setupSuite }) => {
  // Here, setupSuite has the value provided by the fixture.
}, { userData: { isPrime: false, paymentOptions: ["paypal", "visa"] } });  // Test-level option to customize user.

How would you structure tests with fixtures? If you want to simulate complete user journey the tests are getting bigger than just testing a login for example. A test block would then have a lot of lines without the possibility to structure them.

The key is to extract sequences of actions into their own helper functions. Think of each function as a chapter in your user journey novel. That way, your tests can read more like a table of contents, where each step is a call to one of your helper functions.

Here's a quick example of what I mean:

// helper functions
async function loginUser(page, user) {
  await page.fill(".login-username-input", user.username);
  await page.fill(".login-password-input", user.password);
  await page.click(".login-submit-button");
}

async function searchForProduct(page, productName) {
  await page.fill(".search-input", productName);
  await page.click("text='" + productName + "'");
}

// actual test
test('Successful Order', async ({ page, user }) => {
  await loginUser(page, user);
  await searchForProduct(page, 'toothbrush');
  // add more actions as necessary, each in their own function
});

Each helper function takes care of one part of the user journey, making your tests easier to read and maintain. And if you need to change a step in the journey, you just update the corresponding helper function and all the tests that use it get updated too.

Is it possible to setup a page so it's shared accross all tests? The beforeAll hook doesn't receive any page and the each test block always receives its own page. This means that there is no connection between test blocks. If you create a page manually in beforeAll and use the same page instance in every test it would probably be a bad practice and video and tracing would probably not work.. so what can be done here?

If you've got a bunch of steps that need to happen before every test, like logging in or setting up some state, you could stick those in a helper function and call it at the start of each test. Or if your tests really need to share a lot of state, you might just need to make them one big test.

Are user journey like tests actually bad? I feel like they can't be combined well with the fixture approach of playwright as mentioned in the docs. Fixtures in playwright feel like very data-driven which doesn't really fit to end-to-end testing IMO.

Nah, user journey tests aren't bad at all! They're actually super useful because they replicate how real users interact with your app. They help you catch problems that unit tests or isolated component tests might miss. So, they're definitely good to have in your testing toolkit.

Luc Gagan
  • 301
  • 9