Blocks E2E: Use layout-based selectors in Thumbnails block tests (#47904)

* Use layout-based selectors in Thumbnails block tests

* improve test readability

* add changelog entry
This commit is contained in:
Bart Kalisz 2024-06-03 10:26:27 +02:00 committed by GitHub
parent b4efa72b07
commit 44efb7ffac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 177 additions and 527 deletions

View File

@ -1,418 +1,204 @@
/**
* External dependencies
*/
import { Locator, Page } from '@playwright/test';
import { test as base, expect } from '@woocommerce/e2e-utils';
import { test, expect } from '@woocommerce/e2e-utils';
/**
* Internal dependencies
*/
import { ProductGalleryPage } from '../../product-gallery.page';
const blockData = {
name: 'woocommerce/product-gallery-thumbnails',
mainClass: '.wp-block-woocommerce-product-gallery-thumbnails',
selectors: {
frontend: {},
editor: {
thumbnails: '.wp-block-woocommerce-product-gallery-thumbnails',
noThumbnailsOption: 'button[data-value=off]',
leftPositionThumbnailsOption: 'button[data-value=left]',
bottomPositionThumbnailsOption: 'button[data-value=bottom]',
rightPositionThumbnailsOption: 'button[data-value=right]',
},
},
slug: 'single-product',
productPage: '/product/v-neck-t-shirt/',
};
const changeNumberOfThumbnailsInputValue = async (
page: Page,
numberOfThumbnailInput: Locator,
value: string
) => {
await numberOfThumbnailInput.fill( value );
await page.keyboard.press( 'Enter' );
};
const test = base.extend< { pageObject: ProductGalleryPage } >( {
pageObject: async ( { page, editor, frontendUtils }, use ) => {
const pageObject = new ProductGalleryPage( {
page,
editor,
frontendUtils,
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',
} );
await use( pageObject );
},
} );
test.describe( `${ blockData.name }`, () => {
test.beforeEach( async ( { admin, editor } ) => {
await admin.visitSiteEditor( {
postId: `woocommerce/woocommerce//${ blockData.slug }`,
postId: template.id,
postType: 'wp_template',
canvas: 'edit',
} );
await editor.canvas
.locator( '[data-testid="product-image"]' )
.first()
.waitFor();
} );
await expect( editor.canvas.getByText( 'placeholder' ) ).toBeVisible();
test( 'Renders Product Gallery Thumbnails block on the editor and frontend side', async ( {
page,
editor,
pageObject,
frontendUtils,
} ) => {
await editor.insertBlock( {
name: 'woocommerce/product-gallery',
} );
const thumbnailsBlock = await pageObject.getThumbnailsBlock( {
page: 'editor',
} );
await expect( thumbnailsBlock ).toBeVisible();
// Test the default (left) position of thumbnails by cross-checking:
// - The Gallery block has the classes "is-layout-flex" and "is-nowrap".
// - The Thumbnails block has a lower index than the Large Image block.
const groupBlock = editor.canvas
.locator( '[data-type="woocommerce/product-gallery"]' )
.locator( '[data-type="core/group"]' )
.first();
const groupBlockClassAttribute = await groupBlock.getAttribute(
'class'
);
expect( groupBlockClassAttribute ).toContain( 'is-layout-flex' );
expect( groupBlockClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsBlockEarlier = await editor.isBlockEarlierThan(
groupBlock,
'woocommerce/product-gallery-thumbnails',
'core/group'
);
expect( isThumbnailsBlockEarlier ).toBe( true );
await editor.saveSiteEditorEntities();
await page.goto( blockData.productPage );
const groupBlockFrontend = (
await frontendUtils.getBlockByClassWithParent(
'wp-block-group',
'woocommerce/product-gallery'
)
).first();
const groupBlockFrontendClassAttribute =
await groupBlockFrontend.getAttribute( 'class' );
expect( groupBlockFrontendClassAttribute ).toContain(
'is-layout-flex'
);
expect( groupBlockFrontendClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsFrontendBlockEarlier =
await frontendUtils.isBlockEarlierThanGroupBlock(
groupBlockFrontend,
'woocommerce/product-gallery-thumbnails'
);
expect( isThumbnailsFrontendBlockEarlier ).toBe( true );
} );
test.describe( `${ blockData.name } Settings`, () => {
test( 'Hide correctly the thumbnails', async ( { page, editor } ) => {
await editor.insertBlock( {
name: 'woocommerce/product-gallery',
} );
await editor.canvas
.locator( `[data-type="${ blockData.name }"]` )
.click();
await editor.openDocumentSettingsSidebar();
await page
.locator( blockData.selectors.editor.noThumbnailsOption )
.click();
await expect(
page.locator( blockData.selectors.editor.thumbnails )
).toBeHidden();
await editor.saveSiteEditorEntities();
} );
// We can test the left position of thumbnails by cross-checking:
// - The Gallery block has the classes "is-layout-flex" and "is-nowrap".
// - The Thumbnails block has a lower index than the Large Image block.
test( 'Position thumbnails on the left of the large image', async ( {
page,
editor,
frontendUtils,
} ) => {
// Currently we are adding the block under the legacy Product Image Gallery block, but in the future we have to add replace the product gallery block with this block.
const parentBlock = await editor.getBlockByName(
'woocommerce/product-image-gallery'
);
const clientId =
( await parentBlock.getAttribute( 'data-block' ) ) ?? '';
const parentClientId =
( await editor.getBlockRootClientId( clientId ) ) ?? '';
await editor.selectBlocks( parentBlock );
await editor.insertBlock(
{ name: 'woocommerce/product-gallery' },
{ clientId: parentClientId }
);
await ( await editor.getBlockByName( blockData.name ) ).click();
await editor.openDocumentSettingsSidebar();
await page
.locator(
blockData.selectors.editor.leftPositionThumbnailsOption
)
.click();
const groupBlock = editor.canvas
.locator( '[data-type="woocommerce/product-gallery"]' )
.locator( '[data-type="core/group"]' )
.first();
const groupBlockClassAttribute = await groupBlock.getAttribute(
'class'
);
expect( groupBlockClassAttribute ).toContain( 'is-layout-flex' );
expect( groupBlockClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsBlockEarlier = await editor.isBlockEarlierThan(
groupBlock,
'woocommerce/product-gallery-thumbnails',
'core/group'
);
expect( isThumbnailsBlockEarlier ).toBe( true );
await editor.saveSiteEditorEntities();
await page.goto( blockData.productPage );
const groupBlockFrontend = (
await frontendUtils.getBlockByClassWithParent(
'wp-block-group',
'woocommerce/product-gallery'
)
).first();
const groupBlockFrontendClassAttribute =
await groupBlockFrontend.getAttribute( 'class' );
expect( groupBlockFrontendClassAttribute ).toContain(
'is-layout-flex'
);
expect( groupBlockFrontendClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsFrontendBlockEarlier =
await frontendUtils.isBlockEarlierThanGroupBlock(
groupBlockFrontend,
'woocommerce/product-gallery-thumbnails'
);
expect( isThumbnailsFrontendBlockEarlier ).toBe( true );
} );
// We can test the bottom position of thumbnails by cross-checking:
// - The Gallery block has the classes "is-layout-flex" and "is-vertical".
// - The Thumbnails block has a higher index than the Large Image block.
test( 'Position thumbnails on the bottom of the large image', async ( {
page,
editor,
frontendUtils,
} ) => {
// Currently we are adding the block under the legacy Product Image Gallery block, but in the future we have to add replace the product gallery block with this block.
const parentBlock = await editor.getBlockByName(
'woocommerce/product-image-gallery'
);
const clientId =
( await parentBlock.getAttribute( 'data-block' ) ) ?? '';
const parentClientId =
( await editor.getBlockRootClientId( clientId ) ) ?? '';
await editor.selectBlocks( parentBlock );
await editor.insertBlock(
{ name: 'woocommerce/product-gallery' },
{ clientId: parentClientId }
);
await ( await editor.getBlockByName( blockData.name ) ).click();
await editor.openDocumentSettingsSidebar();
await page
.locator(
blockData.selectors.editor.bottomPositionThumbnailsOption
)
.click();
const groupBlock = editor.canvas
.locator( '[data-type="woocommerce/product-gallery"]' )
.locator( '[data-type="core/group"]' )
.first();
const groupBlockClassAttribute = await groupBlock.getAttribute(
'class'
);
expect( groupBlockClassAttribute ).toContain( 'is-layout-flex' );
expect( groupBlockClassAttribute ).toContain( 'is-vertical' );
const isThumbnailsBlockEarlier = await editor.isBlockEarlierThan(
groupBlock,
'woocommerce/product-gallery-thumbnails',
'core/group'
);
expect( isThumbnailsBlockEarlier ).toBe( false );
await editor.saveSiteEditorEntities();
await page.goto( blockData.productPage );
const groupBlockFrontend = (
await frontendUtils.getBlockByClassWithParent(
'wp-block-group',
'woocommerce/product-gallery'
)
).first();
const groupBlockFrontendClassAttribute =
await groupBlockFrontend.getAttribute( 'class' );
expect( groupBlockFrontendClassAttribute ).toContain(
'is-layout-flex'
);
expect( groupBlockFrontendClassAttribute ).toContain(
'is-vertical'
);
const isThumbnailsFrontendBlockEarlier =
await frontendUtils.isBlockEarlierThanGroupBlock(
groupBlockFrontend,
'woocommerce/product-gallery-thumbnails'
);
expect( isThumbnailsFrontendBlockEarlier ).toBe( false );
} );
// We can test the right position of thumbnails by cross-checking:
// - The Gallery block has the classes "is-layout-flex" and "is-nowrap".
// - The Thumbnails block has a higher index than the Large Image block.
test( 'Position thumbnails on the right of the large image', async ( {
page,
editor,
frontendUtils,
} ) => {
// Currently we are adding the block under the legacy Product Image Gallery block, but in the future we have to add replace the product gallery block with this block.
const parentBlock = await editor.getBlockByName(
'woocommerce/product-image-gallery'
);
const clientId =
( await parentBlock.getAttribute( 'data-block' ) ) ?? '';
const parentClientId =
( await editor.getBlockRootClientId( clientId ) ) ?? '';
await editor.selectBlocks( parentBlock );
await editor.insertBlock(
{ name: 'woocommerce/product-gallery' },
{ clientId: parentClientId }
);
await ( await editor.getBlockByName( blockData.name ) ).click();
await editor.openDocumentSettingsSidebar();
await page
.locator(
blockData.selectors.editor.rightPositionThumbnailsOption
)
.click();
const groupBlock = editor.canvas
.locator( '[data-type="woocommerce/product-gallery"]' )
.locator( '[data-type="core/group"]' )
.first();
const groupBlockClassAttribute = await groupBlock.getAttribute(
'class'
);
expect( groupBlockClassAttribute ).toContain( 'is-layout-flex' );
expect( groupBlockClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsBlockEarlier = await editor.isBlockEarlierThan(
groupBlock,
'woocommerce/product-gallery-thumbnails',
'core/group'
);
expect( isThumbnailsBlockEarlier ).toBe( false );
await editor.saveSiteEditorEntities();
await page.goto( blockData.productPage );
const groupBlockFrontend = (
await frontendUtils.getBlockByClassWithParent(
'wp-block-group',
'woocommerce/product-gallery'
)
).first();
const groupBlockFrontendClassAttribute =
await groupBlockFrontend.getAttribute( 'class' );
expect( groupBlockFrontendClassAttribute ).toContain(
'is-layout-flex'
);
expect( groupBlockFrontendClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsFrontendBlockEarlier =
await frontendUtils.isBlockEarlierThanGroupBlock(
groupBlockFrontend,
'woocommerce/product-gallery-thumbnails'
);
expect( isThumbnailsFrontendBlockEarlier ).toBe( false );
} );
test( 'Ensure entered Number of Thumbnails rounds to integer', async ( {
page,
editor,
} ) => {
await editor.insertBlock( {
name: 'woocommerce/product-gallery',
} );
await editor.selectBlocks(
test( 'renders as expected', async ( { page, editor } ) => {
await test.step( 'in editor', async () => {
const productGalleryBlock = editor.canvas.locator(
'[data-type="woocommerce/product-gallery"]'
);
await editor.openDocumentSettingsSidebar();
await expect(
productGalleryBlock.locator(
'[data-type="woocommerce/product-gallery-thumbnails"]'
)
).toBeVisible();
const numberOfThumbnailInput = page.getByRole( 'spinbutton', {
name: 'Number of Thumbnails',
} );
await expect(
productGalleryBlock.locator(
`[data-type="woocommerce/product-gallery-thumbnails"]:left-of(
[data-type="woocommerce/product-gallery-large-image"]
)`
)
).toBeVisible();
await changeNumberOfThumbnailsInputValue(
page,
numberOfThumbnailInput,
'4.2'
await editor.saveSiteEditorEntities();
} );
await test.step( 'in frontend', async () => {
await page.goto( '/product/v-neck-t-shirt/' );
const productGalleryBlock = page.locator(
'[data-block-name="woocommerce/product-gallery"]'
);
const numberOfThumbnailsOnScreen = editor.canvas.locator(
await expect(
productGalleryBlock.locator(
'[data-block-name="woocommerce/product-gallery-thumbnails"]'
)
).toBeVisible();
await expect(
productGalleryBlock.locator(
`[data-block-name="woocommerce/product-gallery-thumbnails"]:left-of(
[data-block-name="woocommerce/product-gallery-large-image"]
)`
)
).toBeVisible();
} );
} );
test.describe( 'settings', () => {
test( 'hides thumbnails', async ( { page, editor } ) => {
await test.step( 'in editor', async () => {
const productGalleryBlock = editor.canvas.locator(
'[data-type="woocommerce/product-gallery"]'
);
const thumbailsBlock = productGalleryBlock.locator(
'[data-type="woocommerce/product-gallery-thumbnails"]'
);
await expect( thumbailsBlock ).toBeVisible();
await editor.selectBlocks( thumbailsBlock );
await editor.openDocumentSettingsSidebar();
await page
.getByLabel( 'Editor settings' )
.locator( 'button[data-value="off"]' )
.click();
await expect( thumbailsBlock ).toBeHidden();
await editor.saveSiteEditorEntities();
} );
await test.step( 'in frontend', async () => {
await page.goto( '/product/v-neck-t-shirt/' );
const productGalleryBlock = page.locator(
'[data-block-name="woocommerce/product-gallery"]'
);
await expect(
productGalleryBlock.locator(
'[data-block-name="woocommerce/product-gallery-large-image"]:visible'
)
).toBeVisible();
await expect(
productGalleryBlock.locator(
'[data-block-name="woocommerce/product-gallery-thumbnails"]'
)
).toBeHidden();
} );
} );
for ( const position of [ 'left', 'bottom', 'right' ] ) {
test( `positions thumbnails to the ${ position }`, async ( {
page,
editor,
} ) => {
const layoutClass = {
left: 'left-of',
bottom: 'below',
right: 'right-of',
}[ position ];
await test.step( 'in editor', async () => {
const productGalleryBlock = editor.canvas.locator(
'[data-type="woocommerce/product-gallery"]'
);
await editor.selectBlocks( productGalleryBlock );
await editor.openDocumentSettingsSidebar();
await page
.getByLabel( 'Editor settings' )
.locator( `button[data-value="${ position }"]` )
.click();
await expect(
productGalleryBlock.locator(
`[data-type="woocommerce/product-gallery-thumbnails"]:${ layoutClass }(
[data-type="woocommerce/product-gallery-large-image"]
)`
)
).toBeVisible();
await editor.saveSiteEditorEntities();
} );
await test.step( 'in frontend', async () => {
await page.goto( '/product/v-neck-t-shirt/' );
const productGalleryBlock = page.locator(
'[data-block-name="woocommerce/product-gallery"]'
);
await expect(
productGalleryBlock.locator(
'[data-block-name="woocommerce/product-gallery-thumbnails"]'
)
).toBeVisible();
await expect(
productGalleryBlock.locator(
`[data-block-name="woocommerce/product-gallery-thumbnails"]:${ layoutClass }(
[data-block-name="woocommerce/product-gallery-large-image"]
)`
)
).toBeVisible();
} );
} );
}
test( 'rounds the number of thumbnails to integer', async ( {
page,
editor,
} ) => {
const productGalleryBlock = editor.canvas.locator(
'[data-type="woocommerce/product-gallery"]'
);
await editor.selectBlocks( productGalleryBlock );
await editor.openDocumentSettingsSidebar();
const numberOfThumbnailInput = page
.getByLabel( 'Editor settings' )
.getByRole( 'spinbutton', {
name: 'Number of Thumbnails',
} );
await numberOfThumbnailInput.fill( '4.2' );
await page.keyboard.press( 'Enter' );
const numberOfThumbnailsOnScreen = productGalleryBlock.locator(
'.wc-block-product-gallery-thumbnails__thumbnail'
);
await expect( numberOfThumbnailsOnScreen ).toHaveCount( 4 );
await changeNumberOfThumbnailsInputValue(
page,
numberOfThumbnailInput,
'4.7'
);
await numberOfThumbnailInput.fill( '4.7' );
await page.keyboard.press( 'Enter' );
await expect( numberOfThumbnailsOnScreen ).toHaveCount( 5 );
} );

View File

@ -65,50 +65,6 @@ export class Editor extends CoreEditor {
} );
}
async isBlockEarlierThan< T >(
containerBlock: T,
firstBlock: string,
secondBlock: string
) {
const container =
containerBlock instanceof Function
? await containerBlock()
: containerBlock;
if ( ! container ) {
throw new Error( 'Container block not found.' );
}
const childBlocks = container.locator( ':scope > .wp-block' );
let firstBlockIndex = -1;
let secondBlockIndex = -1;
for ( let i = 0; i < ( await childBlocks.count() ); i++ ) {
const blockName = await childBlocks
.nth( i )
.getAttribute( 'data-type' );
if ( blockName === firstBlock ) {
firstBlockIndex = i;
}
if ( blockName === secondBlock ) {
secondBlockIndex = i;
}
if ( firstBlockIndex !== -1 && secondBlockIndex !== -1 ) {
break;
}
}
if ( firstBlockIndex === -1 || secondBlockIndex === -1 ) {
throw new Error( 'Both blocks must exist within the editor' );
}
return firstBlockIndex < secondBlockIndex;
}
async transformIntoBlocks() {
// Select the block, so the button is visible.
const block = this.canvas

View File

@ -21,15 +21,6 @@ export class FrontendUtils {
return this.page.locator( selector );
}
async getBlockByClassWithParent( blockClass: string, parentName: string ) {
const parentBlock = await this.getBlockByName( parentName );
if ( ! parentBlock ) {
throw new Error( `Parent block "${ parentName }" not found.` );
}
const block = parentBlock.locator( `.${ blockClass }` );
return block;
}
async addToCart( itemName = '' ) {
await this.page.waitForLoadState( 'domcontentloaded' );
if ( itemName !== '' ) {
@ -99,93 +90,6 @@ export class FrontendUtils {
}
}
async isBlockEarlierThan< T >(
containerBlock: T,
firstBlock: string,
secondBlock: string
) {
const container =
containerBlock instanceof Function
? await containerBlock()
: containerBlock;
if ( ! container ) {
throw new Error( 'Container block not found.' );
}
const childBlocks = container.locator( '[data-block-name]' );
let firstBlockIndex = -1;
let secondBlockIndex = -1;
for ( let i = 0; i < ( await childBlocks.count() ); i++ ) {
const blockName = await childBlocks
.nth( i )
.getAttribute( 'data-block-name' );
if ( blockName === firstBlock ) {
firstBlockIndex = i;
}
if ( blockName === secondBlock ) {
secondBlockIndex = i;
}
if ( firstBlockIndex !== -1 && secondBlockIndex !== -1 ) {
break;
}
}
if ( firstBlockIndex === -1 || secondBlockIndex === -1 ) {
throw new Error( 'Both blocks must exist within the editor' );
}
return firstBlockIndex < secondBlockIndex;
}
async isBlockEarlierThanGroupBlock(
containerBlock: Locator,
firstBlock: string
) {
if ( ! containerBlock ) {
throw new Error( 'Container block not found.' );
}
const childBlocks: Locator = containerBlock.locator( '> div' );
let firstBlockIndex = -1;
let secondBlockIndex = -1;
for ( let i = 0; i < ( await childBlocks.count() ); i++ ) {
const blockName = await childBlocks
.nth( i )
.getAttribute( 'data-block-name' );
const isGroupBlock = await childBlocks
.nth( i )
.evaluate( ( node ) =>
node.classList.contains( 'wp-block-group' )
);
if ( blockName === firstBlock ) {
firstBlockIndex = i;
}
if ( isGroupBlock ) {
secondBlockIndex = i;
}
if ( firstBlockIndex !== -1 && secondBlockIndex !== -1 ) {
break;
}
}
if ( firstBlockIndex === -1 || secondBlockIndex === -1 ) {
throw new Error( 'Both blocks must exist within the editor' );
}
return firstBlockIndex < secondBlockIndex;
}
/**
* Playwright selectText causes flaky tests when running on local
* development machine. This method is more reliable on both environments.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Blocks E2E: Remove confusing utilities in favor of native locator functionality.