{"id":6048,"date":"2026-05-04T07:57:36","date_gmt":"2026-05-04T07:57:36","guid":{"rendered":"https:\/\/developer.wordpress.org\/news\/?p=6048"},"modified":"2026-05-04T07:57:36","modified_gmt":"2026-05-04T07:57:36","slug":"getting-started-writing-wordpress-e2e-tests-with-playwright","status":"publish","type":"post","link":"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/","title":{"rendered":"Getting started writing WordPress E2E Tests with Playwright"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">You have already seen how <a href=\"https:\/\/developer.wordpress.org\/news\/2025\/09\/implementing-namespaces-and-coding-standards-in-wordpress-plugin-development\/\">namespaces and coding standards<\/a>, and adding <a href=\"https:\/\/developer.wordpress.org\/news\/2025\/12\/how-to-add-automated-unit-tests-to-your-wordpress-plugin\/\">automated unit tests<\/a> can increase your confidence that your project works as intended. But there&#8217;s another layer worth adding: end-to-end (E2E) tests. They let you test your project from a different angle, covering cases that unit tests can&#8217;t.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this article, you&#8217;ll learn how to set up Playwright for WordPress E2E testing and write tests that cover block variations, block patterns, and the front-end after setting things up with the REST API. The practical examples cover real-world scenarios, giving you a solid foundation you can adapt to cover your own project&#8217;s features.<\/p>\n\n\n\n<div class=\"wp-block-group has-light-grey-2-background-color has-background is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:2px;margin-top:var(--wp--preset--spacing--30);margin-bottom:var(--wp--preset--spacing--30);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--30)\">\n<p class=\"has-large-font-size wp-block-paragraph\" style=\"font-style:normal;font-weight:600;line-height:1\">Table of Contents<\/p>\n\n\n\n<nav aria-label=\"Table of Contents\" class=\"wp-block-table-of-contents\"><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#what-is-e2e-testing\">What is E2E testing?<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#before-you-begin\">Before you begin<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#the-project-under-test\">The project under test<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#required-tools\">Required tools<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#project-setup\">Project setup<\/a><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#local-environment-setup-with-wp-env\">Local environment setup with wp-env<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#playwright-setup\">Playwright setup<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#installing-dependencies\">Installing dependencies<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#adding-a-configuration-file\">Adding a configuration file<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#verifying-the-setup\">Verifying the setup<\/a><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#your-first-test\">Your first test<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#test-walkthrough\">Test walkthrough<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#running-tests-in-ui-mode\">Running tests in UI Mode<\/a><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#testing-the-book-reviews-project\">Testing the Book Reviews project<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#testing-the-book-author-block\">Testing the Book Author block<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#planning-the-test\">Planning the test<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#inserting-the-block\">Inserting the block<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#verifying-the-block\">Verifying the block<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#verifying-the-attributes\">Verifying the attributes<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#verifying-the-functionality\">Verifying the functionality<\/a><\/li><\/ol><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#testing-the-book-review-card-pattern\">Testing the Book Review Card pattern<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#inserting-the-pattern\">Inserting the pattern<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#verifying-the-pattern\">Verifying the pattern<\/a><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#testing-the-front-end-and-meta-values\">Testing the front-end and meta values<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#creating-the-post-via-rest-api\">Creating the post via REST API<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#verifying-the-front-end-output\">Verifying the front-end output<\/a><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"https:\/\/developer.wordpress.org\/news\/2026\/05\/getting-started-writing-wordpress-e2e-tests-with-playwright\/#further-reading\">Further reading<\/a><\/li><\/ol><\/nav>\n<\/div>\n\n\n\n<h2 id=\"what-is-e2e-testing\" class=\"wp-block-heading\">What is E2E testing?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">E2E tests can be used for performance, accessibility, and visual testing, but most commonly they take the form of UI-driven functional tests, simulating the actions a user would take in a browser interacting with an application, e.g. WordPress.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Unlike <a href=\"https:\/\/developer.wordpress.org\/news\/2025\/12\/how-to-add-automated-unit-tests-to-your-wordpress-plugin\/\">unit tests<\/a>, E2E tests are macro-level. Even when testing a specific feature, they examine how multiple components and layers of the application work together as a whole. This broader scope comes at a cost: E2E tests are slower and sometimes more fragile than unit tests. Because they interact with the full application stack, a change in any layer, the UI, network, or database, could break a test. They are therefore best used to cover critical user flows rather than every possible scenario.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Gutenberg project has <a href=\"https:\/\/make.wordpress.org\/core\/2019\/06\/27\/introducing-the-wordpress-e2e-tests\/\">used E2E tests extensively<\/a> right from the beginning, as they lend themselves well to the nature of that project. Since then, they have <a href=\"https:\/\/make.wordpress.org\/core\/2020\/08\/07\/e2e-end-to-end-testing-in-core-working-group-proposal\/\">made their way into WordPress Core<\/a> as well, complementing the already extensive <a href=\"https:\/\/make.wordpress.org\/core\/handbook\/testing\/automated-testing\/phpunit\/\">PHPUnit<\/a> test suite.<\/p>\n\n\n\n<h2 id=\"before-you-begin\" class=\"wp-block-heading\">Before you begin<\/h2>\n\n\n\n<h3 id=\"the-project-under-test\" class=\"wp-block-heading\">The project under test<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Before moving forward, make sure you are familiar with the <a href=\"https:\/\/developer.wordpress.org\/news\/2024\/05\/building-a-book-review-site-with-block-bindings-part-1-custom-fields-and-block-variations\/\">Building a book review site with Block Bindings<\/a> series, as the tests in this article are based on the features built there.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fcdea9&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fcdea9\" class=\"wp-block-image alignwide size-large wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"640\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-project-1024x640.png\" alt=\"\" class=\"wp-image-6063\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-project-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-project-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-project-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-project-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-project-2048x1280.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s a two-part series and well worth reading from start to finish, as there&#8217;s a lot to learn from it. That said, skimming it is enough to follow along, even if you skip the more technical parts.<\/p>\n\n\n\n<h3 id=\"required-tools\" class=\"wp-block-heading\">Required tools<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;d like to follow along with the coding examples, make sure you have Git, Node.js, and Docker installed. Docker is a <a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-env\/#prerequisites\">requirement for <code>wp-env<\/code><\/a>, which will be used for the local WordPress environment setup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you need help installing any of these tools, refer to the <a href=\"https:\/\/developer.wordpress.org\/block-editor\/getting-started\/devenv\/\">Block Development Environment<\/a> guide.<\/p>\n\n\n\n<h3 id=\"project-setup\" class=\"wp-block-heading\">Project setup<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To set up what <a href=\"https:\/\/profiles.wordpress.org\/greenshady\/\">Justin Tadlock<\/a> built, download or clone the Building a Book Review Site with Block Bindings <a href=\"https:\/\/github.com\/wptrainingteam\/tt4-book-reviews\">repository<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Start from the <code>master<\/code> branch, but if you ever get stuck you can view the <a href=\"https:\/\/github.com\/wptrainingteam\/tt4-book-reviews\/compare\/master...feature\/e2e-playwright-tests\"><code>feature\/e2e-playwright-tests<\/code><\/a> branch to see the final code of the E2E tests added as part of this article.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you have the files locally, install the dependencies, which already include <code>wp-scripts<\/code> among other things, from within the project folder by running:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npm install<\/code><\/pre>\n\n\n\n<h2 id=\"local-environment-setup-with-wp-env\" class=\"wp-block-heading\">Local environment setup with wp-env<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To run the E2E tests, you need a WordPress environment up and running with everything installed and configured, in this case the TT4 Book Reviews child theme, along with the <a href=\"https:\/\/wordpress.org\/themes\/twentytwentyfour\/\">Twenty Twenty-Four<\/a> parent theme, installed and active.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a local WordPress environment, <code>wp-env<\/code> remains a solid choice, as it minimizes setup time, is maintained by the WordPress project itself, and integrates well with other WordPress tooling. That said, <code>wp-env<\/code> isn&#8217;t a strict requirement, and it&#8217;s also not uncommon to maintain a dedicated test site hosted on a server instead of a local installation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To install <code>wp-env<\/code>, run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npm install @wordpress\/env@^10.39.0 --save-dev<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Next, create a <code>.wp-env.json<\/code> configuration file with the following:<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">{\n\t\"$schema\": \"https:\/\/schemas.wp.org\/trunk\/wp-env.json\",\n\t\"themes\": [ \".\" ],\n\t\"lifecycleScripts\": {\n\t\t\"afterStart\": \"THEME_SLUG=$(basename \\\"$PWD\\\"); for SERVICE in cli tests-cli; do wp-env run \\\"$SERVICE\\\" wp theme activate \\\"$THEME_SLUG\\\"; done\"\n\t}\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This maps the current folder as a theme, and the <a href=\"https:\/\/github.com\/WordPress\/gutenberg\/issues\/26766\">lifecycle script activates the theme<\/a> on both the development and test environments that <code>wp-env<\/code> spins up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since the Twenty Twenty-Four theme is available by default in <code>wp-env<\/code>, no extra step is needed to install it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Start the environment by running:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx wp-env start<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once it has started, you should see output similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">WordPress development site started at http:\/\/localhost:8888\nWordPress test site started at http:\/\/localhost:8889\nMySQL is listening on port 32770\nMySQL for automated testing is listening on port 32771\n\n \u2714 Done! (in 29s 136ms)<\/code><\/pre>\n\n\n\n<div class=\"wp-block-wporg-notice is-alert-notice\"><div class=\"wp-block-wporg-notice__icon\"><\/div><div class=\"wp-block-wporg-notice__content\"><p>If you get an error saying that port <code>8888<\/code> or <code>8889<\/code> is not available, it means another service is already using it. The simplest fix is to stop that service and restart it when you will need it later. Alternatively, you can configure <code>wp-env<\/code> to <a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-env\/#custom-port-numbers\">use a different port<\/a>, but you will also need to update the <code>webServer<\/code> setting in the Playwright config below.<\/p><\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Many WordPress tools also assume a <code>wp-env<\/code> script is defined in <code>package.json<\/code>, including the default Playwright configuration that will be added in the next step. Add it by modifying your existing file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">{\n\t\"scripts\": {\n\t\t\"start\": \"...\",\n\t\t\"build\": \"...\",\n\t\t\"wp-env\": \"wp-env\"\n\t},\n\t\"devDependencies\": {\n\t\t\/\/ ...\n\t}\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If it&#8217;s your first time using <code>wp-env<\/code> and you&#8217;d like to learn more, the <a href=\"https:\/\/developer.wordpress.org\/block-editor\/getting-started\/devenv\/get-started-with-wp-env\/\">Get started with wp-env<\/a> guide or the <a href=\"https:\/\/developer.wordpress.org\/news\/2023\/03\/quick-and-easy-local-wordpress-development-with-wp-env\/\">Quick and easy local WordPress development with wp-env<\/a> article are both excellent places to begin.<\/p>\n\n\n\n<h2 id=\"playwright-setup\" class=\"wp-block-heading\">Playwright setup<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For E2E testing,&nbsp;WordPress uses <a href=\"https:\/\/playwright.dev\/\">Playwright<\/a>, which is a reliable, modern, and widely adopted solution.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fcf0e5&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fcf0e5\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-playwright.png\" alt=\"\" class=\"wp-image-6062\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-playwright.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-playwright-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-playwright-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-playwright-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-playwright-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-playwright-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">It isn&#8217;t the only option for E2E testing, though. WordPress <a href=\"https:\/\/make.wordpress.org\/core\/2022\/03\/23\/migrating-wordpress-e2e-tests-to-playwright\/\">has used Puppeteer<\/a> in the past, and some packages still exist for it, but for new projects, Playwright-based packages are always the way to go.<\/p>\n\n\n\n<h3 id=\"installing-dependencies\" class=\"wp-block-heading\">Installing dependencies<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In addition to the Playwright Test package, install the <a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-e2e-test-utils-playwright\/\">End-To-End (E2E) Playwright test utils for WordPress library<\/a>. This provides WordPress-specific conveniences that will make everything easier.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To install both, run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npm install @playwright\/test@^1.58.2 @wordpress\/e2e-test-utils-playwright@^1.41.0 --save-dev<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Playwright also requires <a href=\"https:\/\/playwright.dev\/docs\/browsers#introduction\">browser binaries and system dependencies<\/a> separate from the JavaScript package. The download may be several hundred megabytes, and since it installs system-level dependencies, it may ask for your root password.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install them by running:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx playwright install --with-deps<\/code><\/pre>\n\n\n\n<h3 id=\"adding-a-configuration-file\" class=\"wp-block-heading\">Adding a configuration file<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Playwright has many <a href=\"https:\/\/playwright.dev\/docs\/test-configuration\">configuration<\/a> options, but to get started you can use the <a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/trunk\/packages\/scripts\/config\/playwright.config.js\">default configuration<\/a> that <code>wp-scripts<\/code> exposes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create a <code>playwright.config.js<\/code> file and add the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">export { default } from '@wordpress\/scripts\/config\/playwright.config.js';<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is a solid starting point. Later, if you need to customize any defaults, for example, changing the location of your test files, you can extend the base configuration like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">import { defineConfig } from '@playwright\/test';\nimport baseConfig from '@wordpress\/scripts\/config\/playwright.config';\n\nexport default defineConfig( {\n\t...baseConfig,\n\ttestDir: '.\/tests\/e2e\/', \/\/ Instead of the specs directory which is the default location\n} );<\/code><\/pre>\n\n\n\n<div class=\"wp-block-wporg-notice is-alert-notice\"><div class=\"wp-block-wporg-notice__icon\"><\/div><div class=\"wp-block-wporg-notice__content\"><p>It&#8217;s worth keeping in mind that the default configuration assumes the usage of <code>wp-env<\/code> and starts it automatically when the E2E tests are executed. If you&#8217;re using something besides <code>wp-env<\/code>, you&#8217;ll need to customize the default <a href=\"https:\/\/playwright.dev\/docs\/test-webserver\"><code>webServer<\/code><\/a> configuration.<\/p><\/div><\/div>\n\n\n\n<h3 id=\"verifying-the-setup\" class=\"wp-block-heading\">Verifying the setup<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">At this point, everything is in place for writing your first test. To confirm the setup is working, run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx wp-scripts test-playwright<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see a message indicating that no test files were found:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">Error: No tests found<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s expected, since you haven&#8217;t written any tests yet!<\/p>\n\n\n\n<h2 id=\"your-first-test\" class=\"wp-block-heading\">Your first test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To get started, add a <a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/trunk\/tests\/e2e\/specs\/hello.test.js\">sample test<\/a> borrowed from the WordPress Core E2E test suite before writing anything project-specific. This test loads the WordPress Admin Dashboard and verifies that the &#8220;Welcome to WordPress&#8221; heading is displayed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create a new folder called <code>specs<\/code> and inside it, create a file named <code>main.spec.js<\/code> with the following content:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">import { test, expect } from '@wordpress\/e2e-test-utils-playwright';\n\ntest( 'Loads WordPress dashboard', async ( { admin, page } ) =&gt; {\n\tawait admin.visitAdminPage( '\/' );\n\n\tawait expect(\n\t\tpage.getByRole( 'heading', { name: 'Welcome to WordPress', level: 2 } )\n\t).toBeVisible();\n} );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run the test suite again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx wp-scripts test-playwright<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This time, instead of an error, you should see the test pass:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">Running 1 test using 1 worker\n\n  \u2713  1 [chromium] \u203a specs\/main.spec.js:3:5 \u203a Loads WordPress dashboard (831ms)\n\n  1 passed (2.1s)<\/code><\/pre>\n\n\n\n<h3 id=\"test-walkthrough\" class=\"wp-block-heading\">Test walkthrough<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Every test has a name, in this case &#8220;Loads WordPress dashboard&#8221;, and its logic lives inside the callback passed to <code>test()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Test logic can typically be categorized into three parts: setting preconditions, performing actions, and verifying outcomes. This is commonly referred to as the Arrange, Act, and Assert (AAA) pattern. In practice, in E2E tests these steps may be interleaved or have action-assert cycles rather than a single linear sequence.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the &#8220;Loads WordPress dashboard&#8221; test, the action is simply visiting the admin dashboard.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">await admin.visitAdminPage( '\/' );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Other checks could be imagined for the assertion, but if the &#8220;Welcome to WordPress&#8221; heading with level 2 is visible and you&#8217;re on the <code>\/wp-admin\/<\/code> URL, it&#8217;s a fair assumption that the page has fully loaded and you&#8217;re on the dashboard.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">await expect(\n\tpage.getByRole( 'heading', { name: 'Welcome to WordPress', level: 2 } )\n).toBeVisible();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This test also uses a WordPress-specific helper, <code>admin.visitAdminPage()<\/code>, provided by the WordPress E2E Test Utils package that you installed earlier.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These helpers can hide quite a lot of complexity and handle edge cases to make things easier. Here&#8217;s what the <a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/f4bc5afc378e8800a820a7d6637fcf4b26a0d7c8\/packages\/e2e-test-utils-playwright\/src\/admin\/visit-admin-page.ts#L18\">functions<\/a> actually do behind the scenes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">export async function visitAdminPage(\n\tthis: Admin,\n\tadminPath: string,\n\tquery?: string\n) {\n\tawait this.page.goto(\n\t\tjoin( 'wp-admin', adminPath ) + ( query ? `?${ query }` : '' )\n\t);\n\n\n\t\/\/ Handle upgrade required screen\n\tif ( this.pageUtils.isCurrentURL( 'wp-admin\/upgrade.php' ) ) {\n\t\t\/\/ Click update\n\t\tawait this.page.click( '.button.button-large.button-primary' );\n\t\t\/\/ Click continue\n\t\tawait this.page.click( '.button.button-large' );\n\t}\n\n\n\tif ( this.pageUtils.isCurrentURL( 'wp-login.php' ) ) {\n\t\tthrow new Error( 'Not logged in' );\n\t}\n\n\n\tconst error = await this.getPageError();\n\tif ( error ) {\n\t\tthrow new Error( 'Unexpected error in page content: ' + error );\n\t}\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Everything else in the &#8220;Loads WordPress dashboard&#8221; is standard Playwright API: <code>page.getByRole()<\/code> for selecting elements, referred to as <a href=\"https:\/\/playwright.dev\/docs\/locators\">locators<\/a>, and <code>expect().toBeVisible()<\/code> for the <a href=\"https:\/\/playwright.dev\/docs\/test-assertions\">assertion<\/a> itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s one more thing worth calling out. Before the test ran, you were already authenticated as an administrator, which is why the dashboard is visible at all. This happens automatically when using the default WordPress tooling and configuration. This means all tests run from the perspective of a logged-in admin user.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s an important detail to keep in mind, since some scenarios require testing from a visitor&#8217;s perspective instead.<\/p>\n\n\n\n<h3 id=\"running-tests-in-ui-mode\" class=\"wp-block-heading\">Running tests in UI Mode<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You can also run tests in what is referred to as UI Mode, which makes debugging and inspecting test behavior much easier. Run the same command with the <code>--ui<\/code> flag:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">npx wp-scripts test-playwright --ui<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This opens a new browser window with the full suite of tools UI Mode provides.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fd0e03&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fd0e03\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-load-wordpress-dashboard.png\" alt=\"\" class=\"wp-image-6061\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-load-wordpress-dashboard.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-load-wordpress-dashboard-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-load-wordpress-dashboard-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-load-wordpress-dashboard-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-load-wordpress-dashboard-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-load-wordpress-dashboard-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The UI is fairly intuitive, but if you&#8217;d like to learn about every feature in depth, the Playwright <a href=\"https:\/\/playwright.dev\/docs\/test-ui-mode\">UI Mode documentation<\/a> includes a video walkthrough as well.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Even if you&#8217;re a fan of the command line, it&#8217;s worth giving this a try!<\/p>\n\n\n\n<h2 id=\"testing-the-book-reviews-project\" class=\"wp-block-heading\">Testing the Book Reviews project<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To keep the article to a reasonable length, only a few key areas of the <a href=\"https:\/\/developer.wordpress.org\/news\/2024\/05\/building-a-book-review-site-with-block-bindings-part-1-custom-fields-and-block-variations\/\">Building a Book Review Site with Block Bindings<\/a> are covered, but hopefully once you&#8217;re done reading this, you&#8217;ll feel inspired to cover a few more scenarios on your own.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The areas covered are:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A test that checks whether the Paragraph block variations, such as Book Author, are registered, can be inserted, and produce the expected output when inserted.<\/li>\n\n\n\n<li>A test that verifies whether a custom pattern is registered, can be inserted, and contains the correct blocks once inserted.<\/li>\n\n\n\n<li>And to mix things up a bit, a test that verifies whether post meta values are correctly rendered on the front end once set.<\/li>\n<\/ol>\n\n\n\n<h2 id=\"testing-the-book-author-block\" class=\"wp-block-heading\">Testing the Book Author block<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The TT4 Book Reviews child theme registers a block variation called Book Author.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This variation of the Paragraph block makes use of <a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/block-api\/block-bindings\/\">block bindings<\/a> and uses the value of the <code>themeslug_book_author<\/code> post meta key as its content.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For easy reference, here&#8217;s <a href=\"https:\/\/developer.wordpress.org\/news\/2024\/05\/building-a-book-review-site-with-block-bindings-part-1-custom-fields-and-block-variations\/#registering-variations-for-bound-blocks\">how it was registered<\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">registerBlockVariation( 'core\/paragraph', {\n\tname: 'themeslug\/book-author',\n\ttitle: __( 'Book Author', 'themeslug' ),\n\tdescription: __( 'Displays the book author.', 'themeslug' ),\n\tcategory: 'widgets',\n\tkeywords: [ 'book', 'author' ],\n\ticon: pencil,\n\tscope: [ 'inserter' ],\n\tattributes: {\n\t\tmetadata: {\n\t\t\tbindings: {\n\t\t\t\tcontent: {\n\t\t\t\t\tsource: 'core\/post-meta',\n\t\t\t\t\targs: {\n\t\t\t\t\t\tkey: 'themeslug_book_author',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tplaceholder: __( 'Book Author', 'themeslug' ),\n\t},\n\texample: {},\n\tisActive: ( blockAttributes ) =&gt;\n\t\t'themeslug_book_author' ===\n\t\tblockAttributes?.metadata?.bindings?.content?.args?.key,\n} );<\/code><\/pre>\n\n\n\n<h3 id=\"planning-the-test\" class=\"wp-block-heading\">Planning the test<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A good starting point for any UI-driven functional E2E test is to imagine how you would test it manually, then note down each step in detail, and replicate those steps in code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, you can verify that the Book Author block variation is registered and insertable by creating a new post, opening the block inserter, finding Book Author among the available blocks, and clicking on it to insert it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you can follow these steps and the block shows up in the editor, you can fairly confidently say that things are working as expected. However, this alone isn&#8217;t enough. You should also verify that the Book Author block does what it&#8217;s supposed to do, display the value of the post meta it is bound to.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are different ways to go about this, but start by translating the steps above into code.<\/p>\n\n\n\n<h3 id=\"inserting-the-block\" class=\"wp-block-heading\">Inserting the block<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You could create a new test file under <code>\/specs\/<\/code>, but for simplicity, add a new test called &#8220;Inserts Book Author block&#8221; to <code>main.spec.js<\/code> for now. Here&#8217;s how it should look:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">test( 'Inserts Book Author block', async ( { admin, page, editor } ) =&gt; {\n\tawait admin.createNewPost();\n\n\tawait page\n\t\t.getByRole( 'button', {\n\t\t\tname: 'Block Inserter',\n\t\t} )\n\t\t.click();\n\n\tawait page\n\t\t.getByRole( 'region', { name: 'Block Library' } )\n\t\t.getByRole( 'listbox', { name: 'Widgets' } )\n\t\t.getByRole( 'option', { name: 'Book Author', exact: true } )\n\t\t.click();\n\n\t\/\/ Assertions\n} );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you run the tests in UI Mode, you should see Playwright performing each step and the results:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx wp-scripts test-playwright --ui<\/code><\/pre>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fd20bb&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fd20bb\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-no-assertions.png\" alt=\"\" class=\"wp-image-6066\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-no-assertions.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-no-assertions-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-no-assertions-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-no-assertions-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-no-assertions-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-no-assertions-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">First, it creates a new post using a <a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/trunk\/packages\/e2e-test-utils-playwright\/src\/admin\/create-new-post.ts\">helper<\/a> from the WordPress E2E Test Utils package.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">await admin.createNewPost();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then it finds and selects elements on the page and clicks them, just as you would when testing manually.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">await page\n\t.getByRole( 'button', {\n\t\tname: 'Block Inserter',\n\t} )\n\t.click();\n\nawait page\n\t.getByRole( 'region', { name: 'Block Library' } )\n\t.getByRole( 'listbox', { name: 'Widgets' } )\n\t.getByRole( 'option', { name: 'Book Author', exact: true } )\n\t.click();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">There are many ways to select an element, including <a href=\"https:\/\/playwright.dev\/docs\/locators#locate-by-css-or-xpath\">CSS selectors and XPath<\/a>, however, the WordPress best practices guide recommends <a href=\"https:\/\/developer.wordpress.org\/block-editor\/contributors\/code\/testing-overview\/e2e\/#use-accessible-selectors\">using accessible selectors<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your browser&#8217;s developer tools likely have a feature for viewing the accessibility tree. For example, here&#8217;s how to switch to <a href=\"https:\/\/developer.chrome.com\/docs\/devtools\/accessibility\/reference#tab\">Accessibility Tab<\/a> or toggle the <a href=\"https:\/\/developer.chrome.com\/blog\/full-accessibility-tree\/#full_accessibility_tree_in_devtools\">Full accessibility tree<\/a> on Chrome&#8217;s DevTools. This lets you easily see the roles and names of the elements.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fd29a4&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fd29a4\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-accessibility-tree.png\" alt=\"\" class=\"wp-image-6054\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-accessibility-tree.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-accessibility-tree-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-accessibility-tree-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-accessibility-tree-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-accessibility-tree-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-accessibility-tree-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">When possible, stick to the recommended approach, though in practice that isn&#8217;t always feasible.<\/p>\n\n\n\n<h3 id=\"verifying-the-block\" class=\"wp-block-heading\">Verifying the block<\/h3>\n\n\n\n<h4 id=\"verifying-the-attributes\" class=\"wp-block-heading\">Verifying the attributes<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">One way to verify that the inserted block is using the block bindings and the <code>core\/post-meta binding<\/code> source is to check whether it has the right attributes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Manually, you could verify this by switching to the Code Editor and confirming that the block markup matches your expectations.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fd39e6&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fd39e6\" class=\"wp-block-image size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-insert-book-author-block-html-code.png\" alt=\"\" class=\"wp-image-6056\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-insert-book-author-block-html-code.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-insert-book-author-block-html-code-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-insert-book-author-block-html-code-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-insert-book-author-block-html-code-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-insert-book-author-block-html-code-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-insert-book-author-block-html-code-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"markup\" class=\"language-markup\">&lt;!-- wp:paragraph {\"placeholder\":\"Book Author\",\"metadata\":{\"bindings\":{\"content\":{\"source\":\"core\/post-meta\",\"args\":{\"key\":\"themeslug_book_author\"}}}}} --&gt;\n&lt;p&gt;&lt;\/p&gt;\n&lt;!-- \/wp:paragraph --&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If all these attributes are present, it&#8217;s fair to assume the block will work as intended. If something isn&#8217;t working despite the attributes being correct, the issue likely would be in WordPress Core itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With Playwright you can do this by programmatically retrieving the block structure and comparing it against an expected structure. Here&#8217;s the code for it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">await expect.poll( editor.getBlocks ).toMatchObject( [\n\t{\n\t\tname: 'core\/paragraph',\n\t\tattributes: {\n\t\t\tmetadata: {\n\t\t\t\tbindings: {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tsource: 'core\/post-meta',\n\t\t\t\t\t\targs: { key: 'themeslug_book_author' },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tplaceholder: 'Book Author',\n\t\t},\n\t},\n] );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/playwright.dev\/docs\/test-assertions#expectpoll\"><code>poll<\/code><\/a> is added for safety. Block insertion is asynchronous, and simply calling <code>expect( editor.getBlocks )<\/code> could potentially fail before the blocks are available. <code>poll<\/code> retries the callback until it passes or times out, which improves reliability.<\/p>\n\n\n\n<h4 id=\"verifying-the-functionality\" class=\"wp-block-heading\">Verifying the functionality<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">A different approach is to check whether the value stored in the <code>themeslug_book_author<\/code> post meta is actually displayed as the content of the block, rather than inspecting the attributes directly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you want to do this manually, you have to open the Editor settings, select the Post tab, find the Book Reviews panel, open it, and enter a value in the Author field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s how these steps translate to code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">await editor.openDocumentSettingsSidebar();\nawait page.getByRole( 'tab', { name: 'Post' } ).click();\n\nawait page\n\t.getByRole( 'region', { name: 'Editor settings' } )\n\t.getByRole( 'button', {\n\t\tname: 'Book Review',\n\t} )\n\t.click();\n\nawait page\n\t.getByRole( 'textbox', {\n\t\tname: 'Author',\n\t} )\n\t.fill( 'Jane Austen' );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s quite a few steps just to set a single value.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since for this test the goal isn&#8217;t to test the Author control itself, the value of the post meta can be set programmatically using <a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-data\/#dispatch\"><code>wp.data.dispatch<\/code><\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s how that looks together with the code that selects the block and checks the displayed content:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">await page.evaluate( () =&gt;\n\twp.data\n\t\t.dispatch( 'core\/editor' )\n\t\t.editPost( { meta: { themeslug_book_author: 'Jane Austen' } } )\n);\n\nconst bookAuthorBlock = editor.canvas.getByRole( 'document', {\n\tname: 'Block: Paragraph',\n} );\n\nawait expect( bookAuthorBlock ).toHaveText( 'Jane Austen' );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/playwright.dev\/docs\/api\/class-page#page-evaluate\"><code>evaluate<\/code><\/a> call is used to ensure the callback executes inside the page JavaScript context, so it can access the global <code>wp.data<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You don&#8217;t even necessarily have to choose one approach over the other, you can keep both.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As you write more tests and read more on the subject, you&#8217;ll develop your own ideas of what works best for your project: where to draw the line, what feels like too much coverage, and what doesn&#8217;t feel robust enough.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For reference, here&#8217;s the entire &#8220;Inserts the Book Author block&#8221; test code together:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">test( 'Inserts Book Author block', async ( { admin, page, editor } ) =&gt; {\n\tawait admin.createNewPost();\n\n\tawait page\n\t\t.getByRole( 'button', {\n\t\t\tname: 'Block Inserter',\n\t\t} )\n\t\t.click();\n\n\tawait page\n\t\t.getByRole( 'region', { name: 'Block Library' } )\n\t\t.getByRole( 'listbox', { name: 'Widgets' } )\n\t\t.getByRole( 'option', { name: 'Book Author', exact: true } )\n\t\t.click();\n\n\tawait expect.poll( editor.getBlocks ).toMatchObject( [\n\t\t{\n\t\t\tname: 'core\/paragraph',\n\t\t\tattributes: {\n\t\t\t\tmetadata: {\n\t\t\t\t\tbindings: {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\tsource: 'core\/post-meta',\n\t\t\t\t\t\t\targs: { key: 'themeslug_book_author' },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tplaceholder: 'Book Author',\n\t\t\t},\n\t\t},\n\t] );\n\n\tawait page.evaluate( () =&gt;\n\t\twp.data\n\t\t\t.dispatch( 'core\/editor' )\n\t\t\t.editPost( { meta: { themeslug_book_author: 'Jane Austen' } } )\n\t);\n\n\tconst bookAuthorBlock = editor.canvas.getByRole( 'document', {\n\t\tname: 'Block: Paragraph',\n\t} );\n\n\tawait expect( bookAuthorBlock ).toHaveText( 'Jane Austen' );\n} );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you run the test command at this point, you should see in the output that two tests have passed.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx wp-scripts test-playwright<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">Running 2 tests using 1 worker\n\n  \u2713  1 [chromium] \u203a specs\/main.spec.js:3:5 \u203a Loads WordPress dashboard (869ms)\n  \u2713  2 [chromium] \u203a specs\/main.spec.js:11:5 \u203a Inserts Book Author block (2.6s)\n\n  2 passed (4.8s)<\/code><\/pre>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fd4ded&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fd4ded\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-2.png\" alt=\"\" class=\"wp-image-6058\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-2.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-2-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-2-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-2-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-2-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-author-block-2-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<h2 id=\"testing-the-book-review-card-pattern\" class=\"wp-block-heading\">Testing the Book Review Card pattern<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You can test the other block variations similarly, but let&#8217;s see how you could test one of the patterns registered by the theme, the <a href=\"https:\/\/developer.wordpress.org\/news\/2024\/06\/building-a-book-review-site-with-block-bindings-part-2-queries-patterns-and-templates\/#book-review-card\">Book Review Card<\/a>.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fd55ee&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fd55ee\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-book-review-card-pattern.png\" alt=\"\" class=\"wp-image-6065\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-book-review-card-pattern.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-book-review-card-pattern-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-book-review-card-pattern-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-book-review-card-pattern-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-book-review-card-pattern-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-book-review-card-pattern-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This pattern makes use of all the registered block variations and arranges them in a specific layout, incorporating other blocks such as Columns, Group, and others.<\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Book Review Card pattern source<\/summary>\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">&lt;?php\n\/**\n * Title: Book Review Card\n * Slug: themeslug\/book-review-card\n * Categories: themeslug-book-review\n * Viewport Width: 1376\n *\/\n?&gt;\n&lt;!-- wp:columns {\"verticalAlignment\":\"center\",\"align\":\"wide\",\"style\":{\"spacing\":{\"padding\":{\"top\":\"var:preset|spacing|30\",\"bottom\":\"var:preset|spacing|30\",\"left\":\"var:preset|spacing|30\",\"right\":\"var:preset|spacing|30\"},\"blockGap\":{\"top\":\"var:preset|spacing|40\",\"left\":\"var:preset|spacing|30\"}}},\"backgroundColor\":\"accent\"} --&gt;\n&lt;div class=\"wp-block-columns alignwide are-vertically-aligned-center has-accent-background-color has-background\" style=\"padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--30)\"&gt;\n\n\t&lt;!-- wp:column {\"verticalAlignment\":\"center\",\"width\":\"33.33%\"} --&gt;\n\t&lt;div class=\"wp-block-column is-vertically-aligned-center\" style=\"flex-basis:33.33%\"&gt;\n\t\t&lt;!-- wp:post-featured-image {\"aspectRatio\":\"3\/4\",\"style\":{\"border\":{\"radius\":\"0px\"}}} \/--&gt;\n\t&lt;\/div&gt;\n\t&lt;!-- \/wp:column --&gt;\n\n\t&lt;!-- wp:column {\"verticalAlignment\":\"center\",\"width\":\"66.66%\",\"style\":{\"spacing\":{\"blockGap\":\"var:preset|spacing|40\"}}} --&gt;\n\t&lt;div class=\"wp-block-column is-vertically-aligned-center\" style=\"flex-basis:66.66%\"&gt;\n\t\t&lt;!-- wp:group {\"style\":{\"spacing\":{\"blockGap\":\"var:preset|spacing|10\"}},\"layout\":{\"type\":\"flex\",\"orientation\":\"vertical\"}} --&gt;\n\t\t&lt;div class=\"wp-block-group\"&gt;\n\t\t\t&lt;!-- wp:group {\"style\":{\"spacing\":{\"blockGap\":\"0.25em\"}},\"layout\":{\"type\":\"flex\",\"flexWrap\":\"nowrap\"}} --&gt;\n\t\t\t&lt;div class=\"wp-block-group\"&gt;\n\t\t\t\t&lt;!-- wp:paragraph --&gt;\n\t\t\t\t&lt;p&gt;\u2b50\ufe0f&lt;\/p&gt;\n\t\t\t\t&lt;!-- \/wp:paragraph --&gt;\n\n\t\t\t\t&lt;!-- wp:paragraph {\"placeholder\":\"&lt;?php esc_attr_e( 'Book Rating', 'themeslug' ); ?&gt;\",\"metadata\":{\"bindings\":{\"content\":{\"source\":\"core\/post-meta\",\"args\":{\"key\":\"themeslug_book_rating\"}}}}} --&gt;\n\t\t\t\t&lt;p&gt;&lt;\/p&gt;\n\t\t\t\t&lt;!-- \/wp:paragraph --&gt;\n\n\t\t\t\t&lt;!-- wp:paragraph --&gt;\n\t\t\t\t&lt;p&gt;&lt;?php esc_html_e( '\/ 5 Stars', 'themeslug' ); ?&gt;&lt;\/p&gt;\n\t\t\t\t&lt;!-- \/wp:paragraph --&gt;\n\t\t\t&lt;\/div&gt;\n\t\t\t&lt;!-- \/wp:group --&gt;\n\n\t\t\t&lt;!-- wp:group {\"style\":{\"spacing\":{\"blockGap\":\"0.25em\"}},\"layout\":{\"type\":\"flex\",\"flexWrap\":\"nowrap\"}} --&gt;\n\t\t\t&lt;div class=\"wp-block-group\"&gt;\n\t\t\t\t&lt;!-- wp:paragraph --&gt;\n\t\t\t\t&lt;p&gt;&lt;strong&gt;\ud83d\udcc3&lt;\/strong&gt;&lt;\/p&gt;\n\t\t\t\t&lt;!-- \/wp:paragraph --&gt;\n\n\t\t\t\t&lt;!-- wp:paragraph {\"placeholder\":\"&lt;?php esc_attr_e( 'Book Length', 'themeslug' ); ?&gt;\",\"metadata\":{\"bindings\":{\"content\":{\"source\":\"core\/post-meta\",\"args\":{\"key\":\"themeslug_book_length\"}}}}} --&gt;\n\t\t\t\t&lt;p&gt;&lt;\/p&gt;\n\t\t\t\t&lt;!-- \/wp:paragraph --&gt;\n\n\t\t\t\t&lt;!-- wp:paragraph --&gt;\n\t\t\t\t&lt;p&gt;&lt;?php esc_html_e( 'Pages', 'themeslug' ); ?&gt;&lt;\/p&gt;\n\t\t\t\t&lt;!-- \/wp:paragraph --&gt;\n\t\t\t&lt;\/div&gt;\n\t\t\t&lt;!-- \/wp:group --&gt;\n\n\t\t\t&lt;!-- wp:group {\"style\":{\"spacing\":{\"blockGap\":\"0.25em\"}},\"layout\":{\"type\":\"flex\",\"flexWrap\":\"nowrap\"}} --&gt;\n\t\t\t&lt;div class=\"wp-block-group\"&gt;\n\t\t\t\t&lt;!-- wp:paragraph --&gt;\n\t\t\t\t&lt;p&gt;&lt;?php esc_html_e( '\u270d\ufe0f Written by', 'themeslug' ); ?&gt;&lt;\/p&gt;\n\t\t\t\t&lt;!-- \/wp:paragraph --&gt;\n\n\t\t\t\t&lt;!-- wp:paragraph {\"placeholder\":\"&lt;?php esc_attr_e( 'Book Author', 'themeslug' ); ?&gt;\",\"metadata\":{\"bindings\":{\"content\":{\"source\":\"core\/post-meta\",\"args\":{\"key\":\"themeslug_book_author\"}}}}} --&gt;\n\t\t\t\t&lt;p&gt;&lt;\/p&gt;\n\t\t\t\t&lt;!-- \/wp:paragraph --&gt;\n\t\t\t&lt;\/div&gt;\n\t\t\t&lt;!-- \/wp:group --&gt;\n\n\t\t\t&lt;!-- wp:buttons --&gt;\n\t\t\t&lt;div class=\"wp-block-buttons\"&gt;\n\t\t\t\t&lt;!-- wp:button {\"metadata\":{\"bindings\":{\"url\":{\"source\":\"core\/post-meta\",\"args\":{\"key\":\"themeslug_book_goodreads_url\"}}}}} --&gt;\n\t\t\t\t&lt;div class=\"wp-block-button\"&gt;&lt;a class=\"wp-block-button__link wp-element-button\"&gt;&lt;?php esc_html_e( 'View on Goodreads <span aria-hidden=\"true\" class=\"wp-exclude-emoji\">\u2192<\/span>', 'themeslug' ); ?&gt;&lt;\/a&gt;&lt;\/div&gt;\n\t\t\t\t&lt;!-- \/wp:button --&gt;\n\t\t\t&lt;\/div&gt;\n\t\t\t&lt;!-- \/wp:buttons --&gt;\n\t\t&lt;\/div&gt;\n\t\t&lt;!-- \/wp:group --&gt;\n\n\t\t&lt;!-- wp:pullquote {\"textAlign\":\"left\",\"style\":{\"typography\":{\"fontSize\":\"1.2rem\"},\"spacing\":{\"padding\":{\"top\":\"0\",\"bottom\":\"0\"}}},\"className\":\"is-style-plain\"} --&gt;\n\t\t&lt;figure class=\"wp-block-pullquote has-text-align-left is-style-plain\" style=\"padding-top:0;padding-bottom:0;font-size:1.2rem\"&gt;&lt;blockquote&gt;&lt;p&gt;&lt;\/p&gt;&lt;\/blockquote&gt;&lt;\/figure&gt;\n\t\t&lt;!-- \/wp:pullquote --&gt;\n\t&lt;\/div&gt;\n\t&lt;!-- \/wp:column --&gt;\n\n&lt;\/div&gt;\n&lt;!-- \/wp:columns --&gt;<\/code><\/pre>\n<\/details>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s entirely possible to test this pattern the same way the Book Author block was tested.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, given the number of blocks it contains, the test code would be very repetitive and long. Thankfully, there&#8217;s an alternative approach that comes in handy in these situations, which you&#8217;ll see in a moment.<\/p>\n\n\n\n<h3 id=\"inserting-the-pattern\" class=\"wp-block-heading\">Inserting the pattern<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To check if the pattern is registered and insertable, you can apply the same principle as before.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create a new test called &#8220;Insert Book Reviews pattern&#8221; in <code>main.spec.js<\/code> with the following code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">test( 'Inserts Book Review Card pattern', async ( { admin, page, editor } ) =&gt; {\n\tawait admin.createNewPost();\n\n\tawait page\n\t\t.getByRole( 'button', {\n\t\t\tname: 'Block Inserter',\n\t\t} )\n\t\t.click();\n\n\tawait page\n\t\t.getByRole( 'tab', {\n\t\t\tname: 'Patterns',\n\t\t} )\n\t\t.click();\n\n\tawait page.getByRole( 'tab', { name: 'Book Reviews' } ).click();\n\n\tawait page\n\t\t.getByRole( 'listbox', { name: 'Book Reviews' } )\n\t\t.getByRole( 'option', { name: 'Book Review Card' } )\n\t\t.click();\n\n\t\/\/ Assertions\n} );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">As with the block test, this creates a new post to start fresh, then locates the elements on the page and clicks on the pattern to insert it.<\/p>\n\n\n\n<h3 id=\"verifying-the-pattern\" class=\"wp-block-heading\">Verifying the pattern<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Since the pattern consists of multiple blocks, checking them separately would be tedious. To save time, use <a href=\"https:\/\/playwright.dev\/docs\/aria-snapshots\">snapshot testing<\/a> to verify the entire pattern at once, rather than checking each block individually.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Snapshots are representations of a state, element, or some data captured at a given moment, saved and then used for comparison.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">All you have to do is select the outermost element of the pattern, the one that wraps the rest, and use the <code>toMatchAriaSnapshot<\/code> assertion:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">const bookReviewCardPattern = editor.canvas.getByRole( 'document', {\n\tname: 'Block: Columns',\n} );\n\nawait expect( bookReviewCardPattern ).toMatchAriaSnapshot();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you run <code>test-playwright<\/code> at this point, you&#8217;ll get an error because snapshots have to be generated first:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">Running 3 tests using 1 worker\n\n  \u2713  1 [chromium] \u203a specs\/main.spec.js:3:5 \u203a Loads WordPress dashboard (896ms)\n  \u2713  2 [chromium] \u203a specs\/main.spec.js:11:5 \u203a Inserts Book Author block (2.6s)\n  \u2718  3 [chromium] \u203a specs\/main.spec.js:56:5 \u203a Inserts Book Review Card pattern (8.0s)\n\n\n  1) [chromium] \u203a specs\/main.spec.js:56:5 \u203a Inserts Book Review Card pattern \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n    Error: A snapshot doesn't exist at specs\/__snapshots__\/Inserts-Book-Review-Card-pattern-1-chromium.aria.yml, writing actual.\n\n      80 |      } );\n      81 |\n    &gt; 82 |      await expect( bookReviewCardPattern ).toMatchAriaSnapshot();\n         |      ^\n      83 | } );\n      84 |\n\n  1 failed\n    [chromium] \u203a specs\/main.spec.js:56:5 \u203a Inserts Book Review Card pattern \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  2 passed (13.0s)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you are using the UI Mode, you should see under the Errors panel:<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fd68de&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fd68de\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-missing-snapshot.png\" alt=\"\" class=\"wp-image-6060\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-missing-snapshot.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-missing-snapshot-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-missing-snapshot-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-missing-snapshot-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-missing-snapshot-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-missing-snapshot-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Once you&#8217;ve made sure that the pattern is correct, you can save the snapshot using the usual command with the <code>--update-snapshots<\/code> flag:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx wp-scripts test-playwright --update-snapshots<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">After running it, a new file should appear at <code>\/specs\/__snapshots__\/Inserts-Book-Review-Card-pattern-1-chromium.yml<\/code> containing the accessibility tree representation in YAML format:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"yaml\" class=\"language-yaml\">- 'document \"Block: Columns\"':\n  - 'document \"Block: Column (1 of 2)\"':\n    - 'document \"Block: Featured Image\"':\n      - button \"Add a featured image\"\n  - 'document \"Block: Column (2 of 2)\"':\n    - 'document \"Block: Stack\"':\n      - 'document \"Block: Row\"':\n        - 'document \"Block: Paragraph\"'\n        - document \"Empty themeslug_book_rating; start writing to edit its value\"\n        - 'document \"Block: Paragraph\"'\n      - 'document \"Block: Row\"':\n        - 'document \"Block: Paragraph\"':\n          - strong: \ud83d\udcc3\n        - document \"Empty themeslug_book_length; start writing to edit its value\"\n        - 'document \"Block: Paragraph\"'\n      - 'document \"Block: Row\"':\n        - 'document \"Block: Paragraph\"'\n        - document \"Empty themeslug_book_author; start writing to edit its value\"\n      - 'document \"Block: Buttons\"':\n        - 'document \"Block: Book Goodreads Button\"':\n          - textbox \"Button text\"\n    - 'document \"Block: Pullquote\"':\n      - blockquote:\n        - textbox \"Pullquote text\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now, run the test command without the flag to compare the pattern&#8217;s output with the snapshot:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx wp-scripts test-playwright<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Only use <code>--update-snapshots<\/code> when you intentionally want to update the snapshots!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For reference, here&#8217;s the complete &#8220;Inserts Book Reviews pattern&#8221; test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">test( 'Inserts Book Review Card pattern', async ( { admin, page, editor } ) =&gt; {\n\tawait admin.createNewPost();\n\n\tawait page\n\t\t.getByRole( 'button', {\n\t\t\tname: 'Block Inserter',\n\t\t} )\n\t\t.click();\n\n\tawait page\n\t\t.getByRole( 'tab', {\n\t\t\tname: 'Patterns',\n\t\t} )\n\t\t.click();\n\n\tawait page.getByRole( 'tab', { name: 'Book Reviews' } ).click();\n\n\tawait page\n\t\t.getByRole( 'listbox', { name: 'Book Reviews' } )\n\t\t.getByRole( 'option', { name: 'Book Review Card' } )\n\t\t.click();\n\n\tconst bookReviewCardPattern = editor.canvas.getByRole( 'document', {\n\t\tname: 'Block: Columns',\n\t} );\n\n\tawait expect( bookReviewCardPattern ).toMatchAriaSnapshot();\n} );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">At this point you have three tests, and all should pass.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx wp-scripts test-playwright<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">Running 3 tests using 1 worker\n\n  \u2713  1 [chromium] \u203a specs\/main.spec.js:3:5 \u203a Loads WordPress dashboard (848ms)\n  \u2713  2 [chromium] \u203a specs\/main.spec.js:11:5 \u203a Inserts Book Author block (2.8s)\n  \u2713  3 [chromium] \u203a specs\/main.spec.js:56:5 \u203a Inserts Book Review Card pattern (3.1s)\n\n  3 passed (7.7s)<\/code><\/pre>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fd767e&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fd767e\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern.png\" alt=\"\" class=\"wp-image-6059\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-inserts-book-review-card-pattern-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<h2 id=\"testing-the-front-end-and-meta-values\" class=\"wp-block-heading\">Testing the front-end and meta values<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To test that the blocks are displaying the post meta values on the front end, you need to both insert the blocks or the pattern and set the meta values.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Doing this step by step through the Editor would result in a lot of code. If how the post was created and the set up isn&#8217;t important to the test itself, it&#8217;s worth considering a shortcut.<\/p>\n\n\n\n<h3 id=\"creating-the-post-via-rest-api\" class=\"wp-block-heading\">Creating the post via REST API<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A good shortcut is using the REST API to create a post, adding the necessary metadata for the pattern (author, rating, length, and Goodreads URL). This method bypasses the editor UI entirely.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To create a new post, there&#8217;s a dedicated helper called <a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/trunk\/packages\/e2e-test-utils-playwright\/src\/request-utils\/posts.ts\"><code>requestUtils.createPost()<\/code><\/a>, but you could also interact with the <a href=\"https:\/\/developer.wordpress.org\/rest-api\/reference\/posts\/\">posts<\/a> or any endpoint directly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once again, create a new test in the <code>main.specs.js<\/code> and add the following setup part:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">test( 'Displays book review meta on the frontend', async ( {\n\tpage,\n\trequestUtils,\n} ) =&gt; {\n\tconst newPost = await requestUtils.createPost( {\n\t\tstatus: 'publish',\n\t\ttitle: 'Emma',\n\t\tcontent: '&lt;!-- wp:pattern {\"slug\":\"themeslug\/book-review-card\"} \/--&gt;',\n\t\tmeta: {\n\t\t\tthemeslug_book_author: 'Jane Austen',\n\t\t\tthemeslug_book_rating: '5',\n\t\t\tthemeslug_book_length: '477',\n\t\t\tthemeslug_book_goodreads_url:\n\t\t\t\t'https:\/\/www.goodreads.com\/book\/show\/6969.Emma',\n\t\t},\n\t} );\n\n\tawait page.goto( `?p=${ newPost.id }` );\n\n\t\/\/ Assertions\n} );<\/code><\/pre>\n\n\n\n<h3 id=\"verifying-the-front-end-output\" class=\"wp-block-heading\">Verifying the front-end output<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You may have already guessed what will happen in the assertion part. It&#8217;s a matter of selecting the elements and verifying their state.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s the code for the assertion part:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">await expect( page.getByText( '5 \/ 5 Stars' ) ).toBeVisible();\nawait expect( page.getByText( '477 Pages' ) ).toBeVisible();\nawait expect( page.getByText( 'Written by Jane Austen' ) ).toBeVisible();\nawait expect(\n\tpage.getByRole( 'link', { name: 'View on Goodreads' } )\n).toHaveAttribute(\n\t'href',\n\t'https:\/\/www.goodreads.com\/book\/show\/6969.Emma'\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is one of those cases where it isn&#8217;t possible to use an accessibility locator such as <a href=\"https:\/\/playwright.dev\/docs\/locators#locate-by-role\"><code>getByRole<\/code><\/a>, as, for example, the HTML for the &#8220;5 \/ 5 Stars&#8221; part looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"markup\" class=\"language-markup\">&lt;div class=\"wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-10 wp-block-group-is-layout-flex\"&gt;\n\t&lt;p&gt;\u2b50\ufe0f&lt;\/p&gt;\n\t&lt;p&gt;5&lt;\/p&gt;\n\t&lt;p&gt;\/ 5 Stars&lt;\/p&gt;\n&lt;\/div&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The containing <code>div<\/code> is a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Accessibility\/ARIA\/Reference\/Roles\">non-semantic element<\/a>, and the text itself is spread across multiple <code>p<\/code> tags.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With all that said, here&#8217;s the complete &#8220;Displays book review meta on the frontend&#8221; test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">test( 'Displays book review meta on the frontend', async ( {\n\tpage,\n\trequestUtils,\n} ) =&gt; {\n\tconst newPost = await requestUtils.createPost( {\n\t\tstatus: 'publish',\n\t\ttitle: 'Emma',\n\t\tcontent: '&lt;!-- wp:pattern {\"slug\":\"themeslug\/book-review-card\"} \/--&gt;',\n\t\tmeta: {\n\t\t\tthemeslug_book_author: 'Jane Austen',\n\t\t\tthemeslug_book_rating: '5',\n\t\t\tthemeslug_book_length: '477',\n\t\t\tthemeslug_book_goodreads_url:\n\t\t\t\t'https:\/\/www.goodreads.com\/book\/show\/6969.Emma',\n\t\t},\n\t} );\n\n\tawait page.goto( `?p=${ newPost.id }` );\n\n\tawait expect( page.getByText( '5 \/ 5 Stars' ) ).toBeVisible();\n\tawait expect( page.getByText( '477 Pages' ) ).toBeVisible();\n\tawait expect( page.getByText( 'Written by Jane Austen' ) ).toBeVisible();\n\tawait expect(\n\t\tpage.getByRole( 'link', { name: 'View on Goodreads' } )\n\t).toHaveAttribute(\n\t\t'href',\n\t\t'https:\/\/www.goodreads.com\/book\/show\/6969.Emma'\n\t);\n} );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you run the tests one final time, you should see all of them passing.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">npx wp-scripts test-playwright<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">Running 4 tests using 1 worker\n\n  \u2713  1 [chromium] \u203a specs\/main.spec.js:3:5 \u203a Loads WordPress dashboard (848ms)\n  \u2713  2 [chromium] \u203a specs\/main.spec.js:11:5 \u203a Inserts Book Author block (2.8s)\n  \u2713  3 [chromium] \u203a specs\/main.spec.js:56:5 \u203a Inserts Book Review Card pattern (3.1s)\n  \u2713  4 [chromium] \u203a specs\/main.spec.js:85:5 \u203a Displays book review meta on the frontend (413ms)\n\n  4 passed (8.4s)<\/code><\/pre>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69fe6f7fd91e9&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69fe6f7fd91e9\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2880\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-displays-book-review-meta-on-frontend.png\" alt=\"\" class=\"wp-image-6055\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-displays-book-review-meta-on-frontend.png 2880w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-displays-book-review-meta-on-frontend-300x188.png 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-displays-book-review-meta-on-frontend-1024x640.png 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-displays-book-review-meta-on-frontend-768x480.png 768w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-displays-book-review-meta-on-frontend-1536x960.png 1536w, https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright-displays-book-review-meta-on-frontend-2048x1280.png 2048w\" sizes=\"auto, (max-width: 2880px) 100vw, 2880px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<h2 id=\"further-reading\" class=\"wp-block-heading\">Further reading<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Hopefully this article has shown that, once the setup is out of the way, writing basic E2E tests is more approachable than it might seem, and that a handful of core concepts can take you surprisingly far.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That said, there&#8217;s plenty more to explore, and this article only scratches the surface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/playwright.dev\/docs\/intro\">Playwright documentation<\/a> is comprehensive yet accessible at all levels. A good starting point is the <a href=\"https:\/\/playwright.dev\/docs\/writing-tests\">Writing Tests<\/a> page, which covers a few more actions and assertions beyond what&#8217;s been discussed here.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">From there, it&#8217;s worth learning how to organize tests across multiple files and groups, and how you can simplify things with <a href=\"https:\/\/playwright.dev\/docs\/writing-tests#using-test-hooks\">test hooks<\/a>, which are executed before and after each test. The Playwright <a href=\"https:\/\/playwright.dev\/docs\/best-practices\">best practices<\/a> guide and WordPress <a href=\"https:\/\/developer.wordpress.org\/block-editor\/contributors\/code\/testing-overview\/e2e\/\">Core&#8217;s own recommendations<\/a> are both worth reading as your test suite grows.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For real-world examples, the <a href=\"https:\/\/github.com\/WordPress\/gutenberg\/tree\/trunk\/test\/e2e\/specs\/editor\/blocks\">E2E tests Gutenberg provides for Core blocks<\/a> is a great source of inspiration. The <a href=\"https:\/\/github.com\/WordPress\/gutenberg\/tree\/trunk\/packages\/e2e-test-utils-playwright\/src\">E2E Playwright test utils for WordPress source<\/a> is also worth browsing to discover the full range of available helpers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once your tests grow, <a href=\"https:\/\/playwright.dev\/docs\/test-fixtures\">fixtures<\/a> offer a clean way to extract reusable logic out of individual tests. There&#8217;s also dedicated guidance on <a href=\"https:\/\/playwright.dev\/docs\/ci-intro\">setting up CI<\/a> if you want to run tests on every commit or PR.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, if you&#8217;re curious about where things are heading, keep an eye on <a href=\"https:\/\/playwright.dev\/docs\/test-agents\">Test Agents<\/a>, or if you&#8217;d prefer a lower-code approach, Playwright can also <a href=\"https:\/\/playwright.dev\/docs\/codegen\">generate tests<\/a> for you.<\/p>\n\n\n\n<p class=\"has-text-align-right wp-block-paragraph\"><em>Props to <a href=\"https:\/\/profiles.wordpress.org\/greenshady\/\">@greenshady<\/a> and <a href=\"https:\/\/profiles.wordpress.org\/bph\/\">@bph<\/a> for their reviews.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to set up Playwright for WordPress E2E testing and write tests using real-world examples so you have a solid foundation to adapt for your own project.<\/p>\n","protected":false},"author":13475511,"featured_media":6080,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_crdt_document":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"edge","default_image_id":0,"font":"","enabled":false},"version":2},"_wpas_customize_per_network":false},"categories":[4,219,176],"tags":[213,228],"class_list":["post-6048","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-advanced","category-tests","category-tools","tag-automated-testing","tag-e2e"],"revision_note":"","jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/developer.wordpress.org\/news\/files\/2026\/04\/getting-started-writing-wordpress-e2e-tests-with-playwright.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/posts\/6048","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/users\/13475511"}],"replies":[{"embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/comments?post=6048"}],"version-history":[{"count":34,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/posts\/6048\/revisions"}],"predecessor-version":[{"id":6107,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/posts\/6048\/revisions\/6107"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/media\/6080"}],"wp:attachment":[{"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/media?parent=6048"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/categories?post=6048"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/tags?post=6048"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}