Support testing many variants of a block at runtime via dynamically generated templates (#44223)
This commit is contained in:
parent
cb967f6dfd
commit
5ecea1b8c2
|
@ -232,6 +232,7 @@
|
|||
"github-label-sync": "2.2.0",
|
||||
"glob": "7.2.3",
|
||||
"glob-promise": "4.2.2",
|
||||
"handlebars": "^4.7.8",
|
||||
"husky": "8.0.3",
|
||||
"ignore-loader": "0.1.2",
|
||||
"jest": "^29.7.0",
|
||||
|
|
|
@ -104,7 +104,7 @@ pnpm run test:e2e:classic-theme
|
|||
pnpm run test:e2e:block-theme-with-templates
|
||||
```
|
||||
|
||||
_Note: All command parameters of `test:e2e` can be used for these commands too.
|
||||
\_Note: All command parameters of `test:e2e` can be used for these commands too.
|
||||
|
||||
### Other ways of running tests
|
||||
|
||||
|
@ -144,6 +144,75 @@ To see all options, run the following command:
|
|||
npx playwright test --help
|
||||
```
|
||||
|
||||
### Generating dynamic posts to test block variations
|
||||
|
||||
Testing a single block can be daunting considering all the different attribute combinations that could be
|
||||
considered valid for a single block. The basic templating system available in this test suite allows for
|
||||
the generation of dynamic posts that can be used to test block variations.
|
||||
|
||||
Templates use the Handlebars templating system and you can put them anywhere. It's simplest to co-locate them
|
||||
with the test. You can easily pass custom attributes to a block in your template using the wp-block helper
|
||||
we've defined.
|
||||
|
||||
It looks like this in the template:
|
||||
|
||||
```handlebars
|
||||
{{#> wp-block name="woocommerce/featured-category" attributes=attributes /}}
|
||||
You can nest content here if you want to test the block with some content.
|
||||
{{/wp-block}}
|
||||
```
|
||||
|
||||
In your tests you can use `createPostFromTemplate` to create a post containing your template. If you use it
|
||||
more than once in your test you can extend the test suite and provide the posts as fixtures, like in the example
|
||||
below
|
||||
|
||||
```js
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
const test = base.extend< {
|
||||
dropdownBlockPostPage: TestingPost;
|
||||
defaultBlockPostPage: TestingPost;
|
||||
} >( {
|
||||
defaultBlockPostPage: async ( { requestUtils }, use ) => {
|
||||
const testingPost = await requestUtils.createPostFromTemplate(
|
||||
requestUtils,
|
||||
{ title: 'Product Filter Stock Status Block' },
|
||||
TEMPLATE_PATH,
|
||||
{}
|
||||
);
|
||||
|
||||
await use( testingPost );
|
||||
await requestUtils.deletePost( post.id );
|
||||
},
|
||||
|
||||
dropdownBlockPostPage: async ( { requestUtils }, use ) => {
|
||||
const testingPost = await requestUtils.createPostFromTemplate(
|
||||
requestUtils,
|
||||
{ title: 'Product Filter Stock Status Block' },
|
||||
TEMPLATE_PATH,
|
||||
{
|
||||
attributes: {
|
||||
displayStyle: 'dropdown',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await use( testingPost );
|
||||
await requestUtils.deletePost( post.id );
|
||||
},
|
||||
} );
|
||||
```
|
||||
|
||||
In your test you can navigate to the page. You won't need to clean it up, because
|
||||
the fixture will take care of that for you.
|
||||
|
||||
```js
|
||||
test( 'Test the block', async ( { page, defaultBlockPostPage } ) => {
|
||||
await page.goto( defaultBlockPostPage.link );
|
||||
// do your tests here
|
||||
} );
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If you run into problems the first time you try to run the tests, please run the following command before starting the test suite:
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
PageUtils,
|
||||
RequestUtils,
|
||||
} from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
import {
|
||||
TemplateApiUtils,
|
||||
STORAGE_STATE_PATH,
|
||||
|
@ -23,6 +22,16 @@ import {
|
|||
MiniCartUtils,
|
||||
WPCLIUtils,
|
||||
} from '@woocommerce/e2e-utils';
|
||||
import { Post } from '@wordpress/e2e-test-utils-playwright/build-types/request-utils/posts';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
PostPayload,
|
||||
createPostFromTemplate,
|
||||
deletePost,
|
||||
} from '../utils/create-dynamic-content';
|
||||
|
||||
/**
|
||||
* Set of console logging types observed to protect against unexpected yet
|
||||
|
@ -124,7 +133,14 @@ const test = base.extend<
|
|||
wpCliUtils: WPCLIUtils;
|
||||
},
|
||||
{
|
||||
requestUtils: RequestUtils;
|
||||
requestUtils: RequestUtils & {
|
||||
createPostFromTemplate: (
|
||||
post: PostPayload,
|
||||
templatePath: string,
|
||||
data: unknown
|
||||
) => Promise< Post >;
|
||||
deletePost: ( id: number ) => Promise< void >;
|
||||
};
|
||||
}
|
||||
>( {
|
||||
admin: async ( { page, pageUtils }, use ) => {
|
||||
|
@ -181,7 +197,26 @@ const test = base.extend<
|
|||
storageStatePath: STORAGE_STATE_PATH,
|
||||
} );
|
||||
|
||||
await use( requestUtils );
|
||||
const utilCreatePostFromTemplate = (
|
||||
post: Partial< PostPayload >,
|
||||
templatePath: string,
|
||||
data: unknown
|
||||
) =>
|
||||
createPostFromTemplate(
|
||||
requestUtils,
|
||||
post,
|
||||
templatePath,
|
||||
data
|
||||
);
|
||||
|
||||
const utilDeletePost = ( id: number ) =>
|
||||
deletePost( requestUtils, id );
|
||||
|
||||
await use( {
|
||||
...requestUtils,
|
||||
createPostFromTemplate: utilCreatePostFromTemplate,
|
||||
deletePost: utilDeletePost,
|
||||
} );
|
||||
},
|
||||
{ scope: 'worker', auto: true },
|
||||
],
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { test as base, expect } from '@woocommerce/e2e-playwright-utils';
|
||||
import path from 'path';
|
||||
import { Post } from '@wordpress/e2e-test-utils-playwright/build-types/request-utils/posts';
|
||||
|
||||
const TEMPLATE_PATH = path.join( __dirname, './stock-status.handlebars' );
|
||||
|
||||
const test = base.extend< {
|
||||
dropdownBlockPostPage: Post;
|
||||
defaultBlockPostPage: Post;
|
||||
} >( {
|
||||
defaultBlockPostPage: async ( { requestUtils }, use ) => {
|
||||
const testingPost = await requestUtils.createPostFromTemplate(
|
||||
{ title: 'Product Filter Stock Status Block' },
|
||||
TEMPLATE_PATH,
|
||||
{}
|
||||
);
|
||||
|
||||
await use( testingPost );
|
||||
await requestUtils.deletePost( testingPost.id );
|
||||
},
|
||||
|
||||
dropdownBlockPostPage: async ( { requestUtils }, use ) => {
|
||||
const testingPost = await requestUtils.createPostFromTemplate(
|
||||
{ title: 'Product Filter Stock Status Block' },
|
||||
TEMPLATE_PATH,
|
||||
{
|
||||
attributes: {
|
||||
displayStyle: 'dropdown',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await use( testingPost );
|
||||
await requestUtils.deletePost( testingPost.id );
|
||||
},
|
||||
} );
|
||||
|
||||
test.describe( 'Product Filter: Stock Status Block', async () => {
|
||||
test.describe( 'With default display style', () => {
|
||||
test( 'renders a checkbox list with the available stock statuses', async ( {
|
||||
page,
|
||||
defaultBlockPostPage,
|
||||
} ) => {
|
||||
await page.goto( defaultBlockPostPage.link );
|
||||
|
||||
const stockStatuses = page.locator(
|
||||
'.wc-block-components-checkbox__label'
|
||||
);
|
||||
|
||||
await expect( stockStatuses ).toHaveCount( 2 );
|
||||
await expect( stockStatuses.nth( 0 ) ).toHaveText( 'In stock' );
|
||||
await expect( stockStatuses.nth( 1 ) ).toHaveText( 'Out of stock' );
|
||||
} );
|
||||
|
||||
test( 'filters the list of products by selecting a stock status', async ( {
|
||||
page,
|
||||
defaultBlockPostPage,
|
||||
} ) => {
|
||||
await page.goto( defaultBlockPostPage.link );
|
||||
|
||||
const outOfStockCheckbox = page.getByText( 'Out of stock' );
|
||||
await outOfStockCheckbox.click();
|
||||
|
||||
// wait for navigation
|
||||
await page.waitForURL( /.*filter_stock_status=outofstock.*/ );
|
||||
|
||||
const products = page.locator( '.wc-block-product' );
|
||||
|
||||
await expect( products ).toHaveCount( 1 );
|
||||
} );
|
||||
} );
|
||||
|
||||
test.describe( 'With dropdown display style', () => {
|
||||
test( 'a dropdown is displayed with the available stock statuses', async ( {
|
||||
page,
|
||||
dropdownBlockPostPage,
|
||||
} ) => {
|
||||
await page.goto( dropdownBlockPostPage.link );
|
||||
|
||||
const dropdownLocator = page.locator(
|
||||
'.wc-interactivity-dropdown'
|
||||
);
|
||||
|
||||
await expect( dropdownLocator ).toBeVisible();
|
||||
await dropdownLocator.click();
|
||||
|
||||
await expect( page.getByText( 'In stock' ) ).toBeVisible();
|
||||
await expect( page.getByText( 'Out of stock' ) ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,22 @@
|
|||
<!-- wp:woocommerce/product-collection {"id":"bee7a337-f64e-4efd-be51-e68670b10000","queryId":0,"query":{"perPage":9,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":false,"taxQuery":{},"isProductCollectionBlock":true,"featured":false,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","displayLayout":{"type":"flex","columns":3,"shrinkColumns":true}} -->
|
||||
<div class='wp-block-woocommerce-product-collection'>
|
||||
<!-- wp:woocommerce/product-template -->
|
||||
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
|
||||
|
||||
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
|
||||
|
||||
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small"} /-->
|
||||
|
||||
<!-- wp:woocommerce/product-button {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small"} /-->
|
||||
<!-- /wp:woocommerce/product-template -->
|
||||
|
||||
<!-- wp:woocommerce/product-filter {"filterType":"stock-filter","heading":"Filter by Stock Status"} -->
|
||||
<!-- wp:heading {"level":3} -->
|
||||
<h3 class='wp-block-heading'>Filter by Stock Status</h3>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
{{#> wp-block blockName='woocommerce/product-filter-stock-status' attributes=attributes }}
|
||||
{{/ wp-block }}
|
||||
|
||||
</div>
|
||||
<!-- /wp:woocommerce/product-collection -->
|
|
@ -1,9 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { exec } from 'child_process';
|
||||
import { ExecException, exec } from 'child_process';
|
||||
|
||||
export function cli( cmd, args = [] ) {
|
||||
export function cli(
|
||||
cmd: string,
|
||||
args = []
|
||||
): Promise< {
|
||||
code: number;
|
||||
error: ExecException | null;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
} > {
|
||||
return new Promise( ( resolve ) => {
|
||||
exec( `${ cmd } ${ args.join( ' ' ) }`, ( error, stdout, stderr ) => {
|
||||
resolve( {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { readFile } from 'fs/promises';
|
||||
import Handlebars from 'handlebars';
|
||||
import {
|
||||
CreatePostPayload,
|
||||
Post,
|
||||
} from '@wordpress/e2e-test-utils-playwright/build-types/request-utils/posts';
|
||||
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
export type TestingPost = {
|
||||
post: Post;
|
||||
deletePost: () => Promise< void >;
|
||||
};
|
||||
|
||||
Handlebars.registerPartial(
|
||||
'wp-block',
|
||||
`
|
||||
<!-- wp:{{blockName}} {{{stringify attributes}}} -->
|
||||
{{> @partial-block }}
|
||||
<!-- /wp:{{blockName}} -->
|
||||
`
|
||||
);
|
||||
|
||||
Handlebars.registerHelper( 'stringify', function ( context ) {
|
||||
return JSON.stringify( context );
|
||||
} );
|
||||
|
||||
export const deletePost = async ( requestUtils: RequestUtils, id: number ) => {
|
||||
return requestUtils.rest( {
|
||||
method: 'DELETE',
|
||||
path: `/wp/v2/posts/${ id }`,
|
||||
params: {
|
||||
force: true,
|
||||
},
|
||||
} );
|
||||
};
|
||||
|
||||
const posts: number[] = [];
|
||||
|
||||
const createPost = async (
|
||||
requestUtils: RequestUtils,
|
||||
payload: CreatePostPayload
|
||||
) => {
|
||||
const post = await requestUtils.createPost( payload );
|
||||
posts.push( post.id );
|
||||
return post;
|
||||
};
|
||||
|
||||
export type PostPayload = Partial< CreatePostPayload >;
|
||||
|
||||
export const createPostFromTemplate = async (
|
||||
requestUtils: RequestUtils,
|
||||
post: PostPayload,
|
||||
templatePath: string,
|
||||
data: unknown
|
||||
) => {
|
||||
const templateContent = await readFile( templatePath, 'utf8' );
|
||||
const content = Handlebars.compile( templateContent )( data );
|
||||
|
||||
const payload: CreatePostPayload = {
|
||||
status: 'publish',
|
||||
date_gmt: new Date().toISOString(),
|
||||
content,
|
||||
...post,
|
||||
};
|
||||
|
||||
return createPost( requestUtils, payload );
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: Add a system to support dynamic template testing in blocks e2e tests.
|
||||
|
|
@ -4326,6 +4326,9 @@ importers:
|
|||
glob-promise:
|
||||
specifier: 4.2.2
|
||||
version: 4.2.2(glob@7.2.3)
|
||||
handlebars:
|
||||
specifier: ^4.7.8
|
||||
version: 4.7.8
|
||||
husky:
|
||||
specifier: 8.0.3
|
||||
version: 8.0.3
|
||||
|
|
Loading…
Reference in New Issue