diff --git a/plugins/woocommerce/changelog/update-product-editor-polish-e2e-tests b/plugins/woocommerce/changelog/update-product-editor-polish-e2e-tests new file mode 100644 index 00000000000..d77dec7c2da --- /dev/null +++ b/plugins/woocommerce/changelog/update-product-editor-polish-e2e-tests @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Product Editor: improve E2E tests. Test the `+3 More` item label in the Organization tab diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/create-variable-product-block-editor.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/create-variable-product-block-editor.spec.js index 5b6895b2d48..5599d2f8532 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/create-variable-product-block-editor.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/create-variable-product-block-editor.spec.js @@ -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 diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/data/tabs.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/data/tabs.js new file mode 100644 index 00000000000..6f6765f2a16 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/data/tabs.js @@ -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; diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/fixtures/attributes.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/fixtures/attributes.js new file mode 100644 index 00000000000..33edc3764a8 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/fixtures/attributes.js @@ -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; diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/helpers/confirm-global-attributes-loaded.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/helpers/confirm-global-attributes-loaded.js new file mode 100644 index 00000000000..bc164a51912 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/helpers/confirm-global-attributes-loaded.js @@ -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' } ); +} diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/product-attributes-block-editor.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/product-attributes-block-editor.spec.js index 559aad21670..2e84923709e 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/product-attributes-block-editor.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/product-attributes-block-editor.spec.js @@ -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 } ), } );