<!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>[51989] trunk: Build/Test Tools: Introduce local visual regression testing.</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/51989">51989</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/51989","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>hellofromTonya</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2021-11-02 21:03:10 +0000 (Tue, 02 Nov 2021)</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: Introduce local visual regression testing.

Adds the ability to ''locally'' run visual regression testing for wp-admin pages via `npm run test:visual`. Snapshots are stored on contributors' local machines.

Note:
Wiring to the CI is not included. Why? The challenges for the CI are storage of the artifacts and unreliability of testing these across different environments.

This commit is a first step towards visual regression testing. Running it locally provides a learning opportunity which could help to craft how to build it into the automated CI process.

Props isabel_brison, andraganescu, azaozz, danfarrow, desrosj, hellofromTonya, justinahinon, netweb, talldanwp.
Fixes <a href="https://core.trac.wordpress.org/ticket/49606">#49606</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkgitignore">trunk/.gitignore</a></li>
<li><a href="#trunkpackagelockjson">trunk/package-lock.json</a></li>
<li><a href="#trunkpackagejson">trunk/package.json</a></li>
<li><a href="#trunktestse2econfigbootstrapjs">trunk/tests/e2e/config/bootstrap.js</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>trunk/tests/visual-regression/</li>
<li><a href="#trunktestsvisualregressionREADMEmd">trunk/tests/visual-regression/README.md</a></li>
<li>trunk/tests/visual-regression/config/</li>
<li><a href="#trunktestsvisualregressionconfigbootstrapjs">trunk/tests/visual-regression/config/bootstrap.js</a></li>
<li><a href="#trunktestsvisualregressionjestconfigjs">trunk/tests/visual-regression/jest.config.js</a></li>
<li><a href="#trunktestsvisualregressionruntestsjs">trunk/tests/visual-regression/run-tests.js</a></li>
<li>trunk/tests/visual-regression/specs/</li>
<li><a href="#trunktestsvisualregressionspecsvisualsnapshotstestjs">trunk/tests/visual-regression/specs/visual-snapshots.test.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<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  2021-11-02 20:18:09 UTC (rev 51988)
+++ trunk/.gitignore    2021-11-02 21:03:10 UTC (rev 51989)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -90,3 +90,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> # Files for local environment config
</span><span class="cx" style="display: block; padding: 0 10px"> /docker-compose.override.yml
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+# Visual regression test diffs
+tests/visual-regression/specs/__image_snapshots__
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="trunkpackagelockjson"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/package-lock.json</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/package-lock.json   2021-11-02 20:18:09 UTC (rev 51988)
+++ trunk/package-lock.json     2021-11-02 21:03:10 UTC (rev 51989)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -12535,6 +12535,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                "minimatch": "~3.0.2"
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                "glur": {
+                       "version": "1.1.2",
+                       "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz",
+                       "integrity": "sha1-8g6jbbEDv8KSNDkh8fkeg8NGdok=",
+                       "dev": true
+               },
</ins><span class="cx" style="display: block; padding: 0 10px">                 "gonzales-pe": {
</span><span class="cx" style="display: block; padding: 0 10px">                        "version": "4.3.0",
</span><span class="cx" style="display: block; padding: 0 10px">                        "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -15607,6 +15613,64 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                "jest-image-snapshot": {
+                       "version": "3.0.1",
+                       "resolved": "https://registry.npmjs.org/jest-image-snapshot/-/jest-image-snapshot-3.0.1.tgz",
+                       "integrity": "sha512-bW8eYxgAVyO8cNLlTt15wd5YiWvRfzQyNQ4K8FKHUEPasQADEZ5NzaWmnOpSdh3/NLYoH++TMp6o/rRVLpOIkQ==",
+                       "dev": true,
+                       "requires": {
+                               "chalk": "^1.1.3",
+                               "get-stdin": "^5.0.1",
+                               "glur": "^1.1.2",
+                               "lodash": "^4.17.4",
+                               "mkdirp": "^0.5.1",
+                               "pixelmatch": "^5.1.0",
+                               "pngjs": "^3.4.0",
+                               "rimraf": "^2.6.2"
+                       },
+                       "dependencies": {
+                               "ansi-regex": {
+                                       "version": "2.1.1",
+                                       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+                                       "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+                                       "dev": true
+                               },
+                               "ansi-styles": {
+                                       "version": "2.2.1",
+                                       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+                                       "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+                                       "dev": true
+                               },
+                               "chalk": {
+                                       "version": "1.1.3",
+                                       "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+                                       "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+                                       "dev": true,
+                                       "requires": {
+                                               "ansi-styles": "^2.2.1",
+                                               "escape-string-regexp": "^1.0.2",
+                                               "has-ansi": "^2.0.0",
+                                               "strip-ansi": "^3.0.0",
+                                               "supports-color": "^2.0.0"
+                                       }
+                               },
+                               "strip-ansi": {
+                                       "version": "3.0.1",
+                                       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+                                       "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+                                       "dev": true,
+                                       "requires": {
+                                               "ansi-regex": "^2.0.0"
+                                       }
+                               },
+                               "supports-color": {
+                                       "version": "2.0.0",
+                                       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+                                       "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+                                       "dev": true
+                               }
+                       }
+               },
</ins><span class="cx" style="display: block; padding: 0 10px">                 "jest-jasmine2": {
</span><span class="cx" style="display: block; padding: 0 10px">                        "version": "26.6.3",
</span><span class="cx" style="display: block; padding: 0 10px">                        "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -19785,6 +19849,23 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                "node-modules-regexp": "^1.0.0"
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                "pixelmatch": {
+                       "version": "5.2.1",
+                       "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz",
+                       "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==",
+                       "dev": true,
+                       "requires": {
+                               "pngjs": "^4.0.1"
+                       },
+                       "dependencies": {
+                               "pngjs": {
+                                       "version": "4.0.1",
+                                       "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz",
+                                       "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==",
+                                       "dev": true
+                               }
+                       }
+               },
</ins><span class="cx" style="display: block; padding: 0 10px">                 "pkg-dir": {
</span><span class="cx" style="display: block; padding: 0 10px">                        "version": "3.0.0",
</span><span class="cx" style="display: block; padding: 0 10px">                        "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -19857,6 +19938,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                "irregular-plurals": "^3.2.0"
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                "pngjs": {
+                       "version": "3.4.0",
+                       "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
+                       "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
+                       "dev": true
+               },
</ins><span class="cx" style="display: block; padding: 0 10px">                 "polyfill-library": {
</span><span class="cx" style="display: block; padding: 0 10px">                        "version": "3.105.0",
</span><span class="cx" style="display: block; padding: 0 10px">                        "resolved": "https://registry.npmjs.org/polyfill-library/-/polyfill-library-3.105.0.tgz",
</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        2021-11-02 20:18:09 UTC (rev 51988)
+++ trunk/package.json  2021-11-02 21:03:10 UTC (rev 51989)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -61,6 +61,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                "grunt-webpack": "^4.0.3",
</span><span class="cx" style="display: block; padding: 0 10px">                "ink-docstrap": "1.3.2",
</span><span class="cx" style="display: block; padding: 0 10px">                "install-changed": "1.1.0",
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                "jest-image-snapshot": "3.0.1",
</ins><span class="cx" style="display: block; padding: 0 10px">                 "matchdep": "~2.0.0",
</span><span class="cx" style="display: block; padding: 0 10px">                "prettier": "npm:wp-prettier@2.0.5",
</span><span class="cx" style="display: block; padding: 0 10px">                "qunit": "~2.16.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -172,6 +173,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                "env:pull": "node ./tools/local-env/scripts/docker.js pull",
</span><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><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                "test:visual": "node ./tests/visual-regression/run-tests.js",
</ins><span class="cx" style="display: block; padding: 0 10px">                 "wp-packages-update": "wp-scripts packages-update"
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestse2econfigbootstrapjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/e2e/config/bootstrap.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/e2e/config/bootstrap.js       2021-11-02 20:18:09 UTC (rev 51988)
+++ trunk/tests/e2e/config/bootstrap.js 2021-11-02 21:03:10 UTC (rev 51989)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -33,6 +33,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> // The Jest timeout is increased because these tests are a bit slow
</span><span class="cx" style="display: block; padding: 0 10px"> jest.setTimeout( PUPPETEER_TIMEOUT || 100000 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Adds an event listener to the page to handle additions of page event
</span><span class="cx" style="display: block; padding: 0 10px">  * handlers, to assure that they are removed at test teardown.
</span></span></pre></div>
<a id="trunktestsvisualregressionREADMEmd"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/visual-regression/README.md</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/visual-regression/README.md                           (rev 0)
+++ trunk/tests/visual-regression/README.md     2021-11-02 21:03:10 UTC (rev 51989)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,11 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+# Visual Regression Tests in WordPress Core
+
+These tests make use of Jest and Puppeteer, with a setup very similar to that of the e2e tests, together with [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot) for generating the visual diffs.
+
+## How to Run the Tests Locally
+
+1. Check out trunk.
+2. Run `npm run test:visual` to generate some base snapshots.
+3. Check out the feature branch to be tested.
+4. Run `npm run test:visual` again. If any tests fail, the diff images can be found in `tests/visual-regression/specs/__image_snapshots__/__diff_output__`.
+
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/visual-regression/README.md
</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="trunktestsvisualregressionconfigbootstrapjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/visual-regression/config/bootstrap.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/visual-regression/config/bootstrap.js                         (rev 0)
+++ trunk/tests/visual-regression/config/bootstrap.js   2021-11-02 21:03:10 UTC (rev 51989)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,10 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
+
+// All available options: https://github.com/americanexpress/jest-image-snapshot#%EF%B8%8F-api
+const toMatchImageSnapshot = configureToMatchImageSnapshot( {
+       // Maximum diff to allow in px.
+       failureThreshold: 1,
+} );
+
+// Extend Jest's "expect" with image snapshot functionality.
+expect.extend( { toMatchImageSnapshot } );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/visual-regression/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="trunktestsvisualregressionjestconfigjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/visual-regression/jest.config.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/visual-regression/jest.config.js                              (rev 0)
+++ trunk/tests/visual-regression/jest.config.js        2021-11-02 21:03:10 UTC (rev 51989)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,8 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+const config = require( '@wordpress/scripts/config/jest-e2e.config' );
+
+const jestVisualRegressionConfig = {
+       ...config,
+       setupFilesAfterEnv: [ '<rootDir>/config/bootstrap.js' ],
+};
+
+module.exports = jestVisualRegressionConfig;
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/visual-regression/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="trunktestsvisualregressionruntestsjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/visual-regression/run-tests.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/visual-regression/run-tests.js                                (rev 0)
+++ trunk/tests/visual-regression/run-tests.js  2021-11-02 21:03:10 UTC (rev 51989)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,13 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+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( dotenv.config() );
+
+// Run the tests, passing additional arguments through to the test script.
+execSync(
+       'wp-scripts test-e2e --config tests/visual-regression/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/visual-regression/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><span class="cx" style="display: block; padding: 0 10px">Index: trunk/tests/visual-regression/specs
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- trunk/tests/visual-regression/specs  2021-11-02 20:18:09 UTC (rev 51988)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ trunk/tests/visual-regression/specs   2021-11-02 21:03:10 UTC (rev 51989)
</ins><a id="trunktestsvisualregressionspecs"></a>
<div class="propset"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Property changes: trunk/tests/visual-regression/specs</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnignore"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:ignore</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+__image_snapshots__
</ins><a id="trunktestsvisualregressionspecsvisualsnapshotstestjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/visual-regression/specs/visual-snapshots.test.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/visual-regression/specs/visual-snapshots.test.js                              (rev 0)
+++ trunk/tests/visual-regression/specs/visual-snapshots.test.js        2021-11-02 21:03:10 UTC (rev 51989)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,222 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+import { visitAdminPage } from '@wordpress/e2e-test-utils';
+
+// See https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#pagescreenshotoptions for more available options.
+const screenshotOptions = {
+       fullPage: true,
+};
+
+async function hideElementVisibility( elements ) {
+       for ( let i = 0; i < elements.length; i++ ) {
+               const elementOnPage = await page.$( elements[ i ] );
+               if ( elementOnPage ) {
+                       await elementOnPage.evaluate( ( el ) => {
+                               el.style.visibility = 'hidden';
+                       } );
+               }
+       }
+       await page.waitFor( 1000 );
+}
+
+async function removeElementFromLayout( elements ) {
+       for ( let i = 0; i < elements.length; i++ ) {
+               const elementOnPage = await page.$( elements[ i ] );
+               if ( elementOnPage ) {
+                       await elementOnPage.evaluate( ( el ) => {
+                               el.style.visibility = 'hidden';
+                       } );
+               }
+       }
+       await page.waitFor( 1000 );
+}
+
+const elementsToHide = [ '#footer-upgrade', '#wp-admin-bar-root-default' ];
+
+const elementsToRemove = [ '#toplevel_page_gutenberg' ];
+
+describe( 'Admin Visual Snapshots', () => {
+       beforeAll( async () => {
+               await page.setViewport( {
+                       width: 1000,
+                       height: 750,
+               } );
+       } );
+
+       it( 'All Posts', async () => {
+               await visitAdminPage( '/edit.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Categories', async () => {
+               await visitAdminPage( '/edit-tags.php', 'taxonomy=category' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Tags', async () => {
+               await visitAdminPage( '/edit-tags.php', 'taxonomy=post_tag' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Media Library', async () => {
+               await visitAdminPage( '/upload.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Add New Media', async () => {
+               await visitAdminPage( '/media-new.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'All Pages', async () => {
+               await visitAdminPage( '/edit.php', 'post_type=page' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Comments', async () => {
+               await visitAdminPage( '/edit-comments.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Widgets', async () => {
+               await visitAdminPage( '/widgets.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Menus', async () => {
+               await visitAdminPage( '/nav-menus.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Plugins', async () => {
+               await visitAdminPage( '/plugins.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'All Users', async () => {
+               await visitAdminPage( '/users.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Add New User', async () => {
+               await visitAdminPage( '/user-new.php' );
+               await hideElementVisibility( [
+                       ...elementsToHide,
+                       '.password-input-wrapper',
+               ] );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Your Profile', async () => {
+               await visitAdminPage( '/profile.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Available Tools', async () => {
+               await visitAdminPage( '/tools.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Import', async () => {
+               await visitAdminPage( '/import.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Export', async () => {
+               await visitAdminPage( '/export.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Export Personal Data', async () => {
+               await visitAdminPage( '/export-personal-data.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Erase Personal Data', async () => {
+               await visitAdminPage( '/erase-personal-data.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Reading Settings', async () => {
+               await visitAdminPage( '/options-reading.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Discussion Settings', async () => {
+               await visitAdminPage( '/options-discussion.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Media Settings', async () => {
+               await visitAdminPage( '/options-media.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+
+       it( 'Privacy Settings', async () => {
+               await visitAdminPage( '/options-privacy.php' );
+               await hideElementVisibility( elementsToHide );
+               await removeElementFromLayout( elementsToRemove );
+               const image = await page.screenshot( screenshotOptions );
+               expect( image ).toMatchImageSnapshot();
+       } );
+} );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/visual-regression/specs/visual-snapshots.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></div>

</body>
</html>