<!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>[58430] trunk: Build/Test Tools: add new end-to-end tests for edge cases such as maintenance mode.</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/58430">58430</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/58430","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>swissspidy</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-06-18 08:18:51 +0000 (Tue, 18 Jun 2024)</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 new end-to-end tests for edge cases such as maintenance mode.

Sometimes errors only occur in unusual code paths such as the maintenance mode or installation screens. Due to lack of tests for these scenarios in core, such errors are usually only noticed very late. This change adds new end-to-end (e2e) tests to prevent regressions in the following areas:

- Maintenance mode (presence of a `.maintenance` file)
- Fatal error handler (simulated with an mu-plugin that causes an error)
- Installation screen (verifying full installation flow & that there are no database errors)

Thanks to these tests, an issue was already found and addressed in the default `wp_die` handler, as `wp_robots_noindex_embeds` and `wp_robots_noindex_search` used to cause PHP warnings due to `$wp_query` not existing.

In the future, these tests can be extended to also test scenarios like localized error pages via `wp_load_translations_early()`.

Fixes <a href="https://core.trac.wordpress.org/ticket/61240">#61240</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesfunctionsphp">trunk/src/wp-includes/functions.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestse2especsfatalerrorhandlertestjs">trunk/tests/e2e/specs/fatal-error-handler.test.js</a></li>
<li><a href="#trunktestse2especsinstalltestjs">trunk/tests/e2e/specs/install.test.js</a></li>
<li><a href="#trunktestse2especsmaintenancemodetestjs">trunk/tests/e2e/specs/maintenance-mode.test.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesfunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/functions.php       2024-06-18 07:07:52 UTC (rev 58429)
+++ trunk/src/wp-includes/functions.php 2024-06-18 08:18:51 UTC (rev 58430)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3862,6 +3862,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                <?php
</span><span class="cx" style="display: block; padding: 0 10px">                if ( function_exists( 'wp_robots' ) && function_exists( 'wp_robots_no_robots' ) && function_exists( 'add_filter' ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        add_filter( 'wp_robots', 'wp_robots_no_robots' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        // Prevent warnings because of $wp_query not existing.
+                       remove_filter( 'wp_robots', 'wp_robots_noindex_embeds' );
+                       remove_filter( 'wp_robots', 'wp_robots_noindex_search' );
</ins><span class="cx" style="display: block; padding: 0 10px">                         wp_robots();
</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="trunktestse2especsfatalerrorhandlertestjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/e2e/specs/fatal-error-handler.test.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/e2e/specs/fatal-error-handler.test.js                         (rev 0)
+++ trunk/tests/e2e/specs/fatal-error-handler.test.js   2024-06-18 08:18:51 UTC (rev 58430)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,46 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * External dependencies
+ */
+import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
+import { join } from 'node:path';
+
+/**
+ * WordPress dependencies
+ */
+import { test, expect } from '@wordpress/e2e-test-utils-playwright';
+
+test.describe( 'Fatal error handler', () => {
+       const muPlugins = join(
+               process.cwd(),
+               process.env.LOCAL_DIR ?? 'src',
+               'wp-content/mu-plugins'
+       );
+       const muPluginFile = join( muPlugins, 'fatal-error.php' );
+
+       test.beforeAll( async () => {
+               const muPluginCode = `<?php new NonExistentClass();`;
+
+               if ( ! existsSync( muPlugins ) ) {
+                       mkdirSync( muPlugins, { recursive: true } );
+               }
+               writeFileSync( muPluginFile, muPluginCode );
+       } );
+
+       test.afterAll( async () => {
+               unlinkSync( muPluginFile );
+       } );
+
+       test( 'should display fatal error notice', async ( { admin, page } ) => {
+               await admin.visitAdminPage( '/' );
+
+               await expect(
+                       page.getByText( /Fatal error:/ ),
+                       'should display PHP error message'
+               ).toBeVisible();
+
+               await expect(
+                       page.getByText( /There has been a critical error on this website/ ),
+                       'should display WordPress fatal error handler message'
+               ).toBeVisible();
+       } );
+} );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/e2e/specs/fatal-error-handler.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="trunktestse2especsinstalltestjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/e2e/specs/install.test.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/e2e/specs/install.test.js                             (rev 0)
+++ trunk/tests/e2e/specs/install.test.js       2024-06-18 08:18:51 UTC (rev 58430)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,85 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * External dependencies
+ */
+import { writeFileSync, readFileSync } from 'node:fs';
+import { join } from 'node:path';
+
+/**
+ * WordPress dependencies
+ */
+import { test, expect } from '@wordpress/e2e-test-utils-playwright';
+
+let wpConfigOriginal;
+
+test.describe( 'WordPress installation process', () => {
+       const wpConfig = join(
+               process.cwd(),
+               'wp-config.php',
+       );
+
+
+       test.beforeEach( async () => {
+               wpConfigOriginal = readFileSync( wpConfig, 'utf-8' );
+               // Changing the table prefix tricks WP into new install mode.
+               writeFileSync(
+                       wpConfig,
+                       wpConfigOriginal.replace( `$table_prefix = 'wp_';`, `$table_prefix = 'wp_e2e_';` )
+               );
+       } );
+
+       test.afterEach( async () => {
+               writeFileSync( wpConfig, wpConfigOriginal );
+       } );
+
+       test( 'should install WordPress with pre-existing database credentials', async ( { page } ) => {
+               await page.goto( '/' );
+
+               await expect(
+                       page,
+                       'should redirect to the installation page'
+               ).toHaveURL( /wp-admin\/install\.php$/ );
+
+               await expect(
+                       page.getByText( /WordPress database error/ ),
+                       'should not have any database errors'
+               ).not.toBeVisible();
+
+               // First page: language selector. Keep default English (US).
+               await page.getByRole( 'button', { name: 'Continue' } ).click();
+
+               // Second page: enter site name, username & password.
+
+               await expect( page.getByRole( 'heading', { name: 'Welcome' } ) ).toBeVisible();
+
+               // This information matches tools/local-env/scripts/install.js.
+
+               await page.getByLabel( 'Site Title' ).fill( 'WordPress Develop' );
+               await page.getByLabel( 'Username' ).fill( 'admin' );
+               await page.getByLabel( 'Password', { exact: true } ).fill( '' );
+               await page.getByLabel( 'Password', { exact: true } ).fill( 'password' );
+               await page.getByLabel( /Confirm use of weak password/ ).check()
+               await page.getByLabel( 'Your Email' ).fill( 'test@test.com' );
+
+               await page.getByRole( 'button', { name: 'Install WordPress' } ).click();
+
+               // Installation finished, can now log in.
+
+               await expect( page.getByRole( 'heading', { name: 'Success!' } ) ).toBeVisible();
+
+               await page.getByRole( 'link', { name: 'Log In' } ).click();
+
+               await expect(
+                       page,
+                       'should redirect to the login page'
+               ).toHaveURL( /wp-login\.php$/ );
+
+               await page.getByLabel( 'Username or Email Address' ).fill( 'admin' );
+               await page.getByLabel( 'Password', { exact: true } ).fill( 'password' );
+
+               await page.getByRole( 'button', { name: 'Log In' } ).click();
+
+               await expect(
+                       page.getByRole( 'heading', { name: 'Welcome to WordPress', level: 2 })
+               ).toBeVisible();
+       } );
+} );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/e2e/specs/install.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="trunktestse2especsmaintenancemodetestjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/e2e/specs/maintenance-mode.test.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/e2e/specs/maintenance-mode.test.js                            (rev 0)
+++ trunk/tests/e2e/specs/maintenance-mode.test.js      2024-06-18 08:18:51 UTC (rev 58430)
</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">+/**
+ * External dependencies
+ */
+import { writeFileSync, unlinkSync } from 'node:fs';
+import { join } from 'node:path';
+
+/**
+ * WordPress dependencies
+ */
+import { test, expect } from '@wordpress/e2e-test-utils-playwright';
+
+test.describe( 'Maintenance mode', () => {
+       const documentRoot = join(
+               process.cwd(),
+               process.env.LOCAL_DIR ?? 'src',
+       );
+       const maintenanceLockFile = join( documentRoot, '.maintenance' );
+
+       test.beforeAll( async () => {
+               writeFileSync( maintenanceLockFile, '<?php $upgrading = 10000000000; ?>' ); // Year 2286.
+       } );
+
+       test.afterAll( async () => {
+               unlinkSync( maintenanceLockFile );
+       } );
+
+       test( 'should display maintenance mode page', async ( { page } ) => {
+               await page.goto( '/' );
+               await expect(
+                       page.getByText( /Briefly unavailable for scheduled maintenance\. Check back in a minute\./ )
+               ).toBeVisible();
+       } );
+} );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/e2e/specs/maintenance-mode.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>