Contents
Introduction
Playwright is now supported in @wordpress/scripts
version 26.13.0. In this article, I will introduce a simple way to implement E2E testing using Playwright in block development.
Notes
This article is based on version 26.16.0 of @wordpress/scripts
. If the version is between 26.13.0 and 26.15.0, please see the “(Supplementary) Preparing the config file” section.
Create a block template
Use @wordpress/create-block
to create a plugin template with one block. At the same time, add thewp-env
option so that you can also launch the local environment.
npx @wordpress/create-block playwright-test --wp-env
Starting the local environment.
npm run env start
First of all, let’s confirm that we can insert the block.
By default only fixed text is displayed, so make the text inside editable.
{
...
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": "p"
}
},
...
}
import {
RichText,
useBlockProps,
} from '@wordpress/block-editor';
export default function Edit( { attributes, setAttributes } ) {
const { content } = attributes;
return (
<RichText
tagName="p"
{ ...useBlockProps() }
value={ content }
onChange={ ( newContent ) =>
setAttributes( { content: newContent } )
}
/>
);
}
import { RichText, useBlockProps } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const { content } = attributes;
return (
<p { ...useBlockProps.save() }>
<RichText.Content value={ content } />
</p>
);
}
Let’s try building the block again. You should now be able to edit and save the text of the inserted block.
npm run build
Preparing for Playwright
When you install the latest @wordpress/scripts
, the necessary libraries are included in the dependencies, so there is no need to install any new npm packages.
$ npm ls @playwright/test
playwright-test@0.1.0 /home/username/projects/playwright-test
└─┬ @wordpress/scripts@26.16.0
├── @playwright/test@1.39.0
└─┬ @wordpress/e2e-test-utils-playwright@0.13.0
└── @playwright/test@1.39.0 deduped
$ npm ls @wordpress/e2e-test-utils-playwright
playwright-test@0.1.0 /home/wildworks/projects/playwright-test
└─┬ @wordpress/scripts@26.16.0
└── @wordpress/e2e-test-utils-playwright@0.13.0
Add scripts to run e2e tests
{
...
"scripts": {
"test:e2e": "wp-scripts test-playwright",
"test:e2e:debug": "wp-scripts test-playwright --debug"
}
...
}
@wordpress/scripts
has the default config file for Playwright, so it’s ready to run e2e tests.
Note: If the version of @wordpress/scripts
is between 26.13.0 and 26.15.0, please see the “(Supplementary) Preparing the config file” section.
There is no test file yet, let’s try running the e2e test using Playwright.
npm run test:e2e
> playwright-test@0.1.0 test:e2e
> wp-scripts test-playwright
Error: No tests found
Although you got a message that the test could not be found, you were able to confirm that it worked without any errors.
Here, in order to try overwriting the default configuration file, we will change the directory referenced by the tests from the default specs
to the tests/e2e
directory.
import { defineConfig } from '@playwright/test';
const baseConfig = require( '@wordpress/scripts/config/playwright.config.js' );
const config = defineConfig( {
...baseConfig,
testDir: './tests/e2e',
} );
export default config;
Writing a test
Let’s actually write a simple test. First, we are confirming that the custom block is inserted properly without errors.
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
test.describe( 'Block', () => {
test.beforeEach( async ( { admin } ) => {
// Create a new post before each test
await admin.createNewPost();
} );
test( 'should be created', async ( { editor } ) => {
// Insert a block
await editor.insertBlock( { name: 'create-block/playwright-test' } );
// Test that post content matches snapshot
expect( await editor.getEditedPostContent() ).toMatchSnapshot();
} );
} );
Run e2e tests.
npm run test:e2e
The first time you run it, there is no snapshot, so it should fail with an error like the one below:
Error: A snapshot doesn't exist at /home/username/projects/playwright-test/tests/e2e/__snapshots__/Block-should-be-created-1-chromium.txt, writing actual.
Instead, a snapshot is created with the following contents: This snapshot shows that the custom block has been properly inserted, so it is the snapshot we expected.
tests/e2e/__snapshots__/Block-should-be-created-1-chromium.txt
<!-- wp:create-block/playwright-test -->
<p class="wp-block-create-block-playwright-test"></p>
<!-- /wp:create-block/playwright-test -->
Run the e2e test again.
npm run test:e2e
The test should now pass.
> playwright-test@0.1.0 test:e2e
> wp-scripts test-playwright
Running 1 test using 1 worker
✓ 1 [chromium] › test.spec.js:9:6 › Block › should be created (2.2s)
1 passed (3.1s)
Debugging the test
Try running the test in debug mode. This is useful when writing complex tests and tracing whether operations are performed as expected.
npm run test:e2e:debug
Two windows should appear as shown below. Click once on the step-over button in the smaller window.
Every time you press the Step Over button, you will see the test procedure execute as expected.
Writing additional test
Since the text in this custom block is editable, so we will write a test to check whether the changed text is reflected correctly.
test.describe( 'Block', () => {
// ...
test( 'should be updated the content', async ( { editor, page } ) => {
// Insert a block
await editor.insertBlock( { name: 'create-block/playwright-test' } );
// Update the text inside the block
await page.keyboard.type( 'Hello World!' );
// Test that post content matches snapshot
expect( await editor.getEditedPostContent() ).toMatchSnapshot();
} );
} );
The second new test fails and creates a snapshot, but it should be the snapshot you expected.
<!-- wp:create-block/playwright-test -->
<p class="wp-block-create-block-playwright-test">Hello World!</p>
<!-- /wp:create-block/playwright-test -->
At the end
With @wordpres/scripts
supporting Playwright, I think it has become easier to perform e2e testing in block development. Also, @wordpress/e2e-test-utils-playwright
has many useful utilities. For a concrete usage example, you can take a look at the e2e test for the Gutenberg project.
(Supplementary) Preparing the config file
The handbook mentions that “Playwright will automatically detect the configuration file”, but as reported in this issue, depending on the version of @wordpress/scripts
, you may have to explicitly prepare a configuration file. A pull request that resolves this issue has already been merged, but this pull request is part of @wordpress/scripts
version 26.16.0, so if your version is between 26.13.0 and 26.15.0, you need to define a config file like the one below.
Follow the default configuration of @wordpress/scripts
and override the globalSetup
property to make it work. The directory referenced in the test has also been changed.
playwright.config.js
import { defineConfig } from '@playwright/test';
const baseConfig = require( '@wordpress/scripts/config/playwright.config.js' );
const config = defineConfig( {
...baseConfig,
globalSetup: require.resolve( './tests/e2e/global-setup.js' ),
testDir: './tests/e2e',
} );
export default config;
Prepare the file specified by the globalSetup
property and write it as follows. This code is based on global-setup.js
, which is also defined in WordPress core.
tests/e2e/global-setup.js
const { request } = require( '@playwright/test' );
const { RequestUtils } = require( '@wordpress/e2e-test-utils-playwright' );
async function globalSetup( config ) {
const { storageState, baseURL } = config.projects[ 0 ].use;
const storageStatePath =
typeof storageState === 'string' ? storageState : undefined;
const requestContext = await request.newContext( {
baseURL,
} );
const requestUtils = new RequestUtils( requestContext, {
storageStatePath,
} );
// Authenticate and save the storageState to disk.
await requestUtils.setupRest();
// Reset the test environment before running the tests.
await Promise.all( [
requestUtils.activateTheme( 'twentytwentyone' ),
requestUtils.deleteAllPosts(),
requestUtils.deleteAllBlocks(),
requestUtils.resetPreferences(),
] );
await requestContext.dispose();
}
export default globalSetup;