{"id":210,"date":"2023-11-05T13:59:56","date_gmt":"2023-11-05T13:59:56","guid":{"rendered":"https:\/\/aki-hamano.blog\/?p=210"},"modified":"2025-10-25T03:53:41","modified_gmt":"2025-10-25T03:53:41","slug":"block-e2e","status":"publish","type":"post","link":"https:\/\/aki-hamano.blog\/en\/2023\/11\/05\/block-e2e\/","title":{"rendered":"Introducing e2e testing to WordPress block development"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"f296\">Playwright is now supported in&nbsp;<code>@wordpress\/scripts<\/code>&nbsp;version 26.13.0. In this article, I will introduce a simple way to implement E2E testing using Playwright in block development.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Notes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"6173\">This article is based on version 26.16.0 of&nbsp;<code>@wordpress\/scripts<\/code>. If the version is between 26.13.0 and 26.15.0, please see the \u201c<a href=\"#supplementary\">(Supplementary) Preparing the config file<\/a>\u201d section.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create a block template<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"80fe\">Use&nbsp;<code>@wordpress\/create-block<\/code>&nbsp;to create a plugin template with one block. At the same time, add the<code>wp-env<\/code>&nbsp;option so that you can also launch the local environment.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>npx @wordpress\/create-block playwright-test --wp-env<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"06cf\">Starting the local environment.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>npm run env start<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"b370\">First of all, let\u2019s confirm that we can insert the block.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"700\" height=\"251\" src=\"https:\/\/aki-hamano.blog\/wp-content\/uploads\/2023\/11\/e2e_1.png\" alt=\"Block Template\" class=\"wp-image-221\" srcset=\"https:\/\/aki-hamano.blog\/wp-content\/uploads\/2023\/11\/e2e_1.png 700w, https:\/\/aki-hamano.blog\/wp-content\/uploads\/2023\/11\/e2e_1-300x108.png 300w\" sizes=\"auto, (max-width: 700px) 100vw, 700px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"6ab4\">By default only fixed text is displayed, so make the text inside editable.<\/p>\n\n\n\n<pre data-label=\"src\/block.json\" id=\"src\/block.json\" class=\"wp-block-code lang-json\"><code>{\n  ...\n  \"attributes\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"source\": \"html\",\n      \"selector\": \"p\"\n    }\n  },\n  ...\n}<\/code><\/pre>\n\n\n\n<pre data-label=\"src\/edit.js\" id=\"src\/edit.js\" class=\"wp-block-code lang-jsx\"><code>import {\n  RichText,\n  useBlockProps,\n} from '@wordpress\/block-editor';\n\nexport default function Edit( { attributes, setAttributes } ) {\n  const { content } = attributes;\n  return (\n    &lt;RichText\n      tagName=\"p\"\n      { ...useBlockProps() }\n      value={ content }\n      onChange={ ( newContent ) =&gt;\n         setAttributes( { content: newContent } )\n      }\n    \/&gt;\n  );\n}<\/code><\/pre>\n\n\n\n<pre data-label=\"src\/save.js\" id=\"src\/save.js\" class=\"wp-block-code lang-jsx\"><code>import { RichText, useBlockProps } from '@wordpress\/block-editor';\n\nexport default function save( { attributes } ) {\n  const { content } = attributes;\n  return (\n    &lt;p { ...useBlockProps.save() }&gt;\n      &lt;RichText.Content value={ content } \/&gt;\n    &lt;\/p&gt;\n  );\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"4030\">Let\u2019s try building the block again. You should now be able to edit and save the text of the inserted block.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>npm run build<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Preparing for Playwright<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"4b80\">When you install the latest&nbsp;<code>@wordpress\/scripts<\/code>, the necessary libraries are included in the dependencies, so there is no need to install any new npm packages.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>$ npm ls @playwright\/test\nplaywright-test@0.1.0 \/home\/username\/projects\/playwright-test\n\u2514\u2500\u252c @wordpress\/scripts@26.16.0\n  \u251c\u2500\u2500 @playwright\/test@1.39.0\n  \u2514\u2500\u252c @wordpress\/e2e-test-utils-playwright@0.13.0\n    \u2514\u2500\u2500 @playwright\/test@1.39.0 deduped<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>$ npm ls @wordpress\/e2e-test-utils-playwright\nplaywright-test@0.1.0 \/home\/wildworks\/projects\/playwright-test\n\u2514\u2500\u252c @wordpress\/scripts@26.16.0\n  \u2514\u2500\u2500 @wordpress\/e2e-test-utils-playwright@0.13.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"f1c2\">Add scripts to run e2e tests<\/p>\n\n\n\n<pre data-label=\"package.json\" id=\"package.json\" class=\"wp-block-code lang-json\"><code>{\n  ...\n  \"scripts\": {\n    \"test:e2e\": \"wp-scripts test-playwright\",\n    \"test:e2e:debug\": \"wp-scripts test-playwright --debug\"\n  }\n  ...\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"bc3f\"><code>@wordpress\/scripts<\/code>&nbsp;has<a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/d137227a67c43d04a6cba0cfacf8dd7f95cb74d5\/packages\/scripts\/config\/playwright.config.js\" rel=\"noreferrer noopener\" target=\"_blank\">&nbsp;the default config file for Playwright<\/a>, so it\u2019s ready to run e2e tests.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"75dc\">Note: If the version of&nbsp;<code>@wordpress\/scripts<\/code>&nbsp;is between 26.13.0 and 26.15.0, please see the \u201c<a href=\"#supplementary\">(Supplementary) Preparing the config file<\/a>\u201d section.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"eec7\">There is no test file yet, let\u2019s try running the e2e test using Playwright.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>npm run test:e2e\n\n&gt; playwright-test@0.1.0 test:e2e\n&gt; wp-scripts test-playwright\nError: No tests found<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"7e4c\">Although you got a message that the test could not be found, you were able to confirm that it worked without any errors.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"484d\">Here, in order to try overwriting the default configuration file, we will change the directory referenced by the tests from the default&nbsp;<code>specs<\/code>&nbsp;to the&nbsp;<code>tests\/e2e<\/code>&nbsp;directory.<\/p>\n\n\n\n<pre data-label=\"playwright.config.js\" id=\"playwright.config.js\" class=\"wp-block-code lang-js\"><code>import { defineConfig } from '@playwright\/test';\n\nconst baseConfig = require( '@wordpress\/scripts\/config\/playwright.config.js' );\nconst config = defineConfig( {\n  ...baseConfig,\n  testDir: '.\/tests\/e2e',\n} );\nexport default config;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Writing a test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"a345\">Let\u2019s actually write a simple test. First, we are confirming that the custom block is inserted properly without errors.<\/p>\n\n\n\n<pre data-label=\"tests\/e2e\/test.spec.js\" id=\"tests\/e2e\/test.spec.js\" class=\"wp-block-code lang-js\"><code>import { test, expect } from '@wordpress\/e2e-test-utils-playwright';\n\ntest.describe( 'Block', () =&gt; {\n  test.beforeEach( async ( { admin } ) =&gt; {\n    \/\/ Create a new post before each test\n    await admin.createNewPost();\n  } );\n\n  test( 'should be created', async ( { editor } ) =&gt; {\n    \/\/ Insert a block\n    await editor.insertBlock( { name: 'create-block\/playwright-test' } );\n    \/\/ Test that post content matches snapshot\n    expect( await editor.getEditedPostContent() ).toMatchSnapshot();\n  } );\n} );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"e4e5\">Run e2e tests.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>npm run test:e2e<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"1f74\">The first time you run it, there is no snapshot, so it should fail with an error like the one below:<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>Error: A snapshot doesn't exist at \/home\/username\/projects\/playwright-test\/tests\/e2e\/__snapshots__\/Block-should-be-created-1-chromium.txt, writing actual.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"2876\">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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"47cb\"><code>tests\/e2e\/__snapshots__\/Block-should-be-created-1-chromium.txt<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code lang-html\"><code>&lt;!-- wp:create-block\/playwright-test --&gt;\n&lt;p class=\"wp-block-create-block-playwright-test\"&gt;&lt;\/p&gt;\n&lt;!-- \/wp:create-block\/playwright-test --&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"ca8c\">Run the e2e test again.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>npm run test:e2e<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"3902\">The test should now pass.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>&gt; playwright-test@0.1.0 test:e2e\n&gt; wp-scripts test-playwright\n\nRunning 1 test using 1 worker\n  \u2713  1 &#91;chromium] \u203a test.spec.js:9:6 \u203a Block \u203a should be created (2.2s)\n  1 passed (3.1s)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Debugging the test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"0e67\">Try running the test in debug mode. This is useful when writing complex tests and tracing whether operations are performed as expected.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>npm run test:e2e:debug<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"2adc\">Two windows should appear as shown below. Click once on the step-over button in the smaller window.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/miro.medium.com\/v2\/resize:fit:700\/0*GmnvBcranZbKenl6\" alt=\"Debugging the test\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"a6da\">Every time you press the Step Over button, you will see the test procedure execute as expected.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Writing additional test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"56bb\">Since the text in this custom block is editable, so we will write a test to check whether the changed text is reflected correctly.<\/p>\n\n\n\n<pre data-label=\"tests\/e2e\/test.spec.js\" id=\"tests\/e2e\/test.spec.js\" class=\"wp-block-code lang-js\"><code>test.describe( 'Block', () =&gt; {\n  \/\/ ...\n\n  test( 'should be updated the content', async ( { editor, page } ) =&gt; {\n    \/\/ Insert a block\n    await editor.insertBlock( { name: 'create-block\/playwright-test' } );\n    \/\/ Update the text inside the block\n    await page.keyboard.type( 'Hello World!' );\n    \/\/ Test that post content matches snapshot\n    expect( await editor.getEditedPostContent() ).toMatchSnapshot();\n  } );\n} );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"8c32\">The second new test fails and creates a snapshot, but it should be the snapshot you expected.<\/p>\n\n\n\n<pre data-label=\"tests\/e2e\/__snapshots__\/Block-should-be-updated-the-content-1-chromium.txt\" id=\"tests\/e2e\/__snapshots__\/Block-should-be-updated-the-content-1-chromium.txt\" class=\"wp-block-code lang-html\"><code>&lt;!-- wp:create-block\/playwright-test --&gt;\n&lt;p class=\"wp-block-create-block-playwright-test\"&gt;Hello World!&lt;\/p&gt;\n&lt;!-- \/wp:create-block\/playwright-test --&gt;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">At the end<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"2c34\">With&nbsp;<code>@wordpres\/scripts<\/code>&nbsp;supporting Playwright, I think it has become easier to perform e2e testing in block development. Also,&nbsp;<code>@wordpress\/e2e-test-utils-playwright<\/code>&nbsp;has many useful utilities. For a concrete usage example, you can take a look at the e2e test for the Gutenberg project.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/trunk\/packages\/e2e-test-utils-playwright\/README.md\" target=\"_blank\" rel=\"noreferrer noopener\">@wordpress\/e2e-test-utils-playwright README<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/tree\/trunk\/test\/e2e\" target=\"_blank\" rel=\"noreferrer noopener\">e2e testing in the Gutenberg project<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"supplementary\">(Supplementary) Preparing the config file<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"4b3f\"><a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-scripts\/#test-playwright\" rel=\"noreferrer noopener\" target=\"_blank\">The handbook<\/a>&nbsp;mentions that \u201cPlaywright will automatically detect the configuration file\u201d, but as reported in&nbsp;<a href=\"https:\/\/github.com\/WordPress\/gutenberg\/issues\/55419\" rel=\"noreferrer noopener\" target=\"_blank\">this issue<\/a>, depending on the version of&nbsp;<code>@wordpress\/scripts<\/code>, you may have to explicitly prepare a configuration file.&nbsp;<a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/55453\" rel=\"noreferrer noopener\" target=\"_blank\">A pull request<\/a>&nbsp;that resolves this issue has already been merged, but this pull request is part of&nbsp;<code>@wordpress\/scripts<\/code>&nbsp;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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"058d\">Follow the default configuration of&nbsp;<code>@wordpress\/scripts<\/code>&nbsp;and override the&nbsp;<code>globalSetup<\/code>&nbsp;property to make it work. The directory referenced in the test has also been changed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"23b4\"><code>playwright.config.js<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code lang-js\"><code>import { defineConfig } from '@playwright\/test';\nconst baseConfig = require( '@wordpress\/scripts\/config\/playwright.config.js' );\nconst config = defineConfig( {\n  ...baseConfig,\n  globalSetup: require.resolve( '.\/tests\/e2e\/global-setup.js' ),\n  testDir: '.\/tests\/e2e',\n} );\nexport default config;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"1e2a\">Prepare the file specified by the&nbsp;<code>globalSetup<\/code>&nbsp;property and write it as follows. This code is based on&nbsp;<code>global-setup.js<\/code>, which is also&nbsp;<a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/c9ff475e1fe63de1356bc6a3792e6fd836a8c587\/tests\/e2e\/config\/global-setup.js\" rel=\"noreferrer noopener\" target=\"_blank\">defined in WordPress core<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"2e5f\"><code>tests\/e2e\/global-setup.js<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code lang-js\"><code>const { request } = require( '@playwright\/test' );\nconst { RequestUtils } = require( '@wordpress\/e2e-test-utils-playwright' );\n\nasync function globalSetup( config ) {\n  const { storageState, baseURL } = config.projects&#91; 0 ].use;\n  const storageStatePath =\n    typeof storageState === 'string' ? storageState : undefined;\n  const requestContext = await request.newContext( {\n    baseURL,\n  } );\n  const requestUtils = new RequestUtils( requestContext, {\n    storageStatePath,\n   } );\n  \/\/ Authenticate and save the storageState to disk.\n  await requestUtils.setupRest();\n  \/\/ Reset the test environment before running the tests.\n  await Promise.all( &#91;\n    requestUtils.activateTheme( 'twentytwentyone' ),\n    requestUtils.deleteAllPosts(),\n    requestUtils.deleteAllBlocks(),\n    requestUtils.resetPreferences(),\n  ] );\n  await requestContext.dispose();\n}\nexport default globalSetup;<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Playwright is now supported in&nbsp;@wordpress\/scripts&nbsp;version 26.13.0. In this article, I w [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_locale":"en_US","_original_post":"https:\/\/aki-hamano.blog\/?p=197","footnotes":""},"categories":[13],"tags":[],"class_list":["post-210","post","type-post","status-publish","format-standard","hentry","category-ci-cd-test","en-US"],"_links":{"self":[{"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/posts\/210","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/comments?post=210"}],"version-history":[{"count":17,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/posts\/210\/revisions"}],"predecessor-version":[{"id":251,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/posts\/210\/revisions\/251"}],"wp:attachment":[{"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/media?parent=210"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/categories?post=210"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/tags?post=210"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}