<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[55459] trunk: Build/Test Tools: Add a performance measurement workflow.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/55459">55459</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/55459","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>joemcgill</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-03-03 20:37:10 +0000 (Fri, 03 Mar 2023)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Build/Test Tools: Add a performance measurement workflow.

This adds a new GitHub Action workflow that measures a set of performance metrics on every commit, so we can track changes in the performance of WordPress over time and more easily identify changes that are responsible for significant performance improvements or regressions during development cycles.

The workflow measures the homepage of a classic theme (Twenty Twenty-One) and a block theme (Twenty Twenty-Three) set up with demo content from the Theme Test Data project. Using the e2e testing framework, it makes 20 requests and records the median value of the following Server Timing metrics, generated by an mu-plugin installed as part of this workflow:

- Total server response time
- Server time before templates are loaded
- Server time during template rendering

In addition to measuring the performance metrics of the current commit, it also records performance metrics of a consistent version of WordPress (6.1.1) to be used as a baseline measurement in order to remove variance caused by the GitHub workers themselves from our reporting.

The measurements are collected and displayed at https://www.codevitals.run/project/wordpress.

Props adamsilverstein, mukesh27, flixos90, youknowriad, oandregal, desrosj, costdev, swissspidy.
Fixes <a href="https://core.trac.wordpress.org/ticket/57687">#57687</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkgitignore">trunk/.gitignore</a></li>
<li><a href="#trunkpackagejson">trunk/package.json</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkgithubworkflowsperformanceyml">trunk/.github/workflows/performance.yml</a></li>
<li>trunk/tests/performance/</li>
<li>trunk/tests/performance/config/</li>
<li><a href="#trunktestsperformanceconfigbootstrapjs">trunk/tests/performance/config/bootstrap.js</a></li>
<li><a href="#trunktestsperformancejestconfigjs">trunk/tests/performance/jest.config.js</a></li>
<li><a href="#trunktestsperformancelogresultsjs">trunk/tests/performance/log-results.js</a></li>
<li><a href="#trunktestsperformanceresultsjs">trunk/tests/performance/results.js</a></li>
<li><a href="#trunktestsperformanceruntestsjs">trunk/tests/performance/run-tests.js</a></li>
<li>trunk/tests/performance/specs/</li>
<li><a href="#trunktestsperformancespecshomeblockthemetestjs">trunk/tests/performance/specs/home-block-theme.test.js</a></li>
<li><a href="#trunktestsperformancespecshomeclassicthemetestjs">trunk/tests/performance/specs/home-classic-theme.test.js</a></li>
<li><a href="#trunktestsperformanceutilsjs">trunk/tests/performance/utils.js</a></li>
<li>trunk/tests/performance/wp-content/</li>
<li>trunk/tests/performance/wp-content/mu-plugins/</li>
<li><a href="#trunktestsperformancewpcontentmupluginsservertimingphp">trunk/tests/performance/wp-content/mu-plugins/server-timing.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkgithubworkflowsperformanceyml"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/.github/workflows/performance.yml</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/.github/workflows/performance.yml                           (rev 0)
+++ trunk/.github/workflows/performance.yml     2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,231 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+name: Performance Tests
+
+on:
+  push:
+    branches:
+      - trunk
+      - '6.[2-9]'
+      - '[7-9].[0-9]'
+    tags:
+      - '[0-9]+.[0-9]'
+      - '[0-9]+.[0-9].[0-9]+'
+      - '![45].[0-9].[0-9]+'
+      - '!6.[01].[0-9]+'
+  pull_request:
+    branches:
+      - trunk
+      - '6.[2-9]'
+      - '[7-9].[0-9]'
+
+# Cancels all previous workflow runs for pull requests that have not completed.
+concurrency:
+  # The concurrency group contains the workflow name and the branch name for pull requests
+  # or the commit hash for any other events.
+  group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+  cancel-in-progress: true
+
+env:
+  # This workflow takes two sets of measurements — one for the current commit,
+  # and another against a consistent version that is used as a baseline measurement.
+  # This is done to isolate variance in measurements caused by the GitHub runners
+  # from differences caused by code changes between commits. The BASE_TAG value here
+  # represents the version being used for baseline measurements. It should only be
+  # changed if we want to normalize results against a different baseline.
+  BASE_TAG: '6.1.1'
+  LOCAL_DIR: build
+
+jobs:
+  # Runs the performance test suite.
+  #
+  # Performs the following steps:
+  # - Configure environment variables.
+  # - Checkout repository.
+  # - Set up Node.js.
+  # - Log debug information.
+  # - Install npm dependencies.
+  # - Build WordPress.
+  # - Start Docker environment.
+  # - Log running Docker containers.
+  # - Docker debug information.
+  # - Install WordPress.
+  # - Install WordPress Importer plugin.
+  # - Import mock data.
+  # - Update permalink structure.
+  # - Install MU plugin.
+  # - Run performance tests (current commit).
+  # - Print performance tests results.
+  # - Set the environment to the baseline version.
+  # - Run baseline performance tests.
+  # - Print base line performance tests results.
+  # - Set the base sha.
+  # - Set commit details.
+  # - Publish performance results.
+  # - Ensure version-controlled files are not modified or deleted.
+  # - Dispatch workflow run.
+  performance:
+    name: Run performance tests
+    runs-on: ubuntu-latest
+    if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }}
+
+    steps:
+      - name: Configure environment variables
+        run: |
+          echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV
+          echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV
+
+      - name: Checkout repository
+        uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+
+      - name: Set up Node.js
+        uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
+        with:
+          node-version-file: '.nvmrc'
+          cache: npm
+
+      - name: Log debug information
+        run: |
+          npm --version
+          node --version
+          curl --version
+          git --version
+          svn --version
+          locale -a
+
+      - name: Install npm dependencies
+        run: npm ci
+
+      - name: Build WordPress
+        run: npm run build
+
+      - name: Start Docker environment
+        run: |
+          npm run env:start
+
+      - name: Log running Docker containers
+        run: docker ps -a
+
+      - name: Docker debug information
+        run: |
+          docker -v
+          docker-compose -v
+          docker-compose run --rm mysql mysql --version
+          docker-compose run --rm php php --version
+          docker-compose run --rm php php -m
+          docker-compose run --rm php php -i
+          docker-compose run --rm php locale -a
+
+      - name: Install WordPress
+        run: npm run env:install
+
+      - name: Install WordPress Importer plugin
+        run: npm run env:cli -- plugin install wordpress-importer --activate --path=/var/www/${{ env.LOCAL_DIR }}
+
+      - name: Import mock data
+        run: |
+          curl -O https://raw.githubusercontent.com/WPTT/theme-test-data/b9752e0533a5acbb876951a8cbb5bcc69a56474c/themeunittestdata.wordpress.xml
+          npm run env:cli -- import themeunittestdata.wordpress.xml --authors=create --path=/var/www/${{ env.LOCAL_DIR }}
+          rm themeunittestdata.wordpress.xml
+
+      - name: Update permalink structure
+        run: |
+          npm run env:cli -- rewrite structure '/%year%/%monthnum%/%postname%/' --path=/var/www/${{ env.LOCAL_DIR }}
+
+      - name: Install MU plugin
+        run: |
+          mkdir ./${{ env.LOCAL_DIR }}/wp-content/mu-plugins
+          cp ./tests/performance/wp-content/mu-plugins/server-timing.php ./${{ env.LOCAL_DIR }}/wp-content/mu-plugins/server-timing.php
+
+      - name: Run performance tests (current commit)
+        run: npm run test:performance
+
+      - name: Print performance tests results
+        run: "node ./tests/performance/results.js"
+
+      - name: Set the environment to the baseline version
+        run: |
+          npm run env:cli -- core update --version=${{ env.BASE_TAG }} --force --path=/var/www/${{ env.LOCAL_DIR }}
+          npm run env:cli -- core version --path=/var/www/${{ env.LOCAL_DIR }}
+
+      - name: Run baseline performance tests
+        run: npm run test:performance -- --prefix=base
+
+      - name: Print base line performance tests results
+        run: "node ./tests/performance/results.js --prefix=base"
+
+      - name: Set the base sha
+        # Only needed when publishing results.
+        if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }}
+        uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975 # v6.4.0
+        id: base-sha
+        with:
+            github-token: ${{ secrets.GITHUB_TOKEN }}
+            script: |
+                const baseRef = await github.rest.git.getRef({ owner: context.repo.owner, repo: context.repo.repo, ref: 'tags/${{ env.BASE_TAG }}' });
+                return baseRef.data.object.sha;
+
+      - name: Set commit details
+        # Only needed when publishing results.
+        if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }}
+        uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975 # v6.4.0
+        id: commit-timestamp
+        with:
+            github-token: ${{ secrets.GITHUB_TOKEN }}
+            script: |
+                const commit_details = await github.rest.git.getCommit({ owner: context.repo.owner, repo: context.repo.repo, commit_sha: context.sha });
+                return parseInt((new Date( commit_details.data.author.date ).getTime() / 1000).toFixed(0))
+
+      - name: Publish performance results
+        # Only publish results on pushes to trunk.
+        if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }}
+        env:
+            BASE_SHA: ${{ steps.base-sha.outputs.result }}
+            COMMITTED_AT: ${{ steps.commit-timestamp.outputs.result }}
+            CODEVITALS_PROJECT_TOKEN: ${{ secrets.CODEVITALS_PROJECT_TOKEN }}
+            HOST_NAME: "codevitals.run"
+        run: node ./tests/performance/log-results.js $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA $BASE_SHA $COMMITTED_AT $HOST_NAME
+
+      - name: Ensure version-controlled files are not modified or deleted
+        run: git diff --exit-code
+
+  slack-notifications:
+    name: Slack Notifications
+    uses: WordPress/wordpress-develop/.github/workflows/slack-notifications.yml@trunk
+    needs: [ performance ]
+    if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event_name != 'pull_request' && always() }}
+    with:
+      calling_status: ${{ needs.performance.result == 'success' && 'success' || needs.performance.result == 'cancelled' && 'cancelled' || 'failure' }}
+    secrets:
+      SLACK_GHA_SUCCESS_WEBHOOK: ${{ secrets.SLACK_GHA_SUCCESS_WEBHOOK }}
+      SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }}
+      SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }}
+      SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }}
+
+  failed-workflow:
+    name: Failed workflow tasks
+    runs-on: ubuntu-latest
+    needs: [ performance, slack-notifications ]
+    if: |
+      always() &&
+      github.repository == 'WordPress/wordpress-develop' &&
+      github.event_name != 'pull_request' &&
+      github.run_attempt < 2 &&
+      (
+        needs.performance.result == 'cancelled' || needs.performance.result == 'failure'
+      )
+
+    steps:
+      - name: Dispatch workflow run
+        uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975 # v6.4.0
+        with:
+          retries: 2
+          retry-exempt-status-codes: 418
+          script: |
+            github.rest.actions.createWorkflowDispatch({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              workflow_id: 'failed-workflow.yml',
+              ref: 'trunk',
+              inputs: {
+                run_id: '${{ github.run_id }}'
+              }
+            });
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/.github/workflows/performance.yml
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunkgitignore"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/.gitignore</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/.gitignore  2023-03-03 20:20:44 UTC (rev 55458)
+++ trunk/.gitignore    2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -14,6 +14,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /tests/phpunit/data/plugins/wordpress-importer
</span><span class="cx" style="display: block; padding: 0 10px"> /tests/phpunit/data/.trac-ticket-cache*
</span><span class="cx" style="display: block; padding: 0 10px"> /tests/qunit/compiled.html
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/tests/performance/**/*.test.results.json
</ins><span class="cx" style="display: block; padding: 0 10px"> /src/.wp-tests-version
</span><span class="cx" style="display: block; padding: 0 10px"> /node_modules
</span><span class="cx" style="display: block; padding: 0 10px"> /npm-debug.log
</span></span></pre></div>
<a id="trunkpackagejson"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/package.json</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/package.json        2023-03-03 20:20:44 UTC (rev 55458)
+++ trunk/package.json  2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -176,6 +176,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                "env:cli": "node ./tools/local-env/scripts/docker.js run cli",
</span><span class="cx" style="display: block; padding: 0 10px">                "env:logs": "node ./tools/local-env/scripts/docker.js logs",
</span><span class="cx" style="display: block; padding: 0 10px">                "env:pull": "node ./tools/local-env/scripts/docker.js pull",
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                "test:performance": "node ./tests/performance/run-tests.js",
</ins><span class="cx" style="display: block; padding: 0 10px">                 "test:php": "node ./tools/local-env/scripts/docker.js run -T php composer update -W && node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit",
</span><span class="cx" style="display: block; padding: 0 10px">                "test:e2e": "node ./tests/e2e/run-tests.js",
</span><span class="cx" style="display: block; padding: 0 10px">                "test:visual": "node ./tests/visual-regression/run-tests.js",
</span></span></pre></div>
<a id="trunktestsperformanceconfigbootstrapjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/performance/config/bootstrap.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/performance/config/bootstrap.js                               (rev 0)
+++ trunk/tests/performance/config/bootstrap.js 2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,41 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * WordPress dependencies.
+ */
+import {
+       clearLocalStorage,
+       enablePageDialogAccept,
+       setBrowserViewport,
+} from '@wordpress/e2e-test-utils';
+
+/**
+ * Timeout, in seconds, that the test should be allowed to run.
+ *
+ * @type {string|undefined}
+ */
+const PUPPETEER_TIMEOUT = process.env.PUPPETEER_TIMEOUT;
+
+// The Jest timeout is increased because these tests are a bit slow.
+jest.setTimeout( PUPPETEER_TIMEOUT || 100000 );
+
+async function setupBrowser() {
+       await clearLocalStorage();
+       await setBrowserViewport( 'large' );
+}
+
+/*
+ * Before every test suite run, delete all content created by the test. This ensures
+ * other posts/comments/etc. aren't dirtying tests and tests don't depend on
+ * each other's side-effects.
+ */
+beforeAll( async () => {
+       enablePageDialogAccept();
+
+       await setBrowserViewport( 'large' );
+       await page.emulateMediaFeatures( [
+               { name: 'prefers-reduced-motion', value: 'reduce' },
+       ] );
+} );
+
+afterEach( async () => {
+       await setupBrowser();
+} );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/performance/config/bootstrap.js
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsperformancejestconfigjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/performance/jest.config.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/performance/jest.config.js                            (rev 0)
+++ trunk/tests/performance/jest.config.js      2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,14 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+const config = require( '@wordpress/scripts/config/jest-e2e.config' );
+
+const jestE2EConfig = {
+       ...config,
+       setupFilesAfterEnv: [
+               '<rootDir>/config/bootstrap.js',
+       ],
+       globals: {
+               // Number of requests to run per test.
+               TEST_RUNS: 20,
+       }
+};
+
+module.exports = jestE2EConfig;
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/performance/jest.config.js
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsperformancelogresultsjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/performance/log-results.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/performance/log-results.js                            (rev 0)
+++ trunk/tests/performance/log-results.js      2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,102 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+#!/usr/bin/env node
+
+/**
+ * External dependencies.
+ */
+const fs = require( 'fs' );
+const path = require( 'path' );
+const https = require( 'https' );
+const [ token, branch, hash, baseHash, timestamp, host ] = process.argv.slice( 2 );
+const { median } = require( './utils' );
+
+// The list of test suites to log.
+const testSuites = [
+       'home-block-theme',
+       'home-classic-theme',
+];
+
+// A list of results to parse based on test suites.
+const testResults = testSuites.map(( key ) => ({
+       key,
+       file: `${ key }.test.results.json`,
+}));
+
+// A list of base results to parse based on test suites.
+const baseResults = testSuites.map(( key ) => ({
+       key,
+       file: `base-${ key }.test.results.json`,
+}));
+
+/**
+ * Parse test files into JSON objects.
+ *
+ * @param {string} fileName The name of the file.
+ * @returns An array of parsed objects from each file.
+ */
+const parseFile = ( fileName ) => (
+       JSON.parse(
+               fs.readFileSync( path.join( __dirname, '/specs/', fileName ), 'utf8' )
+       )
+);
+
+/**
+ * Gets the array of metrics from a list of results.
+ *
+ * @param {Object[]} results A list of results to format.
+ * @return {Object[]} Metrics.
+ */
+const formatResults = ( results ) => {
+       return results.reduce(
+               ( result, { key, file } ) => {
+                       return {
+                               ...result,
+                               ...Object.fromEntries(
+                                       Object.entries(
+                                               parseFile( file ) ?? {}
+                                       ).map( ( [ metric, value ] ) => [
+                                               key + '-' + metric,
+                                               median ( value ),
+                                       ] )
+                               ),
+                       };
+               },
+               {}
+       );
+};
+
+const data = new TextEncoder().encode(
+       JSON.stringify( {
+               branch,
+               hash,
+               baseHash,
+               timestamp: parseInt( timestamp, 10 ),
+               metrics: formatResults( testResults ),
+               baseMetrics: formatResults( baseResults ),
+       } )
+);
+
+const options = {
+       hostname: host,
+       port: 443,
+       path: '/api/log?token=' + token,
+       method: 'POST',
+       headers: {
+               'Content-Type': 'application/json',
+               'Content-Length': data.length,
+       },
+};
+
+const req = https.request( options, ( res ) => {
+       console.log( `statusCode: ${ res.statusCode }` );
+
+       res.on( 'data', ( d ) => {
+               process.stdout.write( d );
+       } );
+} );
+
+req.on( 'error', ( error ) => {
+       console.error( error );
+} );
+
+req.write( data );
+req.end();
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/performance/log-results.js
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsperformanceresultsjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/performance/results.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/performance/results.js                                (rev 0)
+++ trunk/tests/performance/results.js  2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,38 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+#!/usr/bin/env node
+
+/**
+ * External dependencies.
+ */
+const fs = require( 'fs' );
+const { join } = require( 'path' );
+const { median, getResultsFilename } = require( './utils' );
+
+const testSuites = [
+    'home-classic-theme',
+    'home-block-theme',
+];
+
+console.log( '\n>> 🎉 Results 🎉 \n' );
+
+for ( const testSuite of testSuites ) {
+    const resultsFileName = getResultsFilename( testSuite + '.test' );
+    const resultsPath = join( __dirname, '/specs/', resultsFileName );
+    fs.readFile( resultsPath, "utf8", ( err, data ) => {
+        if ( err ) {
+            console.log( "File read failed:", err );
+            return;
+        }
+        const convertString = testSuite.charAt( 0 ).toUpperCase() + testSuite.slice( 1 );
+        console.log( convertString.replace( /[-]+/g, " " ) + ':' );
+
+        tableData = JSON.parse( data );
+        const rawResults = [];
+
+        for ( var key in tableData ) {
+            if ( tableData.hasOwnProperty( key ) ) {
+                rawResults[ key ] = median( tableData[ key ] );
+            }
+        }
+        console.table( rawResults );
+    });
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/performance/results.js
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsperformanceruntestsjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/performance/run-tests.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/performance/run-tests.js                              (rev 0)
+++ trunk/tests/performance/run-tests.js        2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,16 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * External dependencies.
+ */
+const dotenv = require( 'dotenv' );
+const dotenv_expand = require( 'dotenv-expand' );
+const { execSync } = require( 'child_process' );
+
+// WP_BASE_URL interpolates LOCAL_PORT, so needs to be parsed by dotenv_expand().
+dotenv_expand.expand( dotenv.config() );
+
+// Run the tests, passing additional arguments through to the test script.
+execSync(
+       'wp-scripts test-e2e --config tests/performance/jest.config.js ' +
+               process.argv.slice( 2 ).join( ' ' ),
+       { stdio: 'inherit' }
+);
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/performance/run-tests.js
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsperformancespecshomeblockthemetestjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/performance/specs/home-block-theme.test.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/performance/specs/home-block-theme.test.js                            (rev 0)
+++ trunk/tests/performance/specs/home-block-theme.test.js      2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,53 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * External dependencies.
+ */
+const { basename, join } = require( 'path' );
+const { writeFileSync } = require( 'fs' );
+const { getResultsFilename } = require( './../utils' );
+
+/**
+ * WordPress dependencies.
+ */
+import { activateTheme, createURL } from '@wordpress/e2e-test-utils';
+
+describe( 'Server Timing - Twenty Twenty Three', () => {
+       const results = {
+               wpBeforeTemplate: [],
+               wpTemplate: [],
+               wpTotal: [],
+       };
+
+       beforeAll( async () => {
+               await activateTheme( 'twentytwentythree' );
+       } );
+
+       afterAll( async () => {
+               const resultsFilename = getResultsFilename( basename( __filename, '.js' ) );
+               writeFileSync(
+                       join( __dirname, resultsFilename ),
+                       JSON.stringify( results, null, 2 )
+               );
+       } );
+
+       it( 'Server Timing Metrics', async () => {
+               let i = TEST_RUNS;
+               while ( i-- ) {
+                       await page.goto( createURL( '/' ) );
+                       const navigationTimingJson = await page.evaluate( () =>
+                               JSON.stringify( performance.getEntriesByType( 'navigation' ) )
+                       );
+
+                       const [ navigationTiming ] = JSON.parse( navigationTimingJson );
+
+                       results.wpBeforeTemplate.push(
+                               navigationTiming.serverTiming[0].duration
+                       );
+                       results.wpTemplate.push(
+                               navigationTiming.serverTiming[1].duration
+                       );
+                       results.wpTotal.push(
+                               navigationTiming.serverTiming[2].duration
+                       );
+               }
+       } );
+} );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/performance/specs/home-block-theme.test.js
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsperformancespecshomeclassicthemetestjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/performance/specs/home-classic-theme.test.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/performance/specs/home-classic-theme.test.js                          (rev 0)
+++ trunk/tests/performance/specs/home-classic-theme.test.js    2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,55 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * External dependencies.
+ */
+const { basename, join } = require( 'path' );
+const { writeFileSync } = require( 'fs' );
+const { exec } = require( 'child_process' );
+const { getResultsFilename } = require( './../utils' );
+
+/**
+ * WordPress dependencies.
+ */
+import { activateTheme, createURL } from '@wordpress/e2e-test-utils';
+
+describe( 'Server Timing - Twenty Twenty One', () => {
+       const results = {
+               wpBeforeTemplate: [],
+               wpTemplate: [],
+               wpTotal: [],
+       };
+
+       beforeAll( async () => {
+               await activateTheme( 'twentytwentyone' );
+               await exec( 'npm run env:cli -- menu location assign all-pages primary' );
+       } );
+
+       afterAll( async () => {
+               const resultsFilename = getResultsFilename( basename( __filename, '.js' ) );
+               writeFileSync(
+                       join( __dirname, resultsFilename ),
+                       JSON.stringify( results, null, 2 )
+               );
+       } );
+
+       it( 'Server Timing Metrics', async () => {
+               let i = TEST_RUNS;
+               while ( i-- ) {
+                       await page.goto( createURL( '/' ) );
+                       const navigationTimingJson = await page.evaluate( () =>
+                               JSON.stringify( performance.getEntriesByType( 'navigation' ) )
+                       );
+
+                       const [ navigationTiming ] = JSON.parse( navigationTimingJson );
+
+                       results.wpBeforeTemplate.push(
+                               navigationTiming.serverTiming[0].duration
+                       );
+                       results.wpTemplate.push(
+                               navigationTiming.serverTiming[1].duration
+                       );
+                       results.wpTotal.push(
+                               navigationTiming.serverTiming[2].duration
+                       );
+               }
+       } );
+} );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/performance/specs/home-classic-theme.test.js
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsperformanceutilsjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/performance/utils.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/performance/utils.js                          (rev 0)
+++ trunk/tests/performance/utils.js    2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,33 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * Computes the median number from an array numbers.
+ *
+ * @param {number[]} array
+ *
+ * @return {number} Median.
+ */
+function median( array ) {
+       const mid = Math.floor( array.length / 2 );
+       const numbers = [ ...array ].sort( ( a, b ) => a - b );
+       return array.length % 2 !== 0
+               ? numbers[ mid ]
+               : ( numbers[ mid - 1 ] + numbers[ mid ] ) / 2;
+}
+
+/**
+ * Gets the result file name.
+ *
+ * @param {string} File name.
+ *
+ * @return {string} Result file name.
+ */
+function getResultsFilename( fileName ) {
+       const prefixArg = process.argv.find( ( arg ) => arg.startsWith( '--prefix' ) );
+       const fileNamePrefix = prefixArg ? `${prefixArg.split( '=' )[1]}-` : '';
+       const resultsFilename = fileNamePrefix + fileName + '.results.json';
+       return resultsFilename;
+}
+
+module.exports = {
+       median,
+       getResultsFilename,
+};
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/performance/utils.js
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsperformancewpcontentmupluginsservertimingphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/performance/wp-content/mu-plugins/server-timing.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/performance/wp-content/mu-plugins/server-timing.php                           (rev 0)
+++ trunk/tests/performance/wp-content/mu-plugins/server-timing.php     2023-03-03 20:37:10 UTC (rev 55459)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,43 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+add_action(
+       'template_redirect',
+       function() {
+
+               global $timestart;
+
+               $server_timing_values = array();
+               $template_start       = microtime( true );
+
+               $server_timing_values['before-template'] = $template_start - $timestart;
+
+               ob_start();
+
+               add_action(
+                       'shutdown',
+                       function() use ( $server_timing_values, $template_start ) {
+
+                               global $timestart;
+
+                               $output = ob_get_clean();
+
+                               $server_timing_values['template'] = microtime( true ) - $template_start;
+
+                               $server_timing_values['total'] = $server_timing_values['before-template'] + $server_timing_values['template'];
+
+                               $header_values = array();
+                               foreach ( $server_timing_values as $slug => $value ) {
+                                       if ( is_float( $value ) ) {
+                                               $value = round( $value * 1000.0, 2 );
+                                       }
+                                       $header_values[] = sprintf( 'wp-%1$s;dur=%2$s', $slug, $value );
+                               }
+                               header( 'Server-Timing: ' . implode( ', ', $header_values ) );
+
+                               echo $output;
+                       },
+                       PHP_INT_MIN
+               );
+       },
+       PHP_INT_MAX
+);
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/performance/wp-content/mu-plugins/server-timing.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>

</body>
</html>