woocommerce/plugins/woocommerce-blocks/tests/e2e/utils/editor/editor-utils.page.ts

331 lines
8.8 KiB
TypeScript

/**
* External dependencies
*/
import { Page } from '@playwright/test';
import { Editor } from '@wordpress/e2e-test-utils-playwright';
import { BlockRepresentation } from '@wordpress/e2e-test-utils-playwright/build-types/editor/insert-block';
export class EditorUtils {
editor: Editor;
page: Page;
constructor( editor: Editor, page: Page ) {
this.editor = editor;
this.page = page;
}
async getBlockByName( name: string ) {
return this.editor.canvas.locator( `[data-type="${ name }"]` );
}
async getBlockByTypeWithParent( name: string, parentName: string ) {
const parentBlock = await this.getBlockByName( parentName );
if ( ! parentBlock ) {
throw new Error( `Parent block "${ parentName }" not found.` );
}
const block = parentBlock.locator( `[data-type="${ name }"]` );
return block;
}
// todo: Make a PR to @wordpress/e2e-test-utils-playwright to add this method.
/**
* Inserts a block after a given client ID.
*
*/
async insertBlock(
blockRepresentation: BlockRepresentation,
index?: string,
rootClientId?: string
) {
await this.page.evaluate(
( {
blockRepresentation: _blockRepresentation,
index: _index,
rootClientId: _rootClientId,
} ) => {
function recursiveCreateBlock( {
name,
attributes = {},
innerBlocks = [],
}: BlockRepresentation ): BlockRepresentation {
return window.wp.blocks.createBlock(
name,
attributes,
innerBlocks.map( ( innerBlock ) =>
recursiveCreateBlock( innerBlock )
)
);
}
const block = recursiveCreateBlock( _blockRepresentation );
window.wp.data
.dispatch( 'core/block-editor' )
.insertBlock( block, _index, _rootClientId );
},
{ blockRepresentation, index, rootClientId }
);
}
async closeModalByName( name: string ) {
const isModalOpen = await this.page.getByLabel( name ).isVisible();
// eslint-disable-next-line playwright/no-conditional-in-test
if ( isModalOpen ) {
await this.page
.getByLabel( name )
.getByRole( 'button', { name: 'Close' } )
.click();
}
}
async replaceBlockByBlockName( name: string, nameToInsert: string ) {
await this.page.evaluate(
( { name: _name, nameToInsert: _nameToInsert } ) => {
const blocks = window.wp.data
.select( 'core/block-editor' )
.getBlocks();
const firstMatchingBlock = blocks
.flatMap(
( {
innerBlocks,
}: {
innerBlocks: BlockRepresentation[];
} ) => innerBlocks
)
.find(
( block: BlockRepresentation ) => block.name === _name
);
const { clientId } = firstMatchingBlock;
const block = window.wp.blocks.createBlock( _nameToInsert );
window.wp.data
.dispatch( 'core/block-editor' )
.replaceBlock( clientId, block );
},
{ name, nameToInsert }
);
}
async getBlockRootClientId( clientId: string ) {
return this.page.evaluate< string | null, string >( ( id ) => {
return window.wp.data
.select( 'core/block-editor' )
.getBlockRootClientId( id );
}, clientId );
}
/**
* Toggles the global inserter.
*/
async toggleGlobalBlockInserter() {
// "Add block" selector is required to make sure performance comparison
// doesn't fail on older branches where we still had "Add block" as label.
await this.page.click(
'.edit-post-header [aria-label="Add block"],' +
'.edit-site-header [aria-label="Add block"],' +
'.edit-post-header [aria-label="Toggle block inserter"],' +
'.edit-site-header [aria-label="Toggle block inserter"],' +
'.edit-widgets-header [aria-label="Add block"],' +
'.edit-widgets-header [aria-label="Toggle block inserter"],' +
'.edit-site-header-edit-mode__inserter-toggle'
);
}
/**
* Checks if the global inserter is open.
*
* @return {Promise<boolean>} Whether the inserter is open or not.
*/
async isGlobalInserterOpen() {
return await this.page.evaluate( () => {
// "Add block" selector is required to make sure performance comparison
// doesn't fail on older branches where we still had "Add block" as
// label.
return !! document.querySelector(
'.edit-post-header [aria-label="Add block"].is-pressed,' +
'.edit-site-header-edit-mode [aria-label="Add block"].is-pressed,' +
'.edit-post-header [aria-label="Toggle block inserter"].is-pressed,' +
'.edit-site-header [aria-label="Toggle block inserter"].is-pressed,' +
'.edit-widgets-header [aria-label="Toggle block inserter"].is-pressed,' +
'.edit-widgets-header [aria-label="Add block"].is-pressed,' +
'.edit-site-header-edit-mode__inserter-toggle.is-pressed'
);
} );
}
/**
* Opens the global inserter.
*/
async openGlobalBlockInserter() {
if ( ! ( await this.isGlobalInserterOpen() ) ) {
await this.toggleGlobalBlockInserter();
await this.page.waitForSelector( '.block-editor-inserter__menu' );
}
}
async enterEditMode() {
await this.editor.page
.getByRole( 'button', {
name: 'Edit',
exact: true,
} )
.click();
}
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 waitForSiteEditorFinishLoading() {
await this.page
.frameLocator( 'iframe[title="Editor canvas"i]' )
.locator( 'body > *' )
.first()
.waitFor();
await this.page
.locator( '.edit-site-canvas-spinner' )
.waitFor( { state: 'hidden' } );
}
async setLayoutOption(
option:
| 'Align Top'
| 'Align Bottom'
| 'Align Middle'
| 'Stretch to Fill'
) {
const button = this.page.locator(
"button[aria-label='Change vertical alignment']"
);
await button.click();
await this.page.getByText( option ).click();
}
async setAlignOption(
option: 'Align Left' | 'Align Center' | 'Align Right' | 'None'
) {
const button = this.page.locator( "button[aria-label='Align']" );
await button.click();
await this.page.getByText( option ).click();
}
async saveTemplate() {
await Promise.all( [
this.editor.saveSiteEditorEntities(),
this.editor.page.waitForResponse(
( response ) =>
response.url().includes( 'wp-json/wp/v2/templates/' ) ||
response.url().includes( 'wp-json/wp/v2/template-parts/' )
),
] );
}
async closeWelcomeGuideModal() {
const isModalOpen = await this.page
.getByRole( 'dialog', { name: 'Welcome to the site editor' } )
.locator( 'div' )
.filter( {
hasText:
'Edit your siteDesign everything on your site — from the header right down to the',
} )
.nth( 2 )
.isVisible();
// eslint-disable-next-line playwright/no-conditional-in-test
if ( isModalOpen ) {
await this.page
.getByRole( 'button', { name: 'Get started' } )
.click();
}
}
async transformIntoBlocks() {
const isNotTransformedIntoBlocks = await this.page
.frameLocator( 'iframe[name="editor-canvas"]' )
.getByRole( 'button', { name: 'Transform into blocks' } )
.count();
if ( isNotTransformedIntoBlocks ) {
await this.page
.frameLocator( 'iframe[name="editor-canvas"]' )
.getByRole( 'group' )
.click();
await this.page
.frameLocator( 'iframe[name="editor-canvas"]' )
.getByRole( 'button', { name: 'Transform into blocks' } )
.click();
// save changes
await this.saveSiteEditorEntities();
}
}
// This method is the same as the one in @wordpress/e2e-test-utils-playwright. But for some reason
// it doesn't work as expected when imported from there. For its first run we get the following error:
// Error: locator.waitFor: Target closed
async saveSiteEditorEntities() {
const editorTopBar = this.page.getByRole( 'region', {
name: 'Editor top bar',
} );
const savePanel = this.page.getByRole( 'region', {
name: 'Save panel',
} );
// First Save button in the top bar.
await editorTopBar
.getByRole( 'button', { name: 'Save', exact: true } )
.click();
// Second Save button in the entities panel.
await savePanel
.getByRole( 'button', { name: 'Save', exact: true } )
.click();
await this.page
.getByRole( 'button', { name: 'Dismiss this notice' } )
.getByText( 'Site updated.' )
.waitFor();
}
}