[e2e tests] Fix flakiness and refactor create-order spec (#51292)

This commit is contained in:
Adrian Moldovan 2024-09-11 19:10:00 +01:00 committed by GitHub
parent 0e2258b57d
commit 0c8a8bd6cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 275 additions and 294 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev

View File

@ -1,10 +1,6 @@
const { test, expect } = require( '@playwright/test' ); const { test: baseTest, expect } = require( '../../fixtures/fixtures' );
const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const { random } = require( '../../utils/helpers' );
const simpleProductName = 'Add new order simple product';
const variableProductName = 'Add new order variable product';
const externalProductName = 'Add new order external product';
const groupedProductName = 'Add new order grouped product';
const taxClasses = [ const taxClasses = [
{ {
name: 'Tax Class Simple', name: 'Tax Class Simple',
@ -37,52 +33,110 @@ const taxRates = [
}, },
]; ];
const taxTotals = [ '10.00', '20.00', '240.00' ]; const taxTotals = [ '10.00', '20.00', '240.00' ];
let simpleProductId,
variableProductId,
externalProductId,
subProductAId,
subProductBId,
groupedProductId,
customerId,
orderId;
test.describe( async function getOrderIdFromPage( page ) {
'WooCommerce Orders > Add new order', // get order ID from the page
{ tag: [ '@services', '@hpos' ] }, const orderText = await page
() => { .locator( 'h2.woocommerce-order-data__heading' )
test.use( { storageState: process.env.ADMINSTATE } ); .textContent();
const parts = orderText.match( /([0-9])\w+/ );
return parts[ 0 ];
}
test.beforeAll( async ( { baseURL } ) => { async function addProductToOrder( page, product, quantity ) {
const api = new wcApi( { await page.getByRole( 'button', { name: 'Add item(s)' } ).click();
url: baseURL, await page.getByRole( 'button', { name: 'Add product(s)' } ).click();
consumerKey: process.env.CONSUMER_KEY, await page.getByText( 'Search for a product…' ).click();
consumerSecret: process.env.CONSUMER_SECRET, await page.locator( 'span > .select2-search__field' ).fill( product.name );
version: 'wc/v3', await page.getByRole( 'option', { name: product.name } ).first().click();
} ); await page
// enable taxes on the account .locator( 'tr' )
await api.put( 'settings/general/woocommerce_calc_taxes', { .filter( { hasText: product.name } )
value: 'yes', .getByPlaceholder( '1' )
} ); .fill( quantity.toString() );
// add tax classes await page.locator( '#btn-ok' ).click();
for ( const taxClass of taxClasses ) {
await api.post( 'taxes/classes', taxClass );
} }
// attach rates to the classes
for ( let i = 0; i < taxRates.length; i++ ) { const test = baseTest.extend( {
await api.post( 'taxes', taxRates[ i ] ); storageState: process.env.ADMINSTATE,
order: async ( { api }, use ) => {
const order = {};
await use( order );
if ( order.id ) {
await api.delete( `orders/${ order.id }`, { force: true } );
} }
// create simple product },
customer: async ( { api }, use ) => {
let customer = {};
const username = `sideshowbob_${ random() }`;
await api
.post( 'customers', {
email: `${ username }@example.com`,
first_name: 'Sideshow',
last_name: 'Bob',
username,
billing: {
first_name: 'Sideshow',
last_name: 'Bob',
company: 'Die Bart Die',
address_1: '123 Fake St',
address_2: '',
city: 'Springfield',
state: 'FL',
postcode: '12345',
country: 'US',
email: `${ username }@example.com`,
phone: '555-555-5556',
},
shipping: {
first_name: 'Sideshow',
last_name: 'Bob',
company: 'Die Bart Die',
address_1: '321 Fake St',
address_2: '',
city: 'Springfield',
state: 'FL',
postcode: '12345',
country: 'US',
},
} )
.then( ( response ) => {
customer = response.data;
} );
await use( customer );
// Cleanup
await api.delete( `customers/${ customer.id }`, { force: true } );
},
simpleProduct: async ( { api }, use ) => {
let product = {};
await api await api
.post( 'products', { .post( 'products', {
name: simpleProductName, name: `Product simple ${ random() }`,
type: 'simple', type: 'simple',
regular_price: '100', regular_price: '100',
tax_class: 'Tax Class Simple', tax_class: 'Tax Class Simple',
} ) } )
.then( ( resp ) => { .then( ( response ) => {
simpleProductId = resp.data.id; product = response.data;
} ); } );
// create variable product
await use( product );
// Cleanup
await api.delete( `products/${ product.id }`, { force: true } );
},
variableProduct: async ( { api }, use ) => {
let product = {};
const variations = [ const variations = [
{ {
regular_price: '100', regular_price: '100',
@ -113,25 +167,36 @@ test.describe(
tax_class: 'Tax Class Variable', tax_class: 'Tax Class Variable',
}, },
]; ];
await api await api
.post( 'products', { .post( 'products', {
name: variableProductName, name: `Product variable ${ random() }`,
type: 'variable', type: 'variable',
tax_class: 'Tax Class Variable', tax_class: 'Tax Class Variable',
} ) } )
.then( ( response ) => { .then( ( response ) => {
variableProductId = response.data.id; product = response.data;
} );
for ( const key in variations ) { for ( const key in variations ) {
api.post( api.post(
`products/${ variableProductId }/variations`, `products/${ product.id }/variations`,
variations[ key ] variations[ key ]
); );
} }
} );
// create external product await use( product );
// Cleanup
await api.delete( `products/${ product.id }`, { force: true } );
},
externalProduct: async ( { api }, use ) => {
let product = {};
await api await api
.post( 'products', { .post( 'products', {
name: externalProductName, name: `Product external ${ random() }`,
regular_price: '800', regular_price: '800',
tax_class: 'Tax Class External', tax_class: 'Tax Class External',
external_url: 'https://wordpress.org/plugins/woocommerce', external_url: 'https://wordpress.org/plugins/woocommerce',
@ -139,9 +204,20 @@ test.describe(
button_text: 'Buy now', button_text: 'Buy now',
} ) } )
.then( ( response ) => { .then( ( response ) => {
externalProductId = response.data.id; product = response.data;
} ); } );
// create grouped product
await use( product );
// Cleanup
await api.delete( `products/${ product.id }`, { force: true } );
},
groupedProduct: async ( { api }, use ) => {
let product = {};
let subProductAId;
let subProductBId;
await api await api
.post( 'products', { .post( 'products', {
name: 'Add-on A', name: 'Add-on A',
@ -160,82 +236,42 @@ test.describe(
} ); } );
await api await api
.post( 'products', { .post( 'products', {
name: groupedProductName, name: `Product grouped ${ random() }`,
regular_price: '29.99', regular_price: '29.99',
grouped_products: [ subProductAId, subProductBId ], grouped_products: [ subProductAId, subProductBId ],
type: 'grouped', type: 'grouped',
} ) } )
.then( ( response ) => { .then( ( response ) => {
groupedProductId = response.data.id; product = response.data;
} );
// create a customer
await api
.post( 'customers', {
email: 'sideshowbob@example.com',
first_name: 'Sideshow',
last_name: 'Bob',
username: 'sideshowbob',
billing: {
first_name: 'Sideshow',
last_name: 'Bob',
company: 'Die Bart Die',
address_1: '123 Fake St',
address_2: '',
city: 'Springfield',
state: 'FL',
postcode: '12345',
country: 'US',
email: 'sideshowbob@example.com',
phone: '555-555-5556',
},
shipping: {
first_name: 'Sideshow',
last_name: 'Bob',
company: 'Die Bart Die',
address_1: '321 Fake St',
address_2: '',
city: 'Springfield',
state: 'FL',
postcode: '12345',
country: 'US',
},
} )
.then( ( response ) => {
customerId = response.data.id;
} );
} ); } );
test.afterEach( async ( { baseURL } ) => { await use( product );
const api = new wcApi( {
url: baseURL, // Cleanup
consumerKey: process.env.CONSUMER_KEY, await api.delete( `products/${ product.id }`, { force: true } );
consumerSecret: process.env.CONSUMER_SECRET, },
version: 'wc/v3',
} ); } );
// clean up order after each test
if ( orderId && orderId !== '' ) { test.describe(
await api.delete( `orders/${ orderId }`, { force: true } ); 'WooCommerce Orders > Add new order',
{ tag: [ '@services', '@hpos' ] },
() => {
test.beforeAll( async ( { api } ) => {
// enable taxes on the account
await api.put( 'settings/general/woocommerce_calc_taxes', {
value: 'yes',
} );
// add tax classes
for ( const taxClass of taxClasses ) {
await api.post( 'taxes/classes', taxClass );
}
// attach rates to the classes
for ( let i = 0; i < taxRates.length; i++ ) {
await api.post( 'taxes', taxRates[ i ] );
} }
} ); } );
test.afterAll( async ( { baseURL } ) => { test.afterAll( async ( { api } ) => {
const api = new wcApi( {
url: baseURL,
consumerKey: process.env.CONSUMER_KEY,
consumerSecret: process.env.CONSUMER_SECRET,
version: 'wc/v3',
} );
// cleans up all products after run
await api.post( 'products/batch', {
delete: [
simpleProductId,
variableProductId,
externalProductId,
subProductAId,
subProductBId,
groupedProductId,
],
} );
// clean up tax classes and rates // clean up tax classes and rates
for ( const { slug } of taxClasses ) { for ( const { slug } of taxClasses ) {
await api await api
@ -258,19 +294,15 @@ test.describe(
await api.put( 'settings/general/woocommerce_calc_taxes', { await api.put( 'settings/general/woocommerce_calc_taxes', {
value: 'no', value: 'no',
} ); } );
// clean up customer
await api.delete( `customers/${ customerId }`, { force: true } );
} ); } );
test( 'can create a simple guest order', async ( { page } ) => { test( 'can create a simple guest order', async ( {
page,
simpleProduct,
order,
} ) => {
await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' ); await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
order.id = await getOrderIdFromPage( page );
// get order ID from the page
const orderText = await page
.locator( 'h2.woocommerce-order-data__heading' )
.textContent();
orderId = orderText.match( /([0-9])\w+/ );
orderId = orderId[ 0 ].toString();
await page await page
.locator( '#order_status' ) .locator( '#order_status' )
@ -334,22 +366,7 @@ test.describe(
.fill( 'Only asked for a slushie' ); .fill( 'Only asked for a slushie' );
// Add a product // Add a product
await page.getByRole( 'button', { name: 'Add item(s)' } ).click(); await addProductToOrder( page, simpleProduct, 2 );
await page
.getByRole( 'button', { name: 'Add product(s)' } )
.click();
await page.getByText( 'Search for a product…' ).click();
await page
.locator( 'span > .select2-search__field' )
.fill( 'Simple' );
await page
.getByRole( 'option', { name: simpleProductName } )
.click();
await page
.getByRole( 'row', { name: '×Add new order simple product' } )
.getByPlaceholder( '1' )
.fill( '2' );
await page.locator( '#btn-ok' ).click();
// Create the order // Create the order
await page.getByRole( 'button', { name: 'Create' } ).click(); await page.getByRole( 'button', { name: 'Create' } ).click();
@ -375,40 +392,26 @@ test.describe(
test( 'can create an order for an existing customer', async ( { test( 'can create an order for an existing customer', async ( {
page, page,
simpleProduct,
customer,
order,
} ) => { } ) => {
await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' ); await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
order.id = await getOrderIdFromPage( page );
// get order ID from the page
const orderText = await page
.locator( 'h2.woocommerce-order-data__heading' )
.textContent();
orderId = orderText.match( /([0-9])\w+/ );
orderId = orderId[ 0 ].toString();
// Select customer // Select customer
await page.getByText( 'Guest' ).click(); await page.getByText( 'Guest' ).click();
await page await page
.locator( 'input[aria-owns="select2-customer_user-results"]' ) .locator( 'input[aria-owns="select2-customer_user-results"]' )
.fill( 'sideshowbob@' ); .fill( customer.username );
await page.getByRole( 'option', { name: 'Sideshow Bob' } ).click(); await page
.getByRole( 'option', {
name: `${ customer.first_name } ${ customer.last_name }`,
} )
.click();
// Add a product // Add a product
await page.getByRole( 'button', { name: 'Add item(s)' } ).click(); await addProductToOrder( page, simpleProduct, 2 );
await page
.getByRole( 'button', { name: 'Add product(s)' } )
.click();
await page.getByText( 'Search for a product…' ).click();
await page
.locator( 'span > .select2-search__field' )
.fill( 'Simple' );
await page
.getByRole( 'option', { name: simpleProductName } )
.click();
await page
.getByRole( 'row', { name: '×Add new order simple product' } )
.getByPlaceholder( '1' )
.fill( '2' );
await page.locator( '#btn-ok' ).click();
// Create the order // Create the order
await page.getByRole( 'button', { name: 'Create' } ).click(); await page.getByRole( 'button', { name: 'Create' } ).click();
@ -431,12 +434,14 @@ test.describe(
// View customer profile // View customer profile
await page.getByRole( 'link', { name: 'Profile →' } ).click(); await page.getByRole( 'link', { name: 'Profile →' } ).click();
await expect( await expect(
page.getByRole( 'heading', { name: 'Edit User sideshowbob' } ) page.getByRole( 'heading', {
name: `Edit User ${ customer.username }`,
} )
).toBeVisible(); ).toBeVisible();
// Go back to the order // Go back to the order
await page.goto( await page.goto(
`wp-admin/admin.php?page=wc-orders&action=edit&id=${ orderId }` `wp-admin/admin.php?page=wc-orders&action=edit&id=${ order.id }`
); );
await page await page
.getByRole( 'link', { .getByRole( 'link', {
@ -449,17 +454,12 @@ test.describe(
await expect( page.getByRole( 'row' ) ).toHaveCount( 3 ); // 1 order and header and footer rows await expect( page.getByRole( 'row' ) ).toHaveCount( 3 ); // 1 order and header and footer rows
} ); } );
test( 'can create new order', async ( { page } ) => { test( 'can create new order', async ( { page, order } ) => {
await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' ); await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
await expect( await expect(
page.locator( 'h1.wp-heading-inline' ) page.locator( 'h1.wp-heading-inline' )
).toContainText( 'Add new order' ); ).toContainText( 'Add new order' );
// get order ID from the page order.id = await getOrderIdFromPage( page );
const orderText = await page
.locator( 'h2.woocommerce-order-data__heading' )
.textContent();
orderId = orderText.match( /([0-9])\w+/ );
orderId = orderId[ 0 ].toString();
await page await page
.locator( '#order_status' ) .locator( '#order_status' )
@ -488,74 +488,51 @@ test.describe(
test( 'can create new complex order with multiple product types & tax classes', async ( { test( 'can create new complex order with multiple product types & tax classes', async ( {
page, page,
simpleProduct,
variableProduct,
externalProduct,
groupedProduct,
order,
} ) => { } ) => {
orderId = '';
await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' ); await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
order.id = await getOrderIdFromPage( page );
// open modal for adding line items // open modal for adding line items
await page.locator( 'button.add-line-item' ).click(); await page.locator( 'button.add-line-item' ).click();
await page.locator( 'button.add-order-item' ).click(); await page.locator( 'button.add-order-item' ).click();
// search for each product to add // search for each product to add
await page.locator( 'text=Search for a product…' ).click(); for ( const product of [
simpleProduct,
variableProduct,
groupedProduct,
externalProduct,
] ) {
await page.getByText( 'Search for a product…' ).click();
await page await page
.locator( '.select2-search--dropdown' ) .locator( 'span > .select2-search__field' )
.getByRole( 'combobox' ) .fill( product.name );
.pressSequentially( simpleProductName );
await page await page
.locator( .getByRole( 'option', { name: product.name } )
'li.select2-results__option.select2-results__option--highlighted' .first()
)
.click();
await page.locator( 'text=Search for a product…' ).click();
await page
.locator( '.select2-search--dropdown' )
.getByRole( 'combobox' )
.pressSequentially( variableProductName );
await page
.locator(
'li.select2-results__option.select2-results__option--highlighted'
)
.click();
await page.locator( 'text=Search for a product…' ).click();
await page
.locator( '.select2-search--dropdown' )
.getByRole( 'combobox' )
.type( groupedProductName );
await page
.locator(
'li.select2-results__option.select2-results__option--highlighted'
)
.click();
await page.locator( 'text=Search for a product…' ).click();
await page
.locator( '.select2-search--dropdown' )
.getByRole( 'combobox' )
.type( externalProductName );
await page
.locator(
'li.select2-results__option.select2-results__option--highlighted'
)
.click(); .click();
}
await page.locator( 'button#btn-ok' ).click(); await page.locator( 'button#btn-ok' ).click();
// assert that products added // assert that products added
await expect( await expect(
page.locator( 'td.name > a >> nth=0' ) page.locator( 'td.name > a >> nth=0' )
).toContainText( simpleProductName ); ).toContainText( simpleProduct.name );
await expect( await expect(
page.locator( 'td.name > a >> nth=1' ) page.locator( 'td.name > a >> nth=1' )
).toContainText( variableProductName ); ).toContainText( variableProduct.name );
await expect( await expect(
page.locator( 'td.name > a >> nth=2' ) page.locator( 'td.name > a >> nth=2' )
).toContainText( groupedProductName ); ).toContainText( groupedProduct.name );
await expect( await expect(
page.locator( 'td.name > a >> nth=3' ) page.locator( 'td.name > a >> nth=3' )
).toContainText( externalProductName ); ).toContainText( externalProduct.name );
// Recalculate taxes // Recalculate taxes
page.on( 'dialog', ( dialog ) => dialog.accept() ); page.on( 'dialog', ( dialog ) => dialog.accept() );