Commit aaa282deb36 for woocommerce
commit aaa282deb36b8794f1087af5bff6ac2e86afb51d
Author: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com>
Date: Thu Oct 22 14:47:14 2026 +0300
Run 6 Blocks e2e Single Product template specs in parallel (#70040)
Each spec copies a sample product and saves a template for that copy
only, so it no longer changes the product pages other workers read.
diff --git a/plugins/woocommerce/changelog/dev-testops-306-blocks-e2e-product-templates b/plugins/woocommerce/changelog/dev-testops-306-blocks-e2e-product-templates
new file mode 100644
index 00000000000..3565c13ab57
--- /dev/null
+++ b/plugins/woocommerce/changelog/dev-testops-306-blocks-e2e-product-templates
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Move 6 Blocks e2e specs that save the Single Product template to the parallel pool; tests only.
diff --git a/plugins/woocommerce/tests/e2e/playwright.config.ts b/plugins/woocommerce/tests/e2e/playwright.config.ts
index be4601da9c3..8d3e773c9c5 100644
--- a/plugins/woocommerce/tests/e2e/playwright.config.ts
+++ b/plugins/woocommerce/tests/e2e/playwright.config.ts
@@ -208,7 +208,6 @@ const blocksSerialSpecs = [
// archive, header, cart, checkout), or the
// `wc_blocks_use_blockified_product_grid_block_as_template` option. Every
// spec that visits those pages reads them.
- '**/tests/blocks/add-to-cart-form/add-to-cart-form.block_theme.spec.ts',
'**/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts',
'**/tests/blocks/all-products/all-products.block_theme.spec.ts',
'**/tests/blocks/attributes-filter/attribute-filter.block_theme.spec.ts',
@@ -217,19 +216,14 @@ const blocksSerialSpecs = [
'**/tests/blocks/classic-template/classic-template.block_theme.spec.ts',
'**/tests/blocks/mini-cart/mini-cart-block.shopper.block_theme.spec.ts',
'**/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts',
- '**/tests/blocks/on-sale-badge/on-sale-badge-single-product-template.block_theme.spec.ts',
'**/tests/blocks/page-content-wrapper/page-content-wrapper.block_theme.spec.ts',
'**/tests/blocks/price-filter/price-filter.block_theme.spec.ts',
'**/tests/blocks/product-collection/product-collection.block_theme.spec.ts',
'**/tests/blocks/product-filters/active-filter-frontend.block_theme.spec.ts',
'**/tests/blocks/product-filters/attribute-filter-frontend.block_theme.spec.ts',
'**/tests/blocks/product-filters/product-filters-frontend.block_theme.spec.ts',
- '**/tests/blocks/product-gallery/inner-blocks/product-gallery-large-image/product-gallery-large-image.block_theme.spec.ts',
- '**/tests/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.spec.ts',
- '**/tests/blocks/product-gallery/product-gallery.block_theme.spec.ts',
'**/tests/blocks/products/products.block_theme.spec.ts',
'**/tests/blocks/rating-filter/rating-filter.block_theme.spec.ts',
- '**/tests/blocks/single-product-template/single-product-template-compatibility-layer.spec.ts',
'**/tests/blocks/stock-filter/stock-filter.block_theme.spec.ts',
'**/tests/blocks/templates/legacy-templates.block_theme.spec.ts',
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-form/add-to-cart-form.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-form/add-to-cart-form.block_theme.spec.ts
index f031fb1317f..47772ebfd53 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-form/add-to-cart-form.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-form/add-to-cart-form.block_theme.spec.ts
@@ -1,8 +1,9 @@
/**
* External dependencies
*/
+import { randomUUID } from 'crypto';
import { Page } from '@playwright/test';
-import { test as base, expect, Editor, wpCLI } from '@woocommerce/e2e-utils';
+import { test as base, expect, Editor } from '@woocommerce/e2e-utils';
/**
* Internal dependencies
@@ -73,24 +74,47 @@ class BlockUtils {
await ( await this.editor.getBlockByName( blockData.slug ) ).click();
await this.page.getByLabel( 'Stepper' ).click();
}
-
- async createSoldIndividuallyProduct() {
- await wpCLI(
- 'wc product create --name="Sold Individually" --regular_price=10 --sold_individually=true --user=admin'
- );
- }
-
- async createManagedStockProduct() {
- await wpCLI(
- 'wc product create --name="Managed Stock" --regular_price=10 --manage_stock=true --stock_quantity=1 --user=admin'
- );
- }
}
-const test = base.extend< { blockUtils: BlockUtils } >( {
+const test = base.extend< {
+ blockUtils: BlockUtils;
+ createHiddenProduct: (
+ name: string,
+ data: Record< string, unknown >
+ ) => Promise< string >;
+} >( {
blockUtils: async ( { editor, page }, use ) => {
await use( new BlockUtils( { editor, page } ) );
},
+ createHiddenProduct: async ( { requestUtils }, use ) => {
+ const productIds: number[] = [];
+
+ await use( async ( name, data ) => {
+ const uniqueName = `${ name } ${ randomUUID().slice( 0, 8 ) }`;
+ const { id } = await requestUtils.rest< { id: number } >( {
+ method: 'POST',
+ path: 'wc/v3/products',
+ data: {
+ name: uniqueName,
+ regular_price: '10',
+ catalog_visibility: 'hidden',
+ ...data,
+ },
+ } );
+ productIds.push( id );
+ return uniqueName;
+ } );
+
+ await Promise.allSettled(
+ productIds.map( ( id ) =>
+ requestUtils.rest( {
+ method: 'DELETE',
+ path: `wc/v3/products/${ id }`,
+ params: { force: true },
+ } )
+ )
+ );
+ },
} );
test.describe( `${ blockData.name } Block`, () => {
@@ -127,47 +151,47 @@ test.describe( `${ blockData.name } Block`, () => {
// inner block of the Single Product Block except for the Single Product
// Template
const template = await requestUtils.createTemplate( 'wp_template', {
- slug: 'product-catalog',
- title: 'Custom Product Catalog',
+ slug: `add-to-cart-form-test-${ test.info().repeatEachIndex }`,
+ title: `Add to Cart Form Test ${ test.info().repeatEachIndex }`,
content: 'placeholder',
} );
- await admin.visitSiteEditor( {
- postId: template.id,
- postType: 'wp_template',
- canvas: 'edit',
- } );
-
- await expect(
- editor.getCustomHtmlBlockContentLocator( 'placeholder' )
- ).toBeVisible();
+ try {
+ await admin.visitSiteEditor( {
+ postId: template.id,
+ postType: 'wp_template',
+ canvas: 'edit',
+ } );
- await editor.insertBlock( { name: 'woocommerce/single-product' } );
+ await expect(
+ editor.getCustomHtmlBlockContentLocator( 'placeholder' )
+ ).toBeVisible();
- await blockUtils.configureSingleProductBlock();
+ await editor.insertBlock( { name: 'woocommerce/single-product' } );
- await expect(
- await editor.getBlockByName( blockData.slug )
- ).toBeVisible();
+ await blockUtils.configureSingleProductBlock();
- // When the block is registered as ancestor, the function doesn't throw an error, but the block is not added.
- // So we check that only one instance of the block is present.
- await editor.insertBlock( { name: blockData.slug } );
- await expect(
- await editor.getBlockByName( blockData.slug )
- ).toBeVisible();
+ await expect(
+ await editor.getBlockByName( blockData.slug )
+ ).toBeVisible();
+
+ // When the block is registered as ancestor, the function doesn't throw an error, but the block is not added.
+ // So we check that only one instance of the block is present.
+ await editor.insertBlock( { name: blockData.slug } );
+ await expect(
+ await editor.getBlockByName( blockData.slug )
+ ).toBeVisible();
+ } finally {
+ await requestUtils.revertTemplate( 'wp_template', template.id );
+ }
} );
test( 'can be added in the Post Editor - Single Product Template', async ( {
admin,
editor,
- requestUtils,
+ createProductTemplate,
} ) => {
- const template = await requestUtils.createTemplate( 'wp_template', {
- slug: 'single-product',
- title: 'Custom Single Product',
- content: 'placeholder',
- } );
+ const { template } = await createProductTemplate( 'beanie' );
await admin.visitSiteEditor( {
postId: template.id,
@@ -259,13 +283,15 @@ test.describe( `${ blockData.name } Block`, () => {
editor,
blockUtils,
page,
+ createHiddenProduct,
} ) => {
- await blockUtils.createSoldIndividuallyProduct();
+ const productName = await createHiddenProduct(
+ 'Sold Individually',
+ { sold_individually: true }
+ );
await admin.createNewPost();
await editor.insertBlock( { name: 'woocommerce/single-product' } );
- const productName = 'Sold Individually';
-
await blockUtils.configureSingleProductBlock( productName );
await blockUtils.enableStepperMode();
@@ -283,13 +309,15 @@ test.describe( `${ blockData.name } Block`, () => {
editor,
blockUtils,
page,
+ createHiddenProduct,
} ) => {
- await blockUtils.createManagedStockProduct();
+ const productName = await createHiddenProduct( 'Managed Stock', {
+ manage_stock: true,
+ stock_quantity: 1,
+ } );
await admin.createNewPost();
await editor.insertBlock( { name: 'woocommerce/single-product' } );
- const productName = 'Managed Stock';
-
await blockUtils.configureSingleProductBlock( productName );
await blockUtils.enableStepperMode();
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/on-sale-badge/on-sale-badge-single-product-template.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/on-sale-badge/on-sale-badge-single-product-template.block_theme.spec.ts
index 42fbf71a8ce..d49406c58c7 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/on-sale-badge/on-sale-badge-single-product-template.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/on-sale-badge/on-sale-badge-single-product-template.block_theme.spec.ts
@@ -2,14 +2,18 @@
* External dependencies
*/
import type { FrameLocator, Page } from '@playwright/test';
-import { test, expect, BLOCK_THEME_SLUG } from '@woocommerce/e2e-utils';
+import { test as base, expect, ProductTemplate } from '@woocommerce/e2e-utils';
const blockData = {
name: 'woocommerce/product-sale-badge',
- slug: 'single-product',
- productPage: '/product/hoodie/',
};
+const test = base.extend< { hoodieTemplate: ProductTemplate } >( {
+ hoodieTemplate: async ( { createProductTemplate }, use ) => {
+ await use( await createProductTemplate( 'hoodie' ) );
+ },
+} );
+
const badgeSelector = '.wc-block-components-product-sale-badge';
const badgeContainerSelector = '.wp-block-woocommerce-product-sale-badge';
@@ -55,9 +59,9 @@ const getAlignmentDelta = async (
test.describe( `${ blockData.name }`, () => {
test.describe( `On the Single Product Template`, () => {
- test.beforeEach( async ( { admin, editor } ) => {
+ test.beforeEach( async ( { admin, editor, hoodieTemplate } ) => {
await admin.visitSiteEditor( {
- postId: `${ BLOCK_THEME_SLUG }//${ blockData.slug }`,
+ postId: hoodieTemplate.template.id,
postType: 'wp_template',
canvas: 'edit',
} );
@@ -67,6 +71,7 @@ test.describe( `${ blockData.name }`, () => {
test( 'renders and aligns the sale badge in editor and frontend', async ( {
editor,
page,
+ hoodieTemplate,
} ) => {
const context = page.context();
const baselinePageCount = context.pages().length;
@@ -96,7 +101,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await frontendPage.goto( blockData.productPage );
+ await frontendPage.goto( hoodieTemplate.productUrl );
await expect(
frontendPage.locator( badgeSelector ).first()
).toBeVisible();
@@ -124,7 +129,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await frontendPage.goto( blockData.productPage );
+ await frontendPage.goto( hoodieTemplate.productUrl );
await expect(
frontendPage.locator( badgeSelector ).first()
).toBeVisible();
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/inner-blocks/product-gallery-large-image/product-gallery-large-image.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/inner-blocks/product-gallery-large-image/product-gallery-large-image.block_theme.spec.ts
index a3b6ecd3f79..60f76323534 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/inner-blocks/product-gallery-large-image/product-gallery-large-image.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/inner-blocks/product-gallery-large-image/product-gallery-large-image.block_theme.spec.ts
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { test as base, expect, BLOCK_THEME_SLUG } from '@woocommerce/e2e-utils';
+import { test as base, expect, ProductTemplate } from '@woocommerce/e2e-utils';
/**
* Internal dependencies
@@ -14,11 +14,13 @@ const blockData = {
frontend: {},
editor: {},
},
- slug: 'single-product',
- productPage: '/product/hoodie/',
};
-const test = base.extend< { pageObject: ProductGalleryPage } >( {
+const test = base.extend< {
+ pageObject: ProductGalleryPage;
+ sourceProductSlug: string;
+ productTemplate: ProductTemplate;
+} >( {
pageObject: async ( { page, editor, frontendUtils }, use ) => {
const pageObject = new ProductGalleryPage( {
page,
@@ -27,12 +29,19 @@ const test = base.extend< { pageObject: ProductGalleryPage } >( {
} );
await use( pageObject );
},
+ sourceProductSlug: [ 'hoodie', { option: true } ],
+ productTemplate: async (
+ { createProductTemplate, sourceProductSlug },
+ use
+ ) => {
+ await use( await createProductTemplate( sourceProductSlug ) );
+ },
} );
test.describe( `${ blockData.name }`, () => {
- test.beforeEach( async ( { admin, editor } ) => {
+ test.beforeEach( async ( { admin, editor, productTemplate } ) => {
await admin.visitSiteEditor( {
- postId: `${ BLOCK_THEME_SLUG }//${ blockData.slug }`,
+ postId: productTemplate.template.id,
postType: 'wp_template',
canvas: 'edit',
} );
@@ -52,6 +61,7 @@ test.describe( `${ blockData.name }`, () => {
pageObject,
editor,
page,
+ productTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
await pageObject.toggleZoomWhileHoveringSetting( true );
@@ -59,7 +69,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( productTemplate.productUrl );
const viewerBlock = await pageObject.getViewerBlock( {
page: 'frontend',
@@ -106,6 +116,7 @@ test.describe( `${ blockData.name }`, () => {
pageObject,
editor,
page,
+ productTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
await pageObject.toggleZoomWhileHoveringSetting( false );
@@ -117,7 +128,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( productTemplate.productUrl );
const viewerBlock = await pageObject.getViewerBlock( {
page: 'frontend',
@@ -142,6 +153,7 @@ test.describe( `${ blockData.name }`, () => {
page,
editor,
pageObject,
+ productTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
await pageObject.addClassicAddToCartFormBlock();
@@ -155,7 +167,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( productTemplate.productUrl );
// Initial state: only the parent's featured image is visible.
const featuredImageId = await pageObject.getViewerImageId();
@@ -189,6 +201,7 @@ test.describe( `${ blockData.name }`, () => {
page,
editor,
pageObject,
+ productTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
await pageObject.addClassicAddToCartFormBlock();
@@ -197,7 +210,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( productTemplate.productUrl );
const featuredImageId = await pageObject.getViewerImageId();
expect( featuredImageId ).not.toBeNull();
const parentImageIds = await pageObject.getVisibleViewerImageIds();
@@ -272,23 +285,28 @@ test.describe( `${ blockData.name }`, () => {
test.describe( 'Frontend navigation', () => {
test.use( {
+ sourceProductSlug: 'beanie',
hasTouch: true,
contextOptions: { reducedMotion: 'no-preference' },
} );
- test.beforeEach( async ( { pageObject, editor, page } ) => {
- await pageObject.addProductGalleryBlock( { cleanContent: true } );
- await editor.saveSiteEditorEntities( {
- isOnlyCurrentEntityDirty: true,
- } );
+ test.beforeEach(
+ async ( { pageObject, editor, page, productTemplate } ) => {
+ await pageObject.addProductGalleryBlock( {
+ cleanContent: true,
+ } );
+ await editor.saveSiteEditorEntities( {
+ isOnlyCurrentEntityDirty: true,
+ } );
- await page.goto( '/product/beanie/' );
+ await page.goto( productTemplate.productUrl );
- await page.setViewportSize( {
- height: 667,
- width: 390, // iPhone 12 Pro
- } );
- } );
+ await page.setViewportSize( {
+ height: 667,
+ width: 390, // iPhone 12 Pro
+ } );
+ }
+ );
test( 'updates thumbnail selection during a native swipe', async ( {
pageObject,
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.spec.ts
index 298db9e5604..ce037faf21a 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.spec.ts
@@ -1,18 +1,18 @@
/**
* External dependencies
*/
-import { test, expect } from '@woocommerce/e2e-utils';
+import { test as base, expect, ProductTemplate } from '@woocommerce/e2e-utils';
-test.describe( 'Product Gallery Thumbnails block', () => {
- test.beforeEach( async ( { admin, editor, requestUtils } ) => {
- const template = await requestUtils.createTemplate( 'wp_template', {
- slug: 'single-product',
- title: 'Custom Single Product',
- content: 'placeholder',
- } );
+const test = base.extend< { beanieTemplate: ProductTemplate } >( {
+ beanieTemplate: async ( { createProductTemplate }, use ) => {
+ await use( await createProductTemplate( 'beanie' ) );
+ },
+} );
+test.describe( 'Product Gallery Thumbnails block', () => {
+ test.beforeEach( async ( { admin, editor, beanieTemplate } ) => {
await admin.visitSiteEditor( {
- postId: template.id,
+ postId: beanieTemplate.template.id,
postType: 'wp_template',
canvas: 'edit',
} );
@@ -30,7 +30,7 @@ test.describe( 'Product Gallery Thumbnails block', () => {
} );
} );
- test( 'renders as expected', async ( { page, editor } ) => {
+ test( 'renders as expected', async ( { page, editor, beanieTemplate } ) => {
await test.step( 'in editor', async () => {
const productGalleryBlock = editor.canvas.locator(
'[data-type="woocommerce/product-gallery"]'
@@ -52,7 +52,7 @@ test.describe( 'Product Gallery Thumbnails block', () => {
} );
await test.step( 'in frontend', async () => {
- await page.goto( '/product/beanie/' );
+ await page.goto( beanieTemplate.productUrl );
const productGalleryBlock = page.locator(
'[data-block-name="woocommerce/product-gallery"]'
@@ -83,6 +83,7 @@ test.describe( 'Product Gallery Thumbnails block', () => {
test( 'thumbnail size settings work correctly', async ( {
page,
editor,
+ beanieTemplate,
} ) => {
await test.step( 'in editor', async () => {
const viewerBlock = editor.canvas.locator(
@@ -125,7 +126,7 @@ test.describe( 'Product Gallery Thumbnails block', () => {
} );
await test.step( 'in frontend', async () => {
- await page.goto( '/product/beanie/' );
+ await page.goto( beanieTemplate.productUrl );
const thumbnailsBlock = page.locator(
'[data-block-name="woocommerce/product-gallery-thumbnails"]'
@@ -150,6 +151,7 @@ test.describe( 'Product Gallery Thumbnails block', () => {
test( 'thumbnails are scrollable and last thumbnail is reachable', async ( {
page,
editor,
+ beanieTemplate,
} ) => {
await test.step( 'in editor', async () => {
const viewerBlock = editor.canvas.locator(
@@ -183,7 +185,7 @@ test.describe( 'Product Gallery Thumbnails block', () => {
} );
await test.step( 'in frontend', async () => {
- await page.goto( '/product/beanie/' );
+ await page.goto( beanieTemplate.productUrl );
const thumbnailsContainer = page.locator(
'[data-block-name="woocommerce/product-gallery-thumbnails"]'
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/product-gallery.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/product-gallery.block_theme.spec.ts
index 3329c3a3bc4..d1bcdb10dc0 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/product-gallery.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/product-gallery/product-gallery.block_theme.spec.ts
@@ -2,7 +2,7 @@
* External dependencies
*/
import { Locator } from '@playwright/test';
-import { test as base, expect } from '@woocommerce/e2e-utils';
+import { test as base, expect, ProductTemplate } from '@woocommerce/e2e-utils';
/**
* Internal dependencies
@@ -12,11 +12,12 @@ import { ProductGalleryPage } from './product-gallery.page';
const blockData = {
name: 'woocommerce/product-gallery',
title: 'Product Gallery',
- slug: 'single-product',
- productPage: '/product/beanie/',
};
-const test = base.extend< { pageObject: ProductGalleryPage } >( {
+const test = base.extend< {
+ pageObject: ProductGalleryPage;
+ beanieTemplate: ProductTemplate;
+} >( {
pageObject: async ( { page, editor, frontendUtils }, use ) => {
const pageObject = new ProductGalleryPage( {
page,
@@ -25,6 +26,9 @@ const test = base.extend< { pageObject: ProductGalleryPage } >( {
} );
await use( pageObject );
},
+ beanieTemplate: async ( { createProductTemplate }, use ) => {
+ await use( await createProductTemplate( 'beanie' ) );
+ },
} );
/**
@@ -67,15 +71,9 @@ const getThumbnailImageIdByNth = async (
};
test.describe( `${ blockData.name }`, () => {
- test.beforeEach( async ( { admin, editor, requestUtils } ) => {
- const template = await requestUtils.createTemplate( 'wp_template', {
- slug: blockData.slug,
- title: 'Custom Single Product',
- content: 'placeholder',
- } );
-
+ test.beforeEach( async ( { admin, editor, beanieTemplate } ) => {
await admin.visitSiteEditor( {
- postId: template.id,
+ postId: beanieTemplate.template.id,
postType: 'wp_template',
canvas: 'edit',
} );
@@ -90,6 +88,7 @@ test.describe( `${ blockData.name }`, () => {
page,
editor,
pageObject,
+ beanieTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
@@ -97,7 +96,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( beanieTemplate.productUrl );
const thumbnailsBlock = await pageObject.getThumbnailsBlock( {
page: 'frontend',
@@ -141,6 +140,7 @@ test.describe( `${ blockData.name }`, () => {
page,
editor,
pageObject,
+ beanieTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
@@ -148,7 +148,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( beanieTemplate.productUrl );
const initialViewerImageId = await pageObject.getViewerImageId();
@@ -195,6 +195,7 @@ test.describe( `${ blockData.name }`, () => {
page,
editor,
pageObject,
+ beanieTemplate,
} ) => {
await page.setViewportSize( { width: 800, height: 800 } );
@@ -204,7 +205,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( beanieTemplate.productUrl );
const initialViewerImageId = await pageObject.getViewerImageId();
@@ -260,6 +261,7 @@ test.describe( `${ blockData.name }`, () => {
pageObject,
page,
editor,
+ beanieTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
await editor.openDocumentSettingsSidebar();
@@ -270,7 +272,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( beanieTemplate.productUrl );
const viewerBlock = await pageObject.getViewerBlock( {
page: 'frontend',
@@ -287,6 +289,7 @@ test.describe( `${ blockData.name }`, () => {
pageObject,
page,
editor,
+ beanieTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
await editor.openDocumentSettingsSidebar();
@@ -295,7 +298,7 @@ test.describe( `${ blockData.name }`, () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( beanieTemplate.productUrl );
await expect( page.locator( 'dialog' ) ).toBeHidden();
@@ -359,6 +362,7 @@ test.describe( `${ blockData.name }`, () => {
editor,
pageObject,
page,
+ beanieTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
await editor.saveSiteEditorEntities( {
@@ -373,7 +377,9 @@ test.describe( `${ blockData.name }`, () => {
await page.getByLabel( 'Open Navigation' ).click();
await page
- .getByRole( 'button', { name: 'Custom Single Product' } )
+ .getByRole( 'button', {
+ name: beanieTemplate.template.title.rendered,
+ } )
.first()
.click();
@@ -388,13 +394,14 @@ test.describe( `${ blockData.name }`, () => {
page,
pageObject,
editor,
+ beanieTemplate,
} ) => {
await pageObject.addProductGalleryBlock( { cleanContent: true } );
await editor.saveSiteEditorEntities( {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( blockData.productPage );
+ await page.goto( beanieTemplate.productUrl );
await page.setViewportSize( {
height: 667,
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/single-product-template/single-product-template-compatibility-layer.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/single-product-template/single-product-template-compatibility-layer.spec.ts
index 99c3f7660f6..472697d8347 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/single-product-template/single-product-template-compatibility-layer.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/single-product-template/single-product-template-compatibility-layer.spec.ts
@@ -182,10 +182,20 @@ test.describe( 'Compatibility Layer in Single Product template', () => {
page,
admin,
editor,
+ requestUtils,
+ createProductTemplate,
} ) => {
+ const singleProductTemplate = await requestUtils.getTemplate(
+ 'wp_template',
+ `${ BLOCK_THEME_SLUG }//single-product`
+ );
+ const capTemplate = await createProductTemplate( 'cap', {
+ content: singleProductTemplate.content.raw,
+ } );
+
/* Switch to the blockified Add to Cart + Options block to be able to test all hooks */
await admin.visitSiteEditor( {
- postId: `${ BLOCK_THEME_SLUG }//single-product`,
+ postId: capTemplate.template.id,
postType: 'wp_template',
canvas: 'edit',
} );
@@ -207,7 +217,15 @@ test.describe( 'Compatibility Layer in Single Product template', () => {
isOnlyCurrentEntityDirty: true,
} );
- await page.goto( '/product/cap/' );
+ const { content } = await requestUtils.getTemplate(
+ 'wp_template',
+ capTemplate.template.id
+ );
+ const hoodieTemplate = await createProductTemplate( 'hoodie', {
+ content: content.raw,
+ } );
+
+ await page.goto( capTemplate.productUrl );
for ( const scenario of simpleProductAddToCartWithOptionsBlockHooks ) {
const hooks = page.getByTestId( scenario.dataTestId );
@@ -222,7 +240,7 @@ test.describe( 'Compatibility Layer in Single Product template', () => {
).toHaveText( scenario.content );
}
- await page.goto( '/product/hoodie/' );
+ await page.goto( hoodieTemplate.productUrl );
for ( const scenario of variableProductAddToCartWithOptionsBlockHooks ) {
const hooks = page.getByTestId( scenario.dataTestId );
diff --git a/plugins/woocommerce/tests/e2e/utils/blocks/request-utils/index.ts b/plugins/woocommerce/tests/e2e/utils/blocks/request-utils/index.ts
index c8ba4a883aa..7fe300e2727 100644
--- a/plugins/woocommerce/tests/e2e/utils/blocks/request-utils/index.ts
+++ b/plugins/woocommerce/tests/e2e/utils/blocks/request-utils/index.ts
@@ -14,6 +14,7 @@ import {
revertTemplate,
createTemplateFromFile,
TemplateCompiler,
+ WPTemplate,
} from './templates';
import { resetFeatureFlag, setFeatureFlag } from './feature-flag';
@@ -39,4 +40,4 @@ export class RequestUtils extends CoreRequestUtils {
resetFeatureFlag: typeof resetFeatureFlag = resetFeatureFlag.bind( this );
}
-export { TemplateCompiler, PostCompiler };
+export { TemplateCompiler, PostCompiler, WPTemplate };
diff --git a/plugins/woocommerce/tests/e2e/utils/blocks/request-utils/templates.ts b/plugins/woocommerce/tests/e2e/utils/blocks/request-utils/templates.ts
index e7d61ba3df2..78fd87fb318 100644
--- a/plugins/woocommerce/tests/e2e/utils/blocks/request-utils/templates.ts
+++ b/plugins/woocommerce/tests/e2e/utils/blocks/request-utils/templates.ts
@@ -22,6 +22,10 @@ export interface WPTemplate {
slug: string;
type: WPTemplateType;
origin: 'plugin' | 'theme' | null;
+ title: {
+ raw: string;
+ rendered: string;
+ };
content: {
raw: string;
};
diff --git a/plugins/woocommerce/tests/e2e/utils/blocks/test.ts b/plugins/woocommerce/tests/e2e/utils/blocks/test.ts
index d13531aa7ac..9c16b364215 100644
--- a/plugins/woocommerce/tests/e2e/utils/blocks/test.ts
+++ b/plugins/woocommerce/tests/e2e/utils/blocks/test.ts
@@ -2,6 +2,7 @@
/**
* External dependencies
*/
+import { randomUUID } from 'crypto';
import { test as base, expect, ConsoleMessage } from '@playwright/test';
import {
STORAGE_STATE_PATH,
@@ -16,6 +17,7 @@ import {
PerformanceUtils,
RequestUtils,
ShippingUtils,
+ WPTemplate,
} from '@woocommerce/e2e-utils';
/**
@@ -106,6 +108,20 @@ function observeConsoleLogging( message: ConsoleMessage ) {
console[ logFunction ]( text );
}
+export type ProductTemplate = { template: WPTemplate; productUrl: string };
+
+type WCProduct = {
+ id: number;
+ name: string;
+ type: string;
+ regular_price: string;
+ sale_price: string;
+ attributes: unknown[];
+ default_attributes: unknown[];
+ images: { id: number }[];
+ image?: { id: number } | null;
+};
+
const test = base.extend<
{
admin: Admin;
@@ -118,6 +134,10 @@ const test = base.extend<
localPickupUtils: LocalPickupUtils;
miniCartUtils: MiniCartUtils;
resetDatabaseAfterEachTest: boolean;
+ createProductTemplate: (
+ sourceProductSlug: string,
+ options?: { content?: string }
+ ) => Promise< ProductTemplate >;
},
{
requestUtils: RequestUtils;
@@ -191,6 +211,97 @@ const test = base.extend<
miniCartUtils: async ( { page, frontendUtils }, use ) => {
await use( new MiniCartUtils( page, frontendUtils ) );
},
+ // Copies a sample product and creates a `single-product-<slug>` template
+ // for the copy, so a spec can save a Single Product template without
+ // changing the pages other workers visit. The copy is hidden from the
+ // catalog. Both are deleted after the test.
+ createProductTemplate: async ( { requestUtils }, use ) => {
+ const productIds: number[] = [];
+ const templateIds: string[] = [];
+
+ await use( async ( sourceProductSlug, options = {} ) => {
+ const [ source ] = await requestUtils.rest< WCProduct[] >( {
+ path: 'wc/v3/products',
+ params: { slug: sourceProductSlug },
+ } );
+ if ( ! source ) {
+ throw new Error(
+ `No product with slug ${ sourceProductSlug }`
+ );
+ }
+ const suffix = randomUUID().slice( 0, 8 );
+ // Copied field by field rather than with the duplicate endpoint, and
+ // with a random slug: concurrent duplicates generate the same
+ // "unique" SKU or slug and fail. The name leaves out the source name,
+ // so specs that pick "Cap" or "Hoodie" by name never match a copy.
+ const product = await requestUtils.rest<
+ WCProduct & { slug: string; permalink: string }
+ >( {
+ method: 'POST',
+ path: 'wc/v3/products',
+ data: {
+ name: `E2E copy ${ suffix }`,
+ slug: `${ sourceProductSlug }-${ suffix }`,
+ type: source.type,
+ regular_price: source.regular_price,
+ sale_price: source.sale_price,
+ attributes: source.attributes,
+ default_attributes: source.default_attributes,
+ images: source.images.map( ( { id } ) => ( { id } ) ),
+ catalog_visibility: 'hidden',
+ },
+ } );
+ productIds.push( product.id );
+
+ if ( source.type === 'variable' ) {
+ const variations = await requestUtils.rest< WCProduct[] >( {
+ path: `wc/v3/products/${ source.id }/variations`,
+ params: { per_page: 100 },
+ } );
+ await requestUtils.rest( {
+ method: 'POST',
+ path: `wc/v3/products/${ product.id }/variations/batch`,
+ data: {
+ create: variations.map( ( variation ) => ( {
+ regular_price: variation.regular_price,
+ sale_price: variation.sale_price,
+ attributes: variation.attributes,
+ image: variation.image && {
+ id: variation.image.id,
+ },
+ } ) ),
+ },
+ } );
+ }
+
+ const template = ( await requestUtils.createTemplate(
+ 'wp_template',
+ {
+ slug: `single-product-${ product.slug }`,
+ title: `Single Product ${ product.slug }`,
+ content: options.content ?? 'placeholder',
+ }
+ ) ) as WPTemplate;
+ templateIds.push( template.id );
+
+ return { template, productUrl: product.permalink };
+ } );
+
+ await Promise.allSettled(
+ templateIds.map( ( templateId ) =>
+ requestUtils.revertTemplate( 'wp_template', templateId )
+ )
+ );
+ await Promise.allSettled(
+ productIds.map( ( productId ) =>
+ requestUtils.rest( {
+ method: 'DELETE',
+ path: `wc/v3/products/${ productId }`,
+ params: { force: true },
+ } )
+ )
+ );
+ },
requestUtils: [
async ( {}, use, workerInfo ) => {
const requestUtils = await RequestUtils.setup( {