Tag: Performance

  • Client-side media processing in WordPress

    Client-side media processing in WordPress

    At WordCamp US 2024 I gave a presentation about client-side media processing, which is all about bringing WordPress’ media uploading and editing capabilities from the server to the browser. Watch the recording or check out the slides. This blog post is a written adaption of this talk.

    This was a long overdue step after my announcement tweet in December 2023 went viral. After that, I talked about it on the WP Tavern Jukebox podcast and in an interview with WPShout. You might also remember an old post I wrote about client-side video optimization. This is a 10x evolution of that.

    A lot has changed since then. Not only have I built new features, but I also completely refactored the Media Experiments plugin that was all part of. For WordCamp US, I chose to put more focus on the technical aspects of media handling in WordPress and the benefits of a new browser-based approach.

    If you haven’t seen it before, here’s a quick glimpse of what browser-based media processing allows us to do:

    Contributors wanted

    You can find everything covered in this article in the Media Experiments GitHub repository. It contains a working WordPress plugin that you can easily install on your site or even just test with one click using WordPress Playground.

    The goal is to eventually bring parts of this into WordPress core itself, which is something I am currently working on.

    To make this project a reality, I need your help! Please try the plugin and provide feedback for anything that catches your eye. Or even batter, check out the source code and help tackle some of the open issues.

    Let WordPress help you

    The WordPress project has a clear philosophy, with pillars such as “Design for the majority” and “Decisions, not options”. There is one section in that philosophy which particularly stands out to me:

    The average WordPress user simply wants to be able to write without problems or interruption. These are the users that we design the software for

    WordPress philosophy

    In my experience, when it comes to uploading images and other types of media, WordPress sometimes falls short of that. There are still many problems and interruptions. This is even more problematic nowadays as the web is more media-rich than ever before.

    Minimizing frustration

    Who hasn’t experienced issues when uploading media to WordPress?

    Perhaps an image was too large and uploading takes forever, maybe even resulting in a timeout. And if it worked, the image was so large that it degraded your site’s performance.

    Maybe you were trying to upload a photo from your phone, but WordPress doesn’t support it and tells you to please use another file. Or even worse, you upload a video, it succeeds, but then you realize none of the browsers actually support the video format.

    In short: uploading media is a frustrating experience.

    To work around these issues, you start googling how to manually convert and compress images before uploading them. Maybe even reducing the dimensions to save some bandwidth.

    If you use videos, maybe you upload them to YouTube because you don’t want to bother with video formats too.

    Maybe you switch your hosting provider because your server still takes too long to generate all those thumbnails or because it doesn’t support the newest image formats.

    This is tedious and time consuming. WordPress should be taking work off your shoulders, not making your lives harder. So I set out to make this better. I wanted to turn this around and let WordPress help you.

    At State of the Word 2023, WordPress co-founder Matt Mullenweg said the following about WordPress’ mission to Democratize Publishing:

    We take things that used to require advanced technical knowledge and make it accessible to everyone.

    Matt Mullenweg

    And I think media uploads is a perfect opportunity for us to apply this. And the solution for that lies in the browser.

    WebAssembly

    Your server might not be capable to generate all those thumbnails or to convert a specific image format to something usable. But thanks to your own device’s computing power and technologies such as WebAssembly, we can fix this for you.

    With WebAssembly you can compile code written in a language like Rust or C++ for running it in browsers with near-native performance. In the browser you can load WebAssembly modules via JavaScript and seamlessly send data back and forth.

    At the core of my what I am showing you here is one such WebAssembly solution called wasm-vips. It is a port of the powerful libvips image processing library. That means any image operation that you can do with vips, you can now do in the browser.

    Vips vs. ImageMagick

    Vips is similar to ImageMagick, which WordPress typically uses, but has some serious advantages. For example, when WordPress loads vips in the browser it can always use the latest version. Whereas on the server we have to use whatever version that is available.

    Sometimes, those are really old versions that have certain bugs or don’t support more modern image formats like AVIF. For hosts it can be challenging to upgrade, as they don’t want to break any sites. And even if ImageMagick already supports a format like AVIF, it could be very slow. Vips on the other hand is more performant, has more features, and even for older formats like JPEG it uses newer encoders with better results.

    Client-side vs. server-side media processing

    Sequence diagram for server-side media processing
    Sequence diagram for server-side media processing

    Traditionally, when you drop an image into the editor or the media library, it is sent to WordPress straight away. There, ImageMagick creates thumbnails for every registered image size one by one. That means a lot of waiting until WordPress can proceed. Here is where timeouts usually happen.

    Eventually, once all the thumbnails are generated, WordPress creates a new attachment and sends it back to the editor. There, the editor can swap out the file you originally dropped with the final one returned by the server.

    Compare this to the client-side approach using the vips image library:

    Sequence diagram for client-side media processing
    Sequence diagram for client-side media processing

    Once you drop an image into the editor, a web worker creates thumbnails of it. A web worker runs in a separate thread from the editor, so none of the image processing affects your workflow. Plus, the cropping happens in parallel, which makes it a super fast process. Every thumbnail is then uploaded separately to the server. The server only has to do little work, just storing the file and returning the attachment data.

    You immediately see all the updates in the editor after every step, so you have a much faster feedback loop. With this approach, the chances for errors, timeouts or memory issues are basically zero.

    New use cases

    The Media Experiments plugin contains tons of media-related features and enhancements. In this section I want to highlight some of them to better demonstrate what this new technology unlocks in WordPress.

    Image compression

    As shown in the demo at the beginning of the article, a key feature is the ability to compress or convert images directly in the browser. This works for existing images as well as new ones. All the thumbnails are generated in the browser as well.

    Bonus: Did you see it? The plugin automatically adds AI-generated image captions and alt text for the image. This simply wouldn’t be possible on a typical WordPress server, but thanks to WebAssembly we can easily use AI models for such a task in the browser.

    You can also compress all existing images in a blog post at once. The images can come from all sorts of sources too, for example from the image block, gallery block, or the post’s featured image.

    In theory you could even do this for the whole media library. The tricky part of course is that your browser needs to be running. So that idea isn’t fully fleshed out yet.

    Smart thumbnails

    By default, when WordPress creates those 150×150 thumbnails it does a hard crop in the center of the image. For some photos that will lead to poor results where for example it cuts off the most relevant part of the picture, like a person’s head.

    Vips supports saliency-aware image cropping out of the box, which looks for things like color saturation to determine a better crop.

    Comparison of default vs. smart image cropping
    Comparison of default vs. smart image cropping

    At first you might think it is just a minor detail, but it’s actually really impactful. It just works, and it works for everybody! You will never have to worry about accidentally cropping off someone’s face again.

    HEIC Images

    If you use an iPhone you might have seen HEIC/HEIF images before, as it uses that format by default. It is a format with strong compression, but only Safari fully supports it.

    Thanks to WebAssembly, WordPress can automatically convert such images to something usable. In this demo you will first notice a broken preview, as the (Chrome) browser doesn’t support the file format. But then it swiftly converts it to a JPEG, fixing the preview, and then uploads it to the server.

    Bonus: this also works for JPEG XL, which is another format that only Safari supports.

    Upload from your phone

    In the above video I used an HEIC image which I previously took on my iPhone and then transferred to my computer. And from my computer I then uploaded it to WordPress. But what if you cut out the middleman?

    In the editor, you can generate a QR code that you scan with your camera, or a URL that you can share with a colleague. Here, I am opening this URL in another browser, but let’s pretend it’s my phone. On your phone you then choose the image you want to upload. After that, it magically appears in the editor on your computer.

    Hat tip to John Blackbourn for the idea!

    Video compression

    Media compression and conversion also works great for videos. When I record screencasts for this post, they will be in the MOV format, which doesn’t work in all browsers.

    Thanks to ffmpeg.wasm, a WebAssembly port of the powerful FFmpeg framework, WordPress can convert them to a more universal format like MP4 or WebM. The same works for audio files as well.

    This solution also generates poster images out of the box, which is important for user experience and performance.

    Bonus: just like for image captions, AI can automatically subtitles for any video.

    Animated GIFs

    Sometimes you’re not dealing with videos though, but with GIFs. Who doesn’t like GIFs?

    Well, the thing is, GIFs are actually really bad for user experience and performance. Not only are they usually very bad quality, they can also be huge in file size. Most likely you should not be using animated GIFs.

    Every time you use the GIF format, a kitten dies
    As Paul Bakaus once said: Gifs must die

    The good news is that animated GIFs are nothing but videos with a few key characteristics:

    • They play automatically.
    • They loop continuously
    • They’re silent.

    By converting large GIFs to videos, you can save big on users’ bandwidth. And that’s exactly what WordPress can and should do for you.

    In the following demo, I am dragging and dropping a GIF file from my computer to the editor. Since it is an image file, WordPress first creates an image block and starts the upload process.

    Then, it detects that it is an animated GIF, and uses FFmpeg to convert it to an MP4 video. This happens in the blink of an eye. As it’s now a video, WordPress replaces the image block with a special GIF variation of the video block that’s looping and autoplaying. And of course the video is multiple times smaller than the original image file. As a user, you can’t tell the difference. It just works.

    Media recording

    Compressing videos and converting GIFs is cool, but one of my personal favorites is the ability to record videos or take still pictures directly in the editor, and then upload them straight to WordPress.

    So if you’re writing some sort of tutorial and want to accompany it with a video, or if you are building the next TikTok competitor, you could do that with WordPress.

    Bonus: You probably don’t see it well in the demo, but thanks to AI you can even blur your background for a little more privacy. Super cool!

    Challenges

    Client-side media processing adds a pretty powerful new dimension to WordPress, but it isn’t always as easy as it looks!

    Cross-origin isolation

    On the implementation side, cross-origin isolation is a tricky topic.

    So, WebAssembly libraries like vips or ffmpeg use multiple threads to speed up the processing, which means they require shared memory. Shared memory means you need SharedArrayBuffer.

    For security reasons, enabling SharedArrayBuffer requires a special configuration called cross-origin isolation. That puts a web page into a special state that enforces some restrictions when loading resources from other origins.

    In the WordPress editor, I tried to implement this as smoothly as possible. Normally, you will not even realize that cross-origin isolation is in effect. However, some things in the editor might not work as expected anymore.

    The most common issue I encountered is with embed previews in the editor.

    So in Chrome, all your embed previews in the editor continue to work, while in Firefox or Safari they don’t because they do not support iframe credentialless when isolation is in effect.

    I hope that Firefox and Safari remedy this in the future. Chrome is also working on an alternative proposal called Document-Isolation-Policy which would help resolve this as well. But that might still be years in the future.

    Open source licenses (GPL compatibility)

    Another unfortunate thing is that open source licenses aren’t always compatible with each other. This is the case with the HEIC conversion for those iPhone photos.

    Being able to convert those iPhone photos directly in the browser before sending them to the server just makes so much sense. Unfortunately, it’s a very proprietary file format. The only open source implementation (libheif) is licensed under the LGPL 3.0, which is only compatible with GPL v3. However, WordPress’ license is GPLv2 or later.

    That means we can’t actually use it 🙁

    The good news is that we found another way, and it’s even already part of the next WordPress release!

    However, this happens on the server again instead of the browser.

    This is possible because on the server the conversion happens in ImageMagick (when compiled with libheif), and not in core itself, so there’s no license concern for WordPress.

    The downside of this approach is that it will only work for very few WordPress sites, as it again depends on your PHP and ImageMagick versions. So while this is a nice step into the right direction, only with the client-side approach can we truly support this for everyone.

    The next steps

    All of these challenges simply mean there is still some work to do before it can be put into the hands of millions of WordPress users.

    While this project started as a separate plugin, I am currently in the process of contributing these features step by step to Gutenberg, where we can further test them behind an experimental flag.

    We start with the fundamental rewrite of the upload logic, adding support for image compression and thumbnail conversion. After that, we can look into format conversion, making it easier to use more modern image formats and choosing the format that is most suitable for any given image. From there, we can expand this to videos and audio files.

    Finally and ideally, we expand beyond the post editor and make this available to the rest of WordPress, like the media gallery or anywhere else where one would upload files.

    I am also really excited about the possibility of making this available to everyone building a block editor outside of WordPress, like Tumblr for example.

    Democratizing publishing

    With client-side media processing we make a giant leap forward when it comes to democratizing publishing.

    As mentioned at the beginning, the average WordPress user simply wants to be able to write without problems or interruption. By eliminating all these problems related to media, users will be able to create media-rich content much easier and faster.

    Thanks to client-side media processing, we can greatly improve the user experience around uploads. You benefit from faster uploads, fewer headaches, smaller images, and less overloaded servers. Also, you no longer need to worry about server support or switch hosting providers. Smaller images and more modern image formats help make your site load faster too, which is a nice little bonus.

    Convinced? Check out the GitHub repository and the proposed roadmap for the Gutenberg integration.

  • Automated testing using WordPress Playground and Blueprints

    Automated testing using WordPress Playground and Blueprints

    Learn how to leverage WordPress Playground and Blueprints for automated end-to-end browser and performance testing.

    Late last year I published a detailed tutorial for getting started with end‑to‑end performance testing in WordPress. It was accompanied by a template GitHub repository and a dedicated GitHub Action for effortlessly running performance tests with zero setup.

    Introductory blog post to browser-based performance testing in WordPress projects

    While that GitHub Action works extremely well, the zero-setup approach has two drawbacks:

    1. It is not possible to configure the test environment, for example by adding demo content or changing plugin configuration
    2. It is not possible to test more complex scenarios, like any user interactions (e.g. for INP)

    For (2) the best alternative right now is to go with the manual approach. For (1), I have now found a solution in WordPress Playground. Playground is a platform that lets you run WordPress instantly on any device. It can be seen as a replacement for the Docker-based @wordpress/env tool.

    A playground set with a slide and a stairway. There are trees in the background.
    A real playground but just as fun! Photo by Hudson Roseboom on Unsplash

    Using Blueprints for automated testing

    One particular strength of WordPress Playground is the idea of Blueprints. Blueprints are JSON files for setting up your WordPress Playground instance. In other words, they are a declarative way for configuring WordPress—like a recipe. A blueprint for installing a specific theme and plugin could look like this:

    {
    	"steps": [
    		{
    			"step": "installPlugin",
    			"pluginZipFile": {
    				"resource": "wordpress.org/plugins",
    				"slug": "performance-lab"
    			}
    		},
    		{
    			"step": "installTheme",
    			"themeZipFile": {
    				"resource": "wordpress.org/themes",
    				"slug": "twentytwentyone"
    			}
    		}
    	]
    }Code language: JSON / JSON with Comments (json)

    Performance testing 2.0

    The newly released version 2 of the performance testing GitHub Action now uses Blueprints under the hood to set up the testing environment and do things like importing demo content and installing mandatory plugins and themes. In addition to that, you can now use Blueprints for your own dedicated setup!

    This way you can install additional plugins, change the site language, define some options, or even run arbitrary WP-CLI commands. There are tons of possible steps and also a Blueprints Gallery with real-world code examples.

    To get started, add a new swissspidy/wp-performance-action@v2 step to your workflow (e.g. .github/workflows/build-test.yml):

    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Run performance tests
        uses: swissspidy/wp-performance-action@v2
        with:
          urls: |
            /
            /sample-page/
          plugins: |
            ./my-awesome-plugin
          blueprint: ./my-custom-blueprint.json
          iterations: 5
          repetitions: 1Code language: YAML (yaml)

    Then, add the blueprint (my-custom-blueprint.json):

    {
      "$schema": "https://playground.wordpress.net/blueprint-schema.json",
      "plugins": [
        "performant-translations",
        "akismet"
      ],
      "steps": [
        {
          "step": "defineWpConfigConsts",
          "consts": {
            "WP_DEBUG": true
          }
        },
        {
          "step": "activatePlugin",
          "pluginName": "My Awesome Plugin",
          "pluginPath": "/wordpress/wp-content/plugins/my-awesome-plugin"
        }
      ]
    }Code language: JSON / JSON with Comments (json)

    And that’s it!

    The GitHub Action will now use your custom blueprint to install and activate your own custom plugin and performance-lab and akismet plugins from the plugin directory.

    Alongside this new feature I also included several bug fixes for things I originally planned to add but never really finished. For instance, it is now actually possible to run the performance tests twice and then compare the difference between the results.

    This way, when you submit a pull request you can run tests first for the main branch and then for your PR branch to quickly see at a glance how the PR affects performance. Here is an example:

    jobs:
      comparison:
        runs-on: ubuntu-latest
    
        steps:
    
        # Check out the target branch and build the plugin
        # ...
    
        - name: Run performance tests (before)
          id: before
          uses: ./
          with:
            urls: |
              /
              /sample-page/
            plugins: |
              ./tests/dummy-plugin
            blueprint: ./my-custom-blueprint.json
            print-results: false
            upload-artifacts: false
    
        # Check out the current branch and build the plugin
        # ...
    
        - name: Run performance tests (after)
          uses: ./
          with:
            urls: |
              /
              /sample-page/
            plugins: |
              ./tests/dummy-plugin
            blueprint: ./my-custom-blueprint.json
            previous-results: ${{ steps.before.outputs.results }}
            print-results: true
            upload-artifacts: falseCode language: PHP (php)

    The result will look a bit like this:

    Screenshot of the performance tests results printed in a GitHub Actions workflow summary, comparing metrics such as LCP or memory usage before and after a change.
    Example workflow summary when comparing two sets of performance testing results.

    Playground is the future

    Being able to use Playground for automated testing is really exciting. It simplifies a lot of the setup and speeds up the bootstrapping, even though the sites themselves aren’t as fast (yet) as when using a Docker-based setup. However, there is a lot of momentum behind WordPress Playground and it is getting better every day. Applications like this one further help push its boundaries.

    I had similar success so far when testing Playground with our WordPress performance comparison script and I think it could work well for the Plugin Check GitHub Action.

    WordPress Playground clearly is the future.

  • Performance for Everyone: Democratizing Performance in WordPress

    Performance for Everyone: Democratizing Performance in WordPress

    At WordCamp Asia 2024 I had the opportunity to talk about the work I’ve been doing as part of the WordPress core performance team. The recordings are not available yet, but you can re-watch the livestream and check out the slide deck in the meantime. This blog post summarizes my presentation and amends it with further information and links to relevant resources.

    In the style of the WordPress mission statement to democratize publishing, I like to call this effort Democratizing Performance. Or in other words: performance for everyone. In my eyes, everyone should be able to have a fast website, regardless of their skill level or technical knowledge. To achieve this, we take things that used to require advanced technical knowledge and make it accessible to everyone.

    Why does performance matter, you ask? Performance is essential for a great user experience on the web, and poor performance can negatively hurt your business. A slow website can lead to visitors leaving and not coming back. If your checkout process is slow, users might not end up buying products in your online store. With performance being a factor for search engines, it can also affect your site’s ranking.

    There are many different aspects to performance within WordPress. Here, I am usually referring to the performance of the frontend of your website, as measured using metrics such as Core Web Vitals. Core Web Vitals are a set of performance metrics that measure loading performance, interactivity, and layout stability. WordPress has been working to improve its performance, and Core Web Vitals are a great way to measure that progress.

    The WordPress core performance team was founded a few years ago. It is dedicated to monitoring, enhancing, and promoting performance in WordPress core and its surrounding ecosystem. Having a dedicated team for this kind of effort shows that the community understands the rising complexity of today’s websites. This way. WordPress is well-equipped to cater for these use cases in a performant way.

    The team’s activities can be roughly grouped into three categories:

    1. Improving core itself, providing new APIs, fixing slow code and measuring improvements
    2. Working with the ecosystem to help people adopt best practices and make their projects faster
    3. Providing tools and documentation to facilitate doing so.

    Tackling performance in an open source project like WordPress involves more than improving the core software itself. This is different from closed platforms, where you don’t have to worry about elevating an entire ecosystem with thousands of plugins and themes. Democratizing performance is not something that WordPress or the core performance team can do alone. It takes all of us, including site assemblers and extenders, to work together to raise the bar for everyone.

    Recent core performance improvements

    Still, there are some things we can do in core itself. Despite the performance team’s young age it already has a proven track record of performance enhancements. To name a few:

    • Automated performance testing using Playwright, feeding metrics into a public dashboard
    • Improvements to image lazy loading. This includes adding the fetchpriority attribute to the image which is likely to be the LCP element.
    • Improve emoji support detection

    In my talk, I highlighted the emoji change because it’s such a great example of improving performance for everyone. Ever since WordPress added emoji support 10 years ago, it loads a little bit of JavaScript on every page to see whether your browser supports the latest and greatest emoji (there are new ones almost every year). It turns out that doing so on every page load is quite wasteful — who would have thought!

    Fortunately, there are better ways to do this. Since last year, this emoji detection happens only once per visit, caching results for subsequent visits in the same session. Additionally, the detection now happens in a web worker, keeping the main thread free for more important work. This change was the main contributing factor to a ~30% client-side performance improvement in WordPress 6.3, compared to WordPress 6.2. To benefit from this, all you had to do was update your website — a great example of performance for everyone.

    Measuring success

    Such impressive numbers are testament to the focus on data-driven decision making in WordPress. It’s important to base our work on actual numbers rather than a gut feeling. Getting these numbers is a two-fold process.

    1. Reproducible, automated testing in a controlled environment where you measure the desired metrics for each code change to verify improvements. Also known as lab testing or lab data.
    2. Measuring how WordPress performs for millions of actual sites in the world. This is so-called field data.

    This kind of field data is available for free through datasets such as HTTP Archive and the Chrome UX Report. The latter is the official dataset of the Web Vitals program. All user-centric Core Web Vitals metrics are represented there. These datasets provide a more accurate picture of WordPress performance in the wild, so this is the data we want to positively influence. The report on WordPress performance impact on Core Web Vitals in 2023 covers some recent highlights in this regard.

    WordPress performance in 2024

    The improvements in the last couple of years were already pretty impressive. Still, the performance team continues working hard on even further improvements. The current year is still relatively young, but there are already some exciting new changes in the works:

    • Performant translations
      Previously, localized WordPress sites could be up to 30% slower than a regular site. Thanks to a new translation library in WordPress 6.5 this difference is now almost completely eliminated.
    • Interactivity performance
      Interaction to Next Paint (INP) is now officially a Core Web Vital. Interactivity performance is therefore top of mind for our team. We’re currently identifying common key problems and opportunities to improve interactivity bottlenecks in WordPress. We are also spreading the word about the new interactivity API in WordPress 6.5.
    • Modern image formats
      Now that all major browsers understand the format, WordPress 6.5 adds AVIF support.
    • Client-side image processing
      You might have heard about my media experiments work already. Essentially, we want to bring most of the image processing in WordPress from the server to the client. This enables exciting things like image optimization or AVIF conversion directly in the browser, regardless of what server you are on.
    • Speculative page prerendering
      There is now a new feature plugin that adds support for speculative prerendering for near-instant page loads. This is a new browser API for prerendering the next pages in the background, for example when hovering over a link. This is a great example of how WordPress can embrace the web platform and provide native support for new APIs to developers.

    Check out the core performance team’s roadmap for this year to learn more about these endeavors.

    Ecosystem & tooling

    As I said in the beginning of this article, democratizing performance involves more than making WordPress itself faster. It’s the responsibility of site builders and developers too. That’s why we try to help the ecosystem track performance and adopt new features we build. Be it through WordCamp talks like this one or more advanced documentation. My recent blog posts on WordPress performance testing and the Plugin Check plugin are great examples of that effort. Both tools are also available as GitHub Actions, making it really easy to get started.

    Performance Lab is also a great tool for us to improve performance well beyond WordPress core. If you haven’t heard about it yet, Performance Lab is a collection of performance-related feature plugins. It allows you to test new features before they eventually end up in a new WordPress release. This way, the team can validate ideas at a larger scale and iterate on them more quickly. Once they are stable, we can propose merging them into core. Or, sometimes we find that a particular enhancement doesn’t work that well, so we discard it.

    Screenshot of the Performance Lab plugin page. The plugin allows testing new performance features ahead of time.

    Dreaming bigger

    As you can see, there is a lot going on already in the performance space. But what if we go further than that? What if we dream bigger? I want to spark your imagination a little bit by thinking about some other exciting things that we could build. For example, there could be a performance section in Query Monitor. Or imagine your browser’s developer tools not only telling how to improve your slow JavaScript, but also how to do it in the context of WordPress.

    Platforms like WP Hive and PluginTests.com are also promising. They basically test all plugins in the entire plugin directory to see if they work as expected. They also measure memory usage and performance impact on both the frontend and backend. Thanks to a browser extension, this information is surfaced directly inside the plugin directory. Ironically, we actually already have the infrastructure available on WordPress.org to do this ourselves. Tide was built exactly for this purpose. Unfortunately the project has stalled since its original inception 6 years ago, but what if it came back?

    Screenshot of the checks performed by tools like WP Hive. They highlight a plugin's performance impact on a site.
    Tools like WP Hive highlight a plugin’s impact on a site

    Finally, how does artificial intelligence fit into this? Of course in 2024 you kind of have to mention AI one way or the other. Imagine an AI assistant in your WordPress admin that tells you how to optimize your site’s configuration. Or the assistant in your code editor having deep knowledge of the latest WordPress APIs and telling you how to load your plugin’s JavaScript in a more efficient way. You don’t have to be an expert to benefit from such helpers, making it possible for everyone to have a fast website.

    Conclusion

    Performance is a critical factor for any website. WordPress is committed to making good performance accessible to everyone, and the results are showing. Still, there is a lot of work to be done and a lot of opportunities to improve performance at scale. The core performance team can’t do this alone. It takes all of us — site owners, site builders, developers, agencies — to make the web a better — and faster — place.

    Together, we can democratize performance.

  • Getting started with end‑to‑end performance testing in WordPress

    Getting started with end‑to‑end performance testing in WordPress

    Learn how to set up Playwright-based end-to-end performance testing for your own WordPress project.

    Introduction

    End-to-end (E2E) tests are a type of software testing that verifies the behavior of a software application from, well, end to end. They simulate an actual user interacting with the application to verify that it behaves as expected. E2E tests are important because they can help to identify and fix bugs that may not be caught by unit tests or other types of testing. Additionally, they can help to ensure that the application is performing as expected under real-world conditions, with real user flows that are typical for the application. This means starting an actual web server, installing WordPress, and interacting with the website through a browser. For example, the majority of the block editor is covered extensively by end-to-end tests.

    Performance testing

    Browser-based performance testing is a subset of this kind of testing. Such tests measure the speed and reactivity of the website in order to find performance regressions. This includes common metrics such as Web Vitals or page load time, but also dedicated metrics that are more tailored to your project. For instance, Gutenberg tracks things like typing speed and the time it takes to open the block inserter.

    Both WordPress core and Gutenberg use Playwright for end-to-end and performance tests. It supports multiple browsers and operating systems, and provides great developer experience thanks to a resilient API and powerful tooling. If you know Puppeteer, Playwright is a forked and enhanced version of it. The WordPress project is actually still undergoing a migration from Puppeteer to Playwright.

    This article shows how to set up Playwright-based end-to-end tests for your own project, with a focus on performance testing. To familiarize yourself with how Playwright works, explore their Getting Started guide. Would you like to jump straight to the code? Check out this example project on GitHub! It provides a ready-to-use boilerplate for Playwright-based performance tests that you can add to your existing project.

    Using a one-stop solution for performance testing

    Before diving right into the details of writing performance tests and fiddling with reporting, there is also a shortcut to get your feet wet.

    Most of what I cover in this article is also available in a single, ready-to-use GitHub Action. You can easily add it to almost any project with little to no configuration. Here’s an example of the minimum setup needed:

    name: Performance Tests
    on:
      push:
        branches: [ main ]
      pull_request:
        branches: [ main ]
    jobs:
      performance-tests:
        timeout-minutes: 60
        runs-on: ubuntu-latest
    
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Run performance tests
            uses: swissspidy/wp-performance-action@v2
            with:
              plugins: |
                ./path-to-my-awesome-plugin
              urls: |
                /
                /sample-page/Code language: YAML (yaml)

    Using this action will spin up a new WordPress installation, install your desired plugins and themes, run Playwright tests against the provided pages on that site, and print easy to understand results to the workflow summary.

    Performance test results summary on GitHub, showing various collected metrics for a given commit, including things like the number of database queries and total load time.

    This one-stop solution allows you to quickly get started with performance testing in a WordPress context and helps to familiarize yourself with the topic. It might even cover all of your needs already, which would be even better! Another big advantage of such a GitHub Action is that you will automatically benefit from new changes made to it. And If you ever need more, continue reading below to learn how you can do it yourself.

    Update (September 2024): check out my follow-up post about v2 of this GitHub Action using WordPress Playground.

    Setting up Playwright tests for a WordPress plugin/theme

    Reminder: if you want a head start on setting up Playwright tests, check out the example project on GitHub. It provides a ready-to-use boilerplate with everything that’s covered below.

    This article assumes that you are developing WordPress blocks, plugins, themes, or even a whole WordPress site, and are familiar with the common @wordpress/scripts and @wordpress/env toolstack. The env package allows you to quickly spin up a local WordPress site using Docker, whereas the scripts package offers a range of programs to lint, format, build, and test your code. This conveniently includes Playwright tests! In addition to that, the @wordpress/e2e-test-utils-playwright package offers a set of useful helpers for writing Playwright tests for a WordPress project.

    All you need to get started is installing these packages using npm:

    npm install --save-dev @wordpress/scripts @wordpress/env @wordpress/e2e-test-utils-playwrightCode language: Bash (bash)

    To start your server right away, you can run the following command:

    npx --package=@wordpress/env wp-env startCode language: Bash (bash)

    Check out the @wordpress/env documentation on how to further configure or customize your local environment, for example to automatically install and activate your plugin/theme in this new WordPress site.

    Note: if you already have @wordpress/env installed or use another local development environment, skip this step and use your existing setup.

    To run the Playwright tests with @wordpress/scripts, use the command npx wp-scripts test-playwright. If you have a custom Playwright configuration file in your project root directory, it will be automatically picked up. Otherwise, provide the path like so:

    npx wp-scripts test-playwright --config tests/performance/playwright.config.ts

    In a custom config file like this one you can override some of the details from the default configuration provided by @wordpress/scripts. Refer to the documentation for a list of possible options. Most commonly, you would need this to customize the default test timeout, the directory where test artifacts are stored, or how often each test should be repeated.

    Writing your first end-to-end browser test

    The aforementioned utilities package hides most of the complexity of writing end-to-end tests and provides functionality for the most common interactions with WordPress. Your first test could be as simple as this:

    import { test, expect } from '@wordpress/e2e-test-utils-playwright';
    
    test.describe( 'Dashboard', () => {
      test.beforeAll( async ( { requestUtils } ) => {
        await requestUtils.activateTheme( 'twentytwentyone' );
      } );
    
      test( 'Should load properly', async ( { admin, page } ) => {
        await admin.visitAdminPage( '/' );
        await expect(
            page.getByRole('heading', { name: 'Welcome to WordPress', level: 2 })
        ).toBeVisible();
      } );
    } );Code language: JavaScript (javascript)

    When running npx wp-scripts test-playwright, this test visits /wp-admin/ and waits for the “Welcome to WordPress” meta box heading to be visible. And before all tests run (in this case there is only one), it ensures the Twenty-Twenty One theme is activated. That’s it! No need to wait for the page to load or anything, Playwright handles everything for you. And thanks to the locator API, the test is self-explanatory as well.

    Locators are very similar to what’s offered by Testing Library in case you have used that one before. But if you are new to this kind of testing API, it’s worth strolling through the documentation a bit more.
    That said, the easiest way to write a new Playwright test is by recording one using the test generator. That’s right, Playwright comes with the ability to generate tests for you as you perform actions in the browser and will automatically pick the right locators for you. This can be done using a VS Code extension or by simply running npx playwright codegen. Very handy!

    Setting up performance tests

    The jump from a simple end-to-end test to a performance test is not so big. The key difference is performing some additional tasks after visiting a page, further processing the collected metrics, and then repeating all that multiple times to get more accurate results. This is where things get interesting!

    First, you need to determine what you want to measure. When running performance tests with an actual browser, it’s of course interesting to simply measure how fast your pages load. From there you can expand to measuring more client-side metrics such as specific user interactions or Web Vitals like Largest Contentful Paint. But you can also focus more on server-side metrics, such as how long it takes for your plugin to perform a specific task during page load. It all depends on your specific project requirements.

    Writing your first performance test

    Putting all of these pieces together, we can turn a simple end-to-end test into a performance test. Let’s track the time to first byte (TTFB) as a start.

    import { test } from '@wordpress/e2e-test-utils-playwright';
    
    test.describe( 'Front End', () => {
      test.use( {
        storageState: {}, // User will be logged out.
      } );
    
      test.beforeAll( async ( { requestUtils } ) => {
        await requestUtils.activateTheme( 'twentytwentyone' );
      } );
    
      const iterations = 20;
      for ( let i = 1; i <= iterations; i++ ) {
        test( `Measure TTFB (${ i } of ${ iterations })`, async ( {
          page,
          metrics,
        } ) => {
          await page.goto( '/' );
    
          const ttfb = await metrics.getTimeToFirstByte();
    
          console.log( `TTFB: ${ttfb}`);
        } );
      }
    } );Code language: JavaScript (javascript)

    What’s standing out is that Playwright’s storageState is reset for the tests, ensuring that tests are performed as a logged-out user. This is because being logged in could skew results. Of course for some other scenarios this is not necessarily desired. It all depends on what you are testing.

    Second, a for loop around the test() block allows running the test multiple times. It’s worth noting that the loop should be outside the test and not inside. This way, Playwright can ensure proper test isolation, so that a new page is created with every iteration. It will be completely isolated from the other pages, like in incognito mode.

    The metrics object used in the test is a so-called test fixture provided by, you guessed it, the e2e utils package we’ve previously installed. How convenient! From now on, most of the time we will be using this fixture.

    Measuring all the things

    Server-Timing

    The Server-Timing HTTP response header is a way for the server to send information about server-side metrics to the client. This is useful to get answers for things like:

    • Was there a cache hit
    • How long did it take to load translations
    • How long did it take to load X from the database
    • How many database queries were performed
    • How much memory was used

    The last ones are admittedly a bit of a stretch. Server-Timing is meant for duration values, not counts. But it’s the most convenient way to send such metrics because they can be processed in JavaScript even after a page navigation. For Playwright-based performance testing this is perfect.

    In WordPress, the easiest way to add Server-Timing headers is by using the Performance Lab plugin. By default it supports exposing the following metrics:

    • wp-before-template: Time it takes for WordPress to initialize, i.e. from the start of WordPress’s execution until it begins sending the template output to the client.
    • wp-template: Time it takes to compute and render the template, which begins right after the above metric has been measured.
    • wp-total: Time it takes for WordPress to respond entirely, i.e. this is simply the sum of wp-before-template + wp-template.

    Additional metrics can be added via the perflab_server_timing_register_metric() function. For example, this adds the number of database queries to the header:

    add_action(
    	'plugins_loaded',
    	static function() {
    		if ( ! function_exists( 'perflab_server_timing_register_metric' ) ) {
    			return;
    		}
    	
    		perflab_server_timing_register_metric(
    			'db-queries',
    			array(
    				'measure_callback' => static function( Perflab_Server_Timing_Metric $metric ) {
    					add_action(
    						'perflab_server_timing_send_header',
    						static function() use ( $metric ) {
    							global $wpdb;
    							$metric->set_value( $wpdb->num_queries );
    						}
    					);
    				},
    				'access_cap'       => 'exist',
    			)
    		);
    	}
    );Code language: PHP (php)

    Once you have everything set up, WordPress will send an HTTP header like this with every response:

    Server-Timing:
    wp-before-template;dur=110.73, wp-template;dur=143, wp-total;dur=253.73, wp-db-queries;dur=27

    Again, the number of database queries is not a duration, but if it works, it works!

    In Playwright tests, this is how you can retrieve the values:

    test.describe( 'Homepage', () => {
      test( 'Server-Timing', async ( { page, metrics } ) => {
        await page.goto( '/' );
        const serverTiming = await metrics.getServerTiming();
    
        // {
        //   'wp-before-template': 110.73,
        //   'wp-template': 143,
        //   'wp-total': 253.73,
        //   'wp-db-queries': 27,
        // }
      } );
    } );Code language: JavaScript (javascript)

    Load time metrics

    Besides getServerTiming() and getTimeToFirstByte(), the metrics fixture provides a handful of other helpers to measure certain load time metrics to make your life easier:

    • getLargestContentfulPaint: Returns the Largest Contentful Paint (LCP) value using the dedicated API.
    • getCumulativeLayoutShift: Returns the Cumulative Layout Shift (CLS) value using the dedicated API.
    • getLoadingDurations: Returns the loading durations using the Navigation Timing API. All the durations exclude the server response time. The returned object contains serverResponse, firstPaint, domContentLoaded, loaded, firstContentfulPaint, timeSinceResponseEnd.

    Some of these methods are mostly there because it’s trivial to retrieve the metrics, but not all of these might make sense for your use case.

    Tracing

    The metrics fixture provides an easy way to access Chromium’s trace event profiling tool. It allows you to get more insights into what Chrome is doing “under the hood” when interacting with a page. To give you an example of what this means, in Gutenberg this is used to measure things like typing speed.

    // Start tracing.
    await metrics.startTracing();
    
    // Type the testing sequence into the empty paragraph.
    await paragraph.type( 'x'.repeat( iterations ) );
    
    // Stop tracing.
    await metrics.stopTracing();
    
    // Get the durations.
    const [ keyDownEvents, keyPressEvents, keyUpEvents ] =
        metrics.getTypingEventDurations();Code language: JavaScript (javascript)

    In addition to getTypingEventDurations() there are also getSelectionEventDurations(), getClickEventDurations(), and getHoverEventDurations().

    Lighthouse reports

    The @wordpress/e2e-test-utils-playwright package has basic support for running Lighthouse reports for a given page. Support is basic because it only performs a handful of audits and does not yet allow any configuration. Also, due to the way Lighthouse works, it’s much slower than taking similar measurements by hand using simple JavaScript snippets. That’s because it does a lot of things under the hood like applying CPU and network throttling to emulate mobile connection speeds. Still, it can be useful to compare numbers and provide feedback to the folks working on this package to further improve it. A basic example:

    test.describe( 'Homepage', () => {
      test( 'Lighthouse', async ( { page, lighthouse } ) => {
        await page.goto( '/' );
        const report = await lighthouse.getReport();
    
        // {
        //   'LCP': 123,
        //   'TBT': 456,
        //   'TTI': 789,
        //   'CLS': 0.01,
        //   'INP': 321,
        // }
      } );
    } );Code language: JavaScript (javascript)

    This one line is enough to run a Lighthouse report, which involves opening a new isolated browser instance on a dedicated port for running tests in.

    Interactivity metrics

    The metrics fixture already provides ways to get some Web Vitals values such as First Contentful Paint (FCP), Largest Contentful Paint (LCP), and Cumulative Layout Shift (CLS). They cover loading performance and layout stability. Interaction to Next Paint (INP), a pending Core Web Vital metric that will replace First Input Delay (FID) in March 2024, is notably absent from that list. That’s because it’s not so trivial to retrieve, as it requires user interaction.

    INP is a metric that assesses a page’s overall responsiveness to user interactions. It does so by observing the latency of all click, tap, and keyboard interactions that occur throughout the lifespan of a user’s visit to a page. The final INP value is the longest interaction observed, ignoring outliers. So how can you measure that reliably in an automated test? Enter the web-vitals library.

    This library is the easiest way to measure all the Web Vitals metrics in a way that accurately matches how they’re measured by Chrome and reported to tools like PageSpeed Insights.

    As of very recently (i.e. it’s not even released yet!), the metrics fixture has preliminary support for web-vitals.js and allows measuring web vitals using one simple method:

    await page.goto( '/' );
    
    console.log( await metrics.getWebVitals() );Code language: JavaScript (javascript)

    Under the hood, this will refresh the page while simultaneously loading the library and collecting numbers. This will measure CLS, FCP, FID, INP, LCP, and TTFB metrics for the given page and return all the ones that exist.

    Again, metrics like INP require user interaction. To accommodate for that, separate the loading and collection part like so:

    await metrics.initWebVitals( /* reload */ false );
    await page.goto( '/some-other-page/' ); // web-vitals.js will be loaded now.
    // Interact with page here...
    console.log( await metrics.getWebVitals() );Code language: JavaScript (javascript)

    You may find that retrieving web vitals using this single method is easier than calling separate getLargestContentfulPaint() and getCumulativeLayoutShift() methods, though the reported numbers will be identical. In the future these methods may be consolidated into one.

    Making sense of performance metrics

    With the foundation for running performance tests set and all these functions to retrieve various metrics available, the next step is to actually collect all this data in a uniform way. What’s needed is a way to store results and ideally compare them with earlier results. This is so you are actually able to make sense of all these metrics and to identify performance regressions.

    For this purpose, I’ve built a custom test reporter that takes metrics collected in tests and combines them all in one single file. Then, a second command-line script formats the data and optionally does a comparison as well. The reporter and the CLI script are both available on the demo GitHub repository, together with all the other example code from this article. Here’s an example output by this script:

    Note: there is work underway to further refine these scripts and make them easier available through a dedicated npm package. Imagine a single package like @wordpress/performance-tests that provides the whole suite of tools ready to go! This is currently being discussed and I will update this post accordingly when something like this happens.

    In a GitHub Action, you would run this combination of scripts in this order:

    1. Start web server (optional, as Playwright will start it for you otherwise)
    2. Run tests
    3. Optionally run tests for the previous commit or target branch
      1. The raw results are also available as a build artifact and a step output. This way you don’t have to unnecessarily run tests twice but can reuse previous results
    4. Run the CLI script to format results and optionally compare them with the ones from step 3

    Here’s an example of what that could look like.

    Tracking performance metrics over time

    Eventually you will get to a point where you want to see the bigger picture and track your project’s performance over time. For example using a dedicated dashboard such as the one WordPress core currently uses.

    Screenshot of the codevitals.run dashboard for WordPress core, tracking all the different metrics

    When doing so, you will inevitably need to deal with data storage and visualization, and things like variance between individual runs. These are not yet currently solved problems, both for WordPress projects but also in general. In a future blog post I plan to go a bit more in depth on this side of things and show you how to set this up for your project, allowing you to make more sense of performance metrics over time.

    Conclusion

    With the foundation from this blog post you should be able to start writing and running your first performance tests for your WordPress project. However, there is still a lot that can be covered and optimized, as performance testing can be quite a complex matter.

    For now, I do recommend checking out the WordPress performance tests GitHub action as well as the demo repository I set up with the complete testing setup. Oh, and bookmark the Playwright documentation just in case.

    This topic is obviously top of mind for me and also the WordPress core performance team, so expect some more updates in this regard in the future. I do recommend following on X/Twitter and the make/performance blog for updates on all things performance testing.

    And of course please let me know your thoughts in the comments so the team and I can further refine the techniques shared in this post. Thanks for reading!