From b49d40ab2cc3abd6f5e4ca6d9fd236165589ff2c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Jan 2026 12:07:11 +0000 Subject: [PATCH 1/2] Fix e2e tests: add proper health check for WordPress Playground Replace the unreliable 2-second fixed wait with a polling mechanism that verifies WordPress is actually ready to accept requests: - Poll /wp-admin/ endpoint up to 30 times (60 seconds max) - Check for HTTP 200 or 302 response - Log progress for debugging - Throw error if server doesn't start in time - Keep additional 1-second wait for plugin initialization --- tests/global-setup.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/tests/global-setup.ts b/tests/global-setup.ts index 1368eea..7992578 100644 --- a/tests/global-setup.ts +++ b/tests/global-setup.ts @@ -55,8 +55,37 @@ async function globalSetup(): Promise { // eslint-disable-next-line no-console console.log('WordPress Playground server started on http://127.0.0.1:9400'); - // Wait a bit for the server to be fully ready - await new Promise((resolve) => setTimeout(resolve, 2000)); + // Wait for WordPress to be fully ready by polling the health endpoint + const maxAttempts = 30; + const delayMs = 2000; + let attempts = 0; + + while (attempts < maxAttempts) { + try { + const response = await fetch('http://127.0.0.1:9400/wp-admin/'); + if (response.ok || response.status === 302) { + // eslint-disable-next-line no-console + console.log(`WordPress is ready after ${attempts + 1} attempt(s)`); + break; + } + } catch { + // Server not ready yet, continue polling + } + + attempts++; + if (attempts < maxAttempts) { + // eslint-disable-next-line no-console + console.log(`Waiting for WordPress to be ready... (attempt ${attempts}/${maxAttempts})`); + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } + } + + if (attempts >= maxAttempts) { + throw new Error('WordPress Playground failed to start within the expected time'); + } + + // Additional wait for plugin initialization + await new Promise((resolve) => setTimeout(resolve, 1000)); } export default globalSetup; From 3a269a4b9ebf2c713140c44c999582b11a9de675 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Jan 2026 14:27:07 +0000 Subject: [PATCH 2/2] Fix CI: pin Node.js to v20 and improve error handling - Pin Node.js to version 20 in playwright.yml to avoid compatibility issues with newer Node versions (v24 caused ErrnoError errno: 44) - Add try-catch around WordPress Playground initialization - Add fetch timeout with AbortController to prevent hanging requests - Log last error when health check times out for better debugging --- .github/workflows/playwright.yml | 2 +- tests/global-setup.ts | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index af113a9..1baa948 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 'lts/*' + node-version: '20' cache: 'npm' - name: Install dependencies diff --git a/tests/global-setup.ts b/tests/global-setup.ts index 7992578..7c036ab 100644 --- a/tests/global-setup.ts +++ b/tests/global-setup.ts @@ -8,7 +8,9 @@ async function globalSetup(): Promise { // Use process.cwd() and navigate to plugin directory const pluginPath = path.join(process.cwd()); - const cliServer = await runCLI({ + let cliServer; + try { + cliServer = await runCLI({ command: 'server', php: '8.3', wp: 'latest', @@ -48,6 +50,11 @@ async function globalSetup(): Promise { ], }, }); + } catch (error) { + // eslint-disable-next-line no-console + console.error('Failed to start WordPress Playground:', error); + throw error; + } // Store the server instance globally for teardown (global as any).cliServer = cliServer; @@ -59,16 +66,27 @@ async function globalSetup(): Promise { const maxAttempts = 30; const delayMs = 2000; let attempts = 0; + let lastError: Error | null = null; while (attempts < maxAttempts) { try { - const response = await fetch('http://127.0.0.1:9400/wp-admin/'); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + const response = await fetch('http://127.0.0.1:9400/wp-admin/', { + signal: controller.signal, + }); + + clearTimeout(timeoutId); + if (response.ok || response.status === 302) { // eslint-disable-next-line no-console console.log(`WordPress is ready after ${attempts + 1} attempt(s)`); + lastError = null; break; } - } catch { + } catch (error) { + lastError = error as Error; // Server not ready yet, continue polling } @@ -81,6 +99,8 @@ async function globalSetup(): Promise { } if (attempts >= maxAttempts) { + // eslint-disable-next-line no-console + console.error('Last error:', lastError); throw new Error('WordPress Playground failed to start within the expected time'); }