Product Editor: improve E2E tests. Test the `+3 More` item label in the Organization tab (#48891)

* introduce attributes fixture

* expose the tabs from the new data/ folder

* introduce confirmGlobalAttributesLoaded helper

* reuse confirmGlobalAttributesLoaded helper

* reuse attributes fixture

* check the `+3 More` item label

* changelog

* not only this test

* rename to waitForGlobalAttributesLoaded
This commit is contained in:
Damián Suárez 2024-06-28 07:58:22 +01:00 committed by GitHub
parent 66ae029f70
commit 38390ab6e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 135 additions and 89 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
Product Editor: improve E2E tests. Test the `+3 More` item label in the Organization tab

View File

@ -8,6 +8,11 @@ const {
} = require( '../../../../utils/product-block-editor' );
const { variableProducts: utils } = require( '../../../../utils' );
const attributes = require( './fixtures/attributes' );
const tabs = require( './data/tabs' );
const {
waitForGlobalAttributesLoaded,
} = require( './helpers/confirm-global-attributes-loaded' );
const {
createVariableProduct,
@ -26,48 +31,18 @@ const productData = {
summary: 'This is a product summary',
};
const attributesData = {
name: 'Size',
terms: [
{
name: 'Large',
slug: 'large',
},
{
name: 'Extra Large',
slug: 'extra-large',
},
{
name: 'Extra Extra Large',
slug: 'extra-extra-large',
},
],
};
const sizeAttribute = attributes.find(
( attribute ) => attribute.name === 'Size'
);
const tabs = [
{
name: 'General',
noteText:
"This product has options, such as size or color. You can manage each variation's images, downloads, and other details individually.",
},
{
name: 'Inventory',
noteText:
"This product has options, such as size or color. You can now manage each variation's inventory and other details individually.",
},
{
name: 'Shipping',
noteText:
"This product has options, such as size or color. You can now manage each variation's shipping settings and other details individually.",
},
];
const termsLength = sizeAttribute.terms.length;
let productId_editVariations,
productId_deleteVariations,
productId_singleVariation;
test.describe( 'Variations tab', { tag: '@gutenberg' }, () => {
test.describe( 'Create variable product', () => {
test.describe( 'Create variable products', () => {
test.beforeAll( async ( { browser } ) => {
productId_editVariations = await createVariableProduct(
productAttributes
@ -132,31 +107,18 @@ test.describe( 'Variations tab', { tag: '@gutenberg' }, () => {
await page.waitForLoadState( 'domcontentloaded' );
/*
* AttributeTableRow is the row that contains
* the attribute name and the options (terms).
*/
const rowSelector =
'.woocommerce-new-attribute-modal__table-row';
/*
* Check the app loads the attributes,
* based on the Spinner visibility.
*/
const spinnerLocator = page.locator(
`${ rowSelector } .components-spinner`
);
await spinnerLocator.waitFor( {
state: 'visible',
} );
await spinnerLocator.waitFor( { state: 'hidden' } );
await waitForGlobalAttributesLoaded( page );
// Attribute combobox input
const attributeInputLocator = page.locator(
'input[aria-describedby^="components-form-token-suggestions-howto-combobox-control"]'
);
await attributeInputLocator.fill( attributesData.name );
await attributeInputLocator.fill( sizeAttribute.name );
await page.locator( 'text=Create "Size"' ).click();
@ -166,7 +128,7 @@ test.describe( 'Variations tab', { tag: '@gutenberg' }, () => {
response
.url()
.includes(
`wp-json/wc/v3/products/attributes?name=${ attributesData.name }&generate_slug=true`
`wp-json/wc/v3/products/attributes?name=${ sizeAttribute.name }&generate_slug=true`
) && response.status() === 201
);
@ -183,7 +145,7 @@ test.describe( 'Variations tab', { tag: '@gutenberg' }, () => {
'input[id^="components-form-token-input-"]'
);
for ( const term of attributesData.terms ) {
for ( const term of sizeAttribute.terms ) {
// Fill the input field with the option
await FormTokenFieldInputLocator.fill( term.name );
await FormTokenFieldInputLocator.press( 'Enter' );
@ -228,6 +190,7 @@ test.describe( 'Variations tab', { tag: '@gutenberg' }, () => {
} );
}
// Wait for the last term to be validated/added
await expect( page.locator( '.is-validating' ) ).toBeHidden();
await page
@ -241,7 +204,7 @@ test.describe( 'Variations tab', { tag: '@gutenberg' }, () => {
await test.step( 'Add prices to variations', async () => {
await expect(
page.getByText(
'3 variations do not have prices. Variations that do not have prices will not be visible to customers.Set prices'
`${ termsLength } variations do not have prices. Variations that do not have prices will not be visible to customers.Set prices`
)
).toBeVisible();
@ -255,10 +218,12 @@ test.describe( 'Variations tab', { tag: '@gutenberg' }, () => {
await expect(
page.getByLabel( 'Dismiss this notice' )
).toContainText( '3 variations updated.' );
).toContainText( `${ termsLength } variations updated.` );
await expect(
page.getByRole( 'button', { name: 'Select all (3)' } )
page.getByRole( 'button', {
name: `Select all (${ termsLength })`,
} )
).toBeVisible();
} );
@ -282,7 +247,7 @@ test.describe( 'Variations tab', { tag: '@gutenberg' }, () => {
// Verify that the first message is the expected one
await expect( snackbarLocator.nth( 0 ) ).toHaveText(
`${ attributesData.terms.length } variations updated.`
`${ sizeAttribute.terms.length } variations updated.`
);
// Verify that the second message is the expected one

View File

@ -0,0 +1,23 @@
/*
* Tabs (groups) define the main menu (header) of the product edit page.
* Each tab has a name and a note text that is displayed below the tab name.
*/
const tabs = [
{
name: 'General',
noteText:
"This product has options, such as size or color. You can manage each variation's images, downloads, and other details individually.",
},
{
name: 'Inventory',
noteText:
"This product has options, such as size or color. You can now manage each variation's inventory and other details individually.",
},
{
name: 'Shipping',
noteText:
"This product has options, such as size or color. You can now manage each variation's shipping settings and other details individually.",
},
];
module.exports = tabs;

View File

@ -0,0 +1,35 @@
/*
* Attributes data
* These attributes represent the attributes
* that the user can assign to a product.
*/
const attributes = [
{
name: 'Color',
terms: [
{ name: 'Red', slug: 'red' },
{ name: 'Blue', slug: 'blue' },
{ name: 'Green', slug: 'green' },
],
},
{
name: 'Size',
terms: [
{ name: 'Small', slug: 'small' },
{ name: 'Medium', slug: 'medium' },
{ name: 'Large', slug: 'large' },
{ name: 'Extra Large', slug: 'extra-large' },
{ name: 'Extra Extra Large', slug: 'extra-extra-large' },
],
},
{
name: 'Style',
terms: [
{ name: 'Modern', slug: 'modern' },
{ name: 'Classic', slug: 'classic' },
{ name: 'Vintage', slug: 'vintage' },
],
},
];
module.exports = attributes;

View File

@ -0,0 +1,18 @@
/**
* Waits for the spinner to appear and disappear,
* indicating that attributes have been loaded.
*
* This function confirms that the attributes have been
* loaded by waiting for a spinner to appear and then disappear.
* The spinner indicates that a loading process is occurring,
* and its disappearance indicates that the process is complete.
*
* @param {Object} page - The Playwright Page object.
*/
export async function waitForGlobalAttributesLoaded( page ) {
const spinnerLocator = page.locator(
'.woocommerce-new-attribute-modal__table-row .components-spinner'
);
await spinnerLocator.waitFor( { state: 'visible' } );
await spinnerLocator.waitFor( { state: 'hidden' } );
}

View File

@ -4,6 +4,10 @@ const {
const { expect } = require( '../../../../fixtures/fixtures' );
const { updateProduct } = require( '../../../../utils/product-block-editor' );
const { clickOnTab } = require( '../../../../utils/simple-products' );
const attributesData = require( './fixtures/attributes' );
const {
waitForGlobalAttributesLoaded,
} = require( './helpers/confirm-global-attributes-loaded' );
async function waitForAttributeList( page ) {
// The list child is different in case there are no results versus when there already are some attributes, so we need to wait for either one to be visible.
@ -99,22 +103,6 @@ test(
'add local attribute (with terms) to the Product',
{ tag: '@gutenberg' },
async ( { page, product } ) => {
const newAttributes = [
{
name: 'Color',
terms: [ 'Red', 'Blue', 'Green' ],
},
{
name: 'Size',
terms: [ 'Small', 'Medium', 'Large' ],
},
{
name: 'Style',
terms: [ 'Modern', 'Classic', 'Vintage' ],
},
];
await test.step( 'go to product editor -> Organization tab -> Click on `Add new`', async () => {
await page.goto(
`wp-admin/post.php?post=${ product.id }&action=edit`
@ -147,15 +135,9 @@ test(
* First, check the app loads the attributes,
* based on the Spinner visibility.
*/
const spinnerLocator = attributeRowsLocator.locator(
'.components-spinner'
);
await spinnerLocator.waitFor( {
state: 'visible',
} );
await spinnerLocator.waitFor( { state: 'hidden' } );
await waitForGlobalAttributesLoaded( page );
for ( const term of newAttributes ) {
for ( const attribute of attributesData ) {
const attributeRowLocator = attributeRowsLocator.last();
const attributeComboboxLocator = attributeRowLocator
@ -165,8 +147,10 @@ test(
.last();
// Create new (local) product attribute.
await attributeComboboxLocator.fill( term.name );
await page.locator( `text=Create "${ term.name }"` ).click();
await attributeComboboxLocator.fill( attribute.name );
await page
.locator( `text=Create "${ attribute.name }"` )
.click();
const FormTokenFieldLocator = attributeRowLocator.locator(
'td.woocommerce-new-attribute-modal__table-attribute-value-column'
@ -179,8 +163,8 @@ test(
);
// Add terms to the attribute.
for ( const attributeTerm of term.terms ) {
await FormTokenFieldInputLocator.fill( attributeTerm );
for ( const term of attribute.terms ) {
await FormTokenFieldInputLocator.fill( term.name );
await FormTokenFieldInputLocator.press( 'Enter' );
}
@ -203,25 +187,42 @@ test(
const attributeRowsLocator =
attributesListLocator.getByRole( 'listitem' );
for ( const attribute of newAttributes ) {
for ( const attribute of attributesData ) {
const attributeRowLocator = attributeRowsLocator.filter( {
has: page.getByText( attribute.name, { exact: true } ),
} );
await expect( attributeRowLocator ).toBeVisible();
for ( const term of attribute.terms ) {
// UI only shows 3 terms, so we need to check if the term is visible.
const shownTerms = attribute.terms.slice( 0, 3 ).entries();
// Check if there are more terms than three.
const moreThanThreeTerms = attribute.terms.length > 3;
for ( const [ index, term ] of shownTerms ) {
/*
* Disabling the eslint rule because the text
* is different when there are more than three terms.
*/
const termLabel =
// eslint-disable-next-line playwright/no-conditional-in-test
moreThanThreeTerms && index === 2
? '+ 3 more'
: term.name;
// Pick the term element/locator
const termLocator = attributeRowLocator
.locator( `[aria-hidden="true"]` )
.filter( {
has: page.getByText( term ),
has: page.getByText( termLabel, {
exact: true,
} ),
} );
// Verify the term is visible
await expect( termLocator ).toBeVisible();
// Verify the term text
await expect( termLocator ).toContainText( term );
await expect( termLocator ).toContainText( termLabel );
}
}
} );
@ -235,7 +236,7 @@ test(
await page.goto( product.permalink );
// Verify attributes in store frontend
for ( const attribute of newAttributes ) {
for ( const attribute of attributesData ) {
const item = page.getByRole( 'row' ).filter( {
has: page.getByText( attribute.name, { exact: true } ),
} );