Steerpike
Steerpike

Reputation: 1863

How to share state between Playwright tests?

I'm learning Playwright and JavaScript concurrently so this may be an elementary question - I'm wondering how people would recommend sharing state - variable customerId in this case - between tests.

Example:

test.describe.only('Generate a new customer', () => {
  let customerId
  let baseUrl = process.env.SHOP_URL
  
  test('Create new customer', async ({ request }) => {
    const response = await request.post(baseUrl +    `/shopify/v5/customer`, {})
    
    const responseBody = JSON.parse(await response.text())
    expect(response.status()).toBe(200)
    customerId = responseBody.customerId //need to persist customerId to pass into following test

  })

  test('Update customer details', async ({ request }) => {
     const response = await request.post(baseUrl +    `/shopify/v5/customer/update`, {})
      {
        data: {
          customerId: customerId, //customerId is undefined here
          name: "Fred"
        },
      }
    )
    expect(response.status()).toBe(200)
  })

the customerId is clearly out of scope in the second test. I will probably refactor these to use a library such as Axios eventually because I am using the Playwright tests to generate data - I'm not actually testing the api here. In the meantime I just need customerId to be persisted in subsequent api calls.

Upvotes: 9

Views: 17610

Answers (4)

Vishal Aggarwal
Vishal Aggarwal

Reputation: 4222

Use custom fixture to share state:

Instead of relying on global variables or workarounds, we can create a custom fixture to share the state between tests. This approach is more clean,maintainable, reusable therefore adheres to best practices.

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

// Step 1: Define a custom fixture for shared state
const sharedStateFixture = test.extend({
  sharedState: async ({}, use) => {
    const state = {}; // Object to hold shared state
    await use(state); // Pass the state to the tests
  },
});

// Step 2: Use the custom fixture in your tests
sharedStateFixture.describe('Tests with shared state', () => {
  sharedStateFixture('Test 1: Set shared state', async ({ sharedState }) => {
    sharedState.user = { name: 'John Doe', age: 30 }; // Set shared state
    console.log('Test 1: Shared state set', sharedState);
  });

  sharedStateFixture('Test 2: Use shared state', async ({ sharedState }) => {
    console.log('Test 2: Shared state used', sharedState);
    console.log('User name:', sharedState.user.name); // Access shared state
  });

  sharedStateFixture('Test 3: Modify shared state', async ({ sharedState }) => {
    sharedState.user.age = 31; // Modify shared state
    console.log('Test 3: Shared state modified', sharedState);
  });
});

Upvotes: 1

Lastik
Lastik

Reputation: 981

It's rarely true that you only need one test for specific action. For your example, it's definitely gonna be more than one test for create and update operations by the time when you set of tests would grow large.

Consider that you have several test for customer creation. It's better to have them grouped together in a one suite or in a related set of test suites. The same goes for the update operation.

As for sharing data between tests, you need to have all the shared data prepared in the beforeAll / beforeEach or in fixtures. At first build Test API, with basic operations.


class CustomerAPI {
    async createCustomer(){
        const response = await request.post(baseUrl + `/shopify/v5/customer`, {});

        const responseBody = JSON.parse(await response.text());
        expect(response.status()).toBe(200);
        return responseBody;
    }

    async updateCustomer(){
        const response = await request.post(baseUrl + `/shopify/v5/customer/update`,
        {
            data: {
                customerId: customerId, //customerId is undefined here
                    name: "Fred"
            },
        });
        expect(response.status()).toBe(200);
        return response;
    }
}

Of course it's very basic implementation example lacking any actual implementation details.

Then in your tests you can utilize this API, e.g. by using fixtures

type TestFixtures = {
    customerAPI: CustomerAPI;
};


// 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.
export const test = base.extend<TestFixtures>({
    customerAPI: async ({ page }, use) => {
        // Set up the fixture.
        const customerAPI = new CustomerAPI(page);
        await customerAPI.createCustomer();

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

        // Clean up the fixture.
        await customerAPI.deleteCustomer();
    }
});
export { expect } from '@playwright/test';

You can make CustomerAPI stateful and store info about created customers.

If you don't like fixtures approach, you can go with beforeAll / afterAll route - use the same CustomerAPI in beforeAll, create all the needed data and then clean it in afterAll.

Upvotes: 5

Yury Semikhatsky
Yury Semikhatsky

Reputation: 2354

To make your example work you need to run the tests in serial mode, something like this will work:

test.describe.serial('Generate a new customer', () => {
  let customerId
  let baseUrl = process.env.SHOP_URL
  
  test('Create new customer', async ({ request }) => {
    const response = await request.post(baseUrl +    `/shopify/v5/customer`, {})
    
    const responseBody = JSON.parse(await response.text())
    expect(response.status()).toBe(200)
    customerId = responseBody.customerId //need to persist customerId to pass into following test

  })

  test('Update customer details', async ({ request }) => {
     const response = await request.post(baseUrl +    `/shopify/v5/customer/update`, {})
      {
        data: {
          customerId: customerId, //customerId is undefined here
          name: "Fred"
        },
      }
    )
    expect(response.status()).toBe(200)
  })
});

Upvotes: 10

senaid
senaid

Reputation: 650

That is anti-pattern, tests should be independent especially in playwright where tests run in parallel by default:

https://playwright.dev/docs/test-parallel

You can merge those two tests into one test.

If You still want to go that way I guess You can use fixtures or hooks to make it work, here are examples:

https://playwright.dev/docs/test-fixtures#without-fixtures

Upvotes: 3

Related Questions