2023-06-01 11:51:59 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2023-06-27 10:01:38 +00:00
|
|
|
import { Page } from '@playwright/test';
|
2023-06-01 11:51:59 +00:00
|
|
|
import { Editor } from '@wordpress/e2e-test-utils-playwright';
|
2023-06-27 10:01:38 +00:00
|
|
|
import { BlockRepresentation } from '@wordpress/e2e-test-utils-playwright/build-types/editor/insert-block';
|
2023-06-01 11:51:59 +00:00
|
|
|
|
|
|
|
export class EditorUtils {
|
|
|
|
editor: Editor;
|
2023-06-27 10:01:38 +00:00
|
|
|
page: Page;
|
|
|
|
constructor( editor: Editor, page: Page ) {
|
2023-06-01 11:51:59 +00:00
|
|
|
this.editor = editor;
|
2023-06-27 10:01:38 +00:00
|
|
|
this.page = page;
|
2023-06-01 11:51:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async getBlockByName( name: string ) {
|
|
|
|
return this.editor.canvas.locator( `[data-type="${ name }"]` );
|
|
|
|
}
|
2023-06-27 10:01:38 +00:00
|
|
|
|
2023-08-04 11:07:31 +00:00
|
|
|
async getBlockByTypeWithParent( name: string, parentName: string ) {
|
|
|
|
const parentBlock = await this.getBlockByName( parentName );
|
|
|
|
if ( ! parentBlock ) {
|
|
|
|
throw new Error( `Parent block "${ parentName }" not found.` );
|
|
|
|
}
|
2023-08-08 13:25:45 +00:00
|
|
|
const block = parentBlock.locator( `[data-type="${ name }"]` );
|
2023-08-04 11:07:31 +00:00
|
|
|
return block;
|
|
|
|
}
|
|
|
|
|
2023-06-27 10:01:38 +00:00
|
|
|
// 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 }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-28 10:04:28 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2023-08-16 09:54:09 +00:00
|
|
|
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 }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-06-27 10:01:38 +00:00
|
|
|
async getBlockRootClientId( clientId: string ) {
|
|
|
|
return this.page.evaluate< string | null, string >( ( id ) => {
|
|
|
|
return window.wp.data
|
|
|
|
.select( 'core/block-editor' )
|
|
|
|
.getBlockRootClientId( id );
|
|
|
|
}, clientId );
|
|
|
|
}
|
2023-06-29 13:41:22 +00:00
|
|
|
|
2023-08-07 15:59:06 +00:00
|
|
|
/**
|
|
|
|
* 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' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-29 13:41:22 +00:00
|
|
|
async enterEditMode() {
|
2023-08-10 23:28:38 +00:00
|
|
|
await this.editor.page
|
|
|
|
.getByRole( 'button', {
|
|
|
|
name: 'Edit',
|
|
|
|
exact: true,
|
|
|
|
} )
|
|
|
|
.click();
|
2023-06-29 13:41:22 +00:00
|
|
|
}
|
2023-08-04 11:07:31 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2023-08-10 23:28:38 +00:00
|
|
|
|
|
|
|
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' } );
|
|
|
|
}
|
2023-08-28 16:32:45 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2023-08-31 16:15:31 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2023-09-05 13:22:17 +00:00
|
|
|
|
|
|
|
async saveTemplate() {
|
|
|
|
await Promise.all( [
|
|
|
|
this.editor.saveSiteEditorEntities(),
|
2023-09-20 12:56:00 +00:00
|
|
|
this.editor.page.waitForResponse(
|
|
|
|
( response ) =>
|
|
|
|
response.url().includes( 'wp-json/wp/v2/templates/' ) ||
|
|
|
|
response.url().includes( 'wp-json/wp/v2/template-parts/' )
|
2023-09-05 13:22:17 +00:00
|
|
|
),
|
|
|
|
] );
|
|
|
|
}
|
2023-09-20 12:56:00 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2023-06-01 11:51:59 +00:00
|
|
|
}
|