From 6cb52c00c04723ddcbb1774083336271b3cb6895 Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Tue, 19 Mar 2024 08:59:58 +0100 Subject: [PATCH 1/2] Product Collection: add e2e tests with all product elements included (#45623) * Prepare test cases * Shorten out the test description * Add first test in a post with dummy expect * Verify all content is displayed * Add the test in Product Archive and Home Page * Add changelog * Add tag to Beanie product * Switch to Beanie which is on sale to verify on sale badge * Add comments to explain the expects * Adjust the expected content * Switch to lower case in expect * Switch from woocommerce/product-summary to core/post-excerpt * Adjust products.sh * Improve method waiting for products to show so it;'s deterministic * Refresh locators in template * Remove unnecessary check * Eslint disable: expects are extracted to function so disable eslint compaining there's no expect * Adjust other test after amending products setup * Change the verify happening in a wrong place * Tests adjustments * Revert Blog Home template before performing a test * Fix other tests --- .../tests/e2e/bin/scripts/products.sh | 3 +- .../product-collection.block_theme.spec.ts | 126 +++++++++++++++++- .../product-collection.page.ts | 75 +++++++++-- ...e-tests-with-all-product-elements-included | 4 + 4 files changed, 190 insertions(+), 18 deletions(-) create mode 100644 plugins/woocommerce/changelog/45214-product-collection-add-e2e-tests-with-all-product-elements-included diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/scripts/products.sh b/plugins/woocommerce-blocks/tests/e2e/bin/scripts/products.sh index 63658d69042d..5c616153d02e 100644 --- a/plugins/woocommerce-blocks/tests/e2e/bin/scripts/products.sh +++ b/plugins/woocommerce-blocks/tests/e2e/bin/scripts/products.sh @@ -22,12 +22,13 @@ image3=$(wp post list --post_type=attachment --field=ID --name="hoodie-2.jpg" -- wp post meta update $hoodie_product_id _product_image_gallery "$image1,$image2,$image3" # Create a tag, so we can add tests for tag-related blocks and templates. +beanie_product_id=$(wp post list --post_type=product --field=ID --name="Beanie" --format=ids) tag_id=$(wp wc product_tag create --name="Recommended" --slug="recommended" --description="Curated products selected by our experts" --porcelain --user=1) wp wc product update $hoodie_product_id --tags="[ { \"id\": $tag_id } ]" --user=1 +wp wc product update $beanie_product_id --tags="[ { \"id\": $tag_id } ]" --user=1 # This is a non-hacky work around to set up the cross sells product. cap_product_id=$(wp post list --post_type=product --field=ID --name="Cap" --format=ids) -beanie_product_id=$(wp post list --post_type=product --field=ID --name="Beanie" --format=ids) wp post meta update $beanie_product_id _crosssell_ids "$cap_product_id" # Set a product out of stock diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts index 8baa78eafe56..3af42dc068fb 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts @@ -2,7 +2,7 @@ * External dependencies */ import { test as base, expect } from '@woocommerce/e2e-playwright-utils'; -import type { Request } from '@playwright/test'; +import type { Request, Locator } from '@playwright/test'; /** * Internal dependencies @@ -47,6 +47,108 @@ test.describe( 'Product Collection', () => { await expect( pageObject.addToCartButtons ).toHaveCount( 9 ); } ); + test.describe( 'Renders correctly with all Product Elements', async () => { + const insertProductElements = async ( + pageObject: ProductCollectionPage + ) => { + // By default there are inner blocks: + // - woocommerce/product-image + // - core/post-title + // - woocommerce/product-price + // - woocommerce/product-button + // We're adding remaining ones + const productElements = [ + { name: 'woocommerce/product-rating', attributes: {} }, + { name: 'woocommerce/product-sku', attributes: {} }, + { name: 'woocommerce/product-stock-indicator', attributes: {} }, + { name: 'woocommerce/product-sale-badge', attributes: {} }, + { + name: 'core/post-excerpt', + attributes: { + __woocommerceNamespace: + 'woocommerce/product-collection/product-summary', + }, + }, + { + name: 'core/post-terms', + attributes: { term: 'product_tag' }, + }, + { + name: 'core/post-terms', + attributes: { term: 'product_cat' }, + }, + ]; + + await Promise.all( + productElements.map( async ( productElement ) => { + await pageObject.insertBlockInProductCollection( + productElement + ); + } ) + ); + }; + + const verifyProductContent = async ( product: Locator ) => { + await expect( product ).toContainText( 'Beanie' ); // core/post-title + await expect( product ).toContainText( + '$20.00 Original price was: $20.00.$18.00Current price is: $18.00.' + ); // woocommerce/product-price + await expect( product ).toContainText( 'woo-beanie' ); // woocommerce/product-sku + await expect( product ).toContainText( 'In stock' ); // woocommerce/product-stock-indicator + await expect( product ).toContainText( + 'This is a simple product.' + ); // core/post-excerpt + await expect( product ).toContainText( 'Accessories' ); // core/post-terms - product_cat + await expect( product ).toContainText( 'Recommended' ); // core/post-terms - product_tag + await expect( product ).toContainText( 'SaleProduct on sale' ); // woocommerce/product-sale-badge + await expect( product ).toContainText( 'Add to cart' ); // woocommerce/product-button + }; + + // Expects are collected in verifyProductContent function + // eslint-disable-next-line playwright/expect-expect + test( 'In a post', async ( { pageObject } ) => { + await pageObject.createNewPostAndInsertBlock(); + await insertProductElements( pageObject ); + await pageObject.publishAndGoToFrontend(); + + const product = pageObject.products.nth( 1 ); + + await verifyProductContent( product ); + } ); + + // Expects are collected in verifyProductContent function + // eslint-disable-next-line playwright/expect-expect + test( 'In a Product Archive (Product Catalog)', async ( { + pageObject, + editor, + } ) => { + await pageObject.replaceProductsWithProductCollectionInTemplate( + 'woocommerce/woocommerce//archive-product' + ); + await insertProductElements( pageObject ); + await editor.saveSiteEditorEntities(); + await pageObject.goToProductCatalogFrontend(); + + const product = pageObject.products.nth( 1 ); + + await verifyProductContent( product ); + } ); + + // Expects are collected in verifyProductContent function + // eslint-disable-next-line playwright/expect-expect + test( 'On a Home Page', async ( { pageObject, editor } ) => { + await pageObject.goToHomePageAndInsertCollection(); + + await insertProductElements( pageObject ); + await editor.saveSiteEditorEntities(); + await pageObject.goToHomePageFrontend(); + + const product = pageObject.products.nth( 1 ); + + await verifyProductContent( product ); + } ); + } ); + test.describe( 'Product Collection Sidebar Settings', () => { test.beforeEach( async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock(); @@ -200,10 +302,16 @@ test.describe( 'Product Collection', () => { await pageObject.setFilterComboboxValue( filterName, [ 'Recommended', ] ); - await expect( pageObject.productTitles ).toHaveText( [ 'Hoodie' ] ); + await expect( pageObject.productTitles ).toHaveText( [ + 'Beanie', + 'Hoodie', + ] ); await pageObject.publishAndGoToFrontend(); - await expect( pageObject.productTitles ).toHaveText( [ 'Hoodie' ] ); + await expect( pageObject.productTitles ).toHaveText( [ + 'Beanie', + 'Hoodie', + ] ); } ); test( 'Products can be filtered based on product attributes like color, size etc.', async ( { @@ -316,7 +424,7 @@ test.describe( 'Product Collection', () => { await expect( pageObject.products ).toHaveCount( 4 ); } ); - test.describe( 'Sync with current template (former "Inherit query from template")', () => { + test.describe( 'Sync with current template', () => { test( 'should not be visible on posts', async ( { pageObject, } ) => { @@ -333,8 +441,10 @@ test.describe( 'Product Collection', () => { test( 'should work as expected in Product Catalog template', async ( { pageObject, + editor, } ) => { await pageObject.goToProductCatalogAndInsertCollection(); + await editor.openDocumentSettingsSidebar(); const sidebarSettings = await pageObject.locateSidebarSettings(); @@ -385,10 +495,12 @@ test.describe( 'Product Collection', () => { test( 'is enabled by default in 1st Product Collection and disabled in 2nd+', async ( { pageObject, + editor, } ) => { // First Product Catalog // Option should be visible & ENABLED by default await pageObject.goToProductCatalogAndInsertCollection(); + await editor.openDocumentSettingsSidebar(); const sidebarSettings = await pageObject.locateSidebarSettings(); @@ -638,7 +750,7 @@ test.describe( 'Product Collection', () => { await expect( pageObject.products ).toHaveCount( 4 ); } ); - test( "Product Catalog Collection can be added in post and doesn't inherit query from template", async ( { + test( "Product Catalog Collection can be added in post and doesn't sync query with template", async ( { pageObject, } ) => { await pageObject.createNewPostAndInsertBlock( 'productCatalog' ); @@ -656,12 +768,14 @@ test.describe( 'Product Collection', () => { await expect( pageObject.products ).toHaveCount( 9 ); } ); - test( 'Product Catalog Collection can be added in product archive and inherits query from template', async ( { + test( 'Product Catalog Collection can be added in product archive and syncs query with template', async ( { pageObject, + editor, } ) => { await pageObject.goToProductCatalogAndInsertCollection( 'productCatalog' ); + await editor.openDocumentSettingsSidebar(); const sidebarSettings = await pageObject.locateSidebarSettings(); const input = sidebarSettings.locator( diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts index c2e3231aa300..6128be395fa5 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts @@ -5,6 +5,11 @@ import { Locator, Page } from '@playwright/test'; import { TemplateApiUtils, EditorUtils } from '@woocommerce/e2e-utils'; import { Editor, Admin } from '@wordpress/e2e-test-utils-playwright'; +/** + * Internal dependencies + */ +import { BLOCK_THEME_SLUG } from '../../utils/constants'; + export const SELECTORS = { productTemplate: '.wc-block-product-template', product: '.wc-block-product-template .wc-block-product', @@ -127,6 +132,7 @@ class ProductCollectionPage { async createNewPostAndInsertBlock( collection?: Collections ) { await this.admin.createNewPost( { legacyCanvas: true } ); + await this.editorUtils.closeWelcomeGuideModal(); await this.insertProductCollection(); await this.chooseCollectionInPost( collection ); await this.refreshLocators( 'editor' ); @@ -184,34 +190,55 @@ class ProductCollectionPage { this.BLOCK_SLUG ); await this.chooseCollectionInTemplate( collection ); + await this.refreshLocators( 'editor' ); await this.editor.saveSiteEditorEntities(); } async goToProductCatalogFrontend() { await this.page.goto( `/shop` ); + await this.refreshLocators( 'frontend' ); + } + + async goToHomePageFrontend() { + await this.page.goto( `/` ); + await this.refreshLocators( 'frontend' ); } async insertProductCollection() { await this.editor.insertBlock( { name: this.BLOCK_SLUG } ); } - async goToProductCatalogAndInsertCollection( collection?: Collections ) { - await this.templateApiUtils.revertTemplate( - 'woocommerce/woocommerce//archive-product' - ); - + async goToTemplateAndInsertCollection( + template: string, + collection?: Collections + ) { + await this.templateApiUtils.revertTemplate( template ); await this.admin.visitSiteEditor( { - postId: 'woocommerce/woocommerce//archive-product', + postId: template, postType: 'wp_template', } ); await this.editorUtils.waitForSiteEditorFinishLoading(); await this.editor.canvas.click( 'body' ); await this.insertProductCollection(); await this.chooseCollectionInTemplate( collection ); - await this.editor.openDocumentSettingsSidebar(); + await this.refreshLocators( 'editor' ); await this.editor.saveSiteEditorEntities(); } + async goToHomePageAndInsertCollection( collection?: Collections ) { + await this.goToTemplateAndInsertCollection( + `${ BLOCK_THEME_SLUG }//home`, + collection + ); + } + + async goToProductCatalogAndInsertCollection( collection?: Collections ) { + await this.goToTemplateAndInsertCollection( + 'woocommerce/woocommerce//archive-product', + collection + ); + } + async searchProducts( phrase: string ) { await this.page .getByLabel( SELECTORS.productSearchLabel ) @@ -516,6 +543,25 @@ class ProductCollectionPage { await this.page.setViewportSize( { width, height } ); } + async insertBlockInProductCollection( block: { + name: string; + attributes: object; + } ) { + await this.waitForProductsToLoad(); + const productTemplate = await this.editorUtils.getBlockByName( + 'woocommerce/product-template' + ); + const productTemplateId = + ( await productTemplate.getAttribute( 'data-block' ) ) ?? ''; + + await this.editor.selectBlocks( productTemplate ); + await this.editorUtils.insertBlock( + block, + undefined, + productTemplateId + ); + } + async insertProductCollectionInSingleProductBlock() { this.insertSingleProductBlock(); @@ -618,10 +664,17 @@ class ProductCollectionPage { } private async waitForProductsToLoad() { - // Wait for the product blocks to be loaded. - await this.page.waitForSelector( 'wc-block-product-template__spinner', { - state: 'detached', - } ); + const loaderInTemplate = this.page + .frameLocator( 'iframe[name="editor-canvas"]' ) + .getByLabel( 'Block: Product Template' ) + .locator( 'circle' ); + const loaderInPost = this.page + .getByLabel( 'Block: Product Template' ) + .locator( 'circle' ); + await Promise.all( [ + loaderInTemplate.waitFor( { state: 'hidden', timeout: 100000 } ), + loaderInPost.waitFor( { state: 'hidden', timeout: 100000 } ), + ] ); } } diff --git a/plugins/woocommerce/changelog/45214-product-collection-add-e2e-tests-with-all-product-elements-included b/plugins/woocommerce/changelog/45214-product-collection-add-e2e-tests-with-all-product-elements-included new file mode 100644 index 000000000000..5af5682f2b81 --- /dev/null +++ b/plugins/woocommerce/changelog/45214-product-collection-add-e2e-tests-with-all-product-elements-included @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Product Collection: Add E2E tests confirming all Product Elements are rendered correctly From f6738e44311f57bffcd3a17476f8785479e63450 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Tue, 19 Mar 2024 05:57:17 -0400 Subject: [PATCH 2/2] Fix organization tab e2e tests (#45692) --- .../fix-e2e-test-organization-tab-selector | 4 +++ ...anization-tab-product-block-editor.spec.js | 28 +++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-e2e-test-organization-tab-selector diff --git a/plugins/woocommerce/changelog/fix-e2e-test-organization-tab-selector b/plugins/woocommerce/changelog/fix-e2e-test-organization-tab-selector new file mode 100644 index 000000000000..63268295b7bc --- /dev/null +++ b/plugins/woocommerce/changelog/fix-e2e-test-organization-tab-selector @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fix organization tab e2e tests #45692 diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/organization-tab-product-block-editor.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/organization-tab-product-block-editor.spec.js index 6b4a72bede92..ca2de837905b 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/organization-tab-product-block-editor.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/organization-tab-product-block-editor.spec.js @@ -44,18 +44,22 @@ test.describe( 'General tab', () => { ) .last() .fill( productData.summary ); - await page - .locator( - '[id^="wp-block-woocommerce-product-regular-price-field"]' - ) - .first() - .fill( productData.productPrice ); - await page - .locator( - '[id^="wp-block-woocommerce-product-sale-price-field"]' - ) - .first() - .fill( productData.salePrice ); + + await clickOnTab( 'Pricing', page ); + + const regularPrice = page + .locator( 'input[name="regular_price"]' ) + .first(); + await regularPrice.waitFor( { state: 'visible' } ); + await regularPrice.click(); + await regularPrice.fill( productData.productPrice ); + + const salePrice = page + .locator( 'input[name="sale_price"]' ) + .first(); + await salePrice.waitFor( { state: 'visible' } ); + await salePrice.click(); + await salePrice.fill( productData.salePrice ); await clickOnTab( 'Organization', page );