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.
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.
- 1 stackoverflow.com/a/70614296/21881142 This will not work if you have a failed test in between. All data will be lost. – Vladimir Tanov Commented May 11, 2023 at 13:02
4 Answers
Reset to default 9To 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)
})
});
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
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
.
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);
});
});