diff --git a/.github/actions/e2e-suite/action.yml b/.github/actions/e2e-suite/action.yml new file mode 100644 index 0000000..237f119 --- /dev/null +++ b/.github/actions/e2e-suite/action.yml @@ -0,0 +1,127 @@ +name: Run e2e suite +description: >- + Build the plugin, boot wp-env against a given WordPress core, and run the + Playwright suite. Shared by the blocking and experimental matrix jobs so + the two cannot drift apart. + +inputs: + label: + description: Short lane name, used in artifact names (e.g. "7.1"). + required: true + core: + description: wp-env core source for this lane (e.g. WordPress/WordPress#7.1). + required: true + unpinned_plugins: + description: >- + When "true", ignore the Advanced Query Loop version pinned in + .wp-env.json and use its latest release. + required: false + default: 'false' + comment_on_pr: + description: Whether to leave a sticky Playwright report comment on the PR. + required: false + default: 'false' + +outputs: + outcome: + description: '"success" or "failure" — whether the Playwright run passed.' + value: ${{ steps.tests.outputs.outcome }} + +runs: + using: composite + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: 'npm' + + - name: Install dependencies + shell: bash + run: npm i + + - name: Build plugin + shell: bash + run: npm run build + + - name: Install Playwright Browsers + shell: bash + run: npx playwright install --with-deps chromium + + # .wp-env.json pins Advanced Query Loop so that re-running an old green + # commit stays green. This override deliberately un-pins it for the + # scheduled early-warning run. Core is not touched here — it comes from + # WP_ENV_CORE below. + - name: Un-pin Advanced Query Loop + if: inputs.unpinned_plugins == 'true' + shell: bash + run: | + cat > .wp-env.override.json <<'JSON' + { + "plugins": [ + ".", + "https://downloads.wordpress.org/plugin/advanced-query-loop.zip" + ] + } + JSON + cat .wp-env.override.json + + - name: Start WordPress environment + shell: bash + env: + WP_ENV_CORE: ${{ inputs.core }} + run: npm run wp-env start + + - name: Wait for WordPress to be ready + shell: bash + run: | + timeout 60 bash -c 'until curl -sSf http://localhost:8889 > /dev/null 2>&1; do sleep 2; done' || echo "WordPress may not be fully ready" + sleep 5 + + - name: Debug - Check WordPress status + shell: bash + run: | + curl -I http://localhost:8889 || echo "WordPress not responding" + docker ps + # Confirms the lane really is running the core it claims to, + # rather than every lane silently testing the same version. + npx wp-env run tests-cli wp core version || true + + # continue-on-error is not available to composite steps, so record the + # outcome by hand and let the caller decide whether it is fatal. + - name: Run Playwright tests + id: tests + shell: bash + env: + CI: true + run: | + if npm run test:e2e; then + echo "outcome=success" >> "$GITHUB_OUTPUT" + else + echo "outcome=failure" >> "$GITHUB_OUTPUT" + fi + + - name: Stop WordPress environment + if: always() + shell: bash + run: npm run wp-env stop || true + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report-wp-${{ inputs.label }} + path: test-results/ + retention-days: 30 + if-no-files-found: ignore + + - name: Comment PR with test results + uses: daun/playwright-report-comment@v3 + if: always() && github.event_name == 'pull_request' + continue-on-error: true + with: + report-file: test-results/results.json + report-tag: wp-${{ inputs.label }} + comment-title: 'Playwright test results — WP ${{ inputs.label }}' + create-comment: ${{ inputs.comment_on_pr }} + job-summary: true diff --git a/.github/workflows/e2e-latest.yml b/.github/workflows/e2e-latest.yml new file mode 100644 index 0000000..d726117 --- /dev/null +++ b/.github/workflows/e2e-latest.yml @@ -0,0 +1,78 @@ +name: E2E (latest AQL) + +# Early warning for third-party plugin drift. +# +# .wp-env.json pins Advanced Query Loop to an exact release so that PR CI is +# reproducible and re-running an old green commit stays green. The trade-off is +# that a breaking AQL release is invisible until someone bumps the pin — which +# is exactly how AQL 5.0.0 broke the suite. This runs the suite against AQL's +# latest release on a schedule, so we find out on our own time. +# +# The WordPress core axis is not covered here: the main Playwright matrix +# already runs a non-blocking `nightly` lane against core trunk on every push. +# So this pins core to current stable and varies only the plugin. +# +# It never runs on pull requests, so a failure here cannot block a merge. + +on: + schedule: + # 05:00 UTC every Monday. + - cron: '0 5 * * 1' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + e2e: + name: Playwright + permissions: + contents: read + pull-requests: write + uses: ./.github/workflows/playwright-tests.yml + with: + # Pinned to the lane that is green, so a failure here means an AQL + # regression and nothing else. Pointing this at 7.0/7.1 would fail + # every week on the known WordPress 7.x gaps and bury the signal. + core_matrix: '[{ "label": "6.9", "core": "WordPress/WordPress#6.9.7", "comment": false }]' + # Core trunk is already covered by the nightly lane on every push. + experimental_matrix: '[]' + unpinned_plugins: true + + report: + name: Report drift + needs: e2e + if: failure() && github.event_name == 'schedule' + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - name: Open or update the drift issue + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + + title='E2E suite fails against the latest Advanced Query Loop' + body=$(printf '%s\n' \ + 'The scheduled `E2E (latest AQL)` run failed.' \ + '' \ + 'The suite passes against the Advanced Query Loop version pinned in' \ + '`.wp-env.json` but fails against its latest release, so bumping that pin' \ + 'will break PR CI until the suite is updated.' \ + '' \ + "Failing run: ${RUN_URL}") + + # One rolling issue rather than one per week. + existing=$(gh issue list --state open --search "$title in:title" \ + --json number --jq '.[0].number // empty') + + if [ -n "$existing" ]; then + gh issue comment "$existing" --body "$body" + else + gh issue create --title "$title" --body "$body" + fi diff --git a/.github/workflows/playwright-tests.yml b/.github/workflows/playwright-tests.yml index 3f724c5..124f982 100644 --- a/.github/workflows/playwright-tests.yml +++ b/.github/workflows/playwright-tests.yml @@ -5,82 +5,166 @@ on: branches: [main, master, develop] pull_request: branches: [main, master, develop] + # Reused by the scheduled `E2E (latest AQL)` workflow, which runs a single + # lane against an un-pinned Advanced Query Loop. + workflow_call: + inputs: + core_matrix: + description: >- + JSON array of blocking lanes, each { label, core }. + Defaults to the supported-versions matrix. + type: string + default: '' + experimental_matrix: + description: >- + JSON array of non-blocking lanes, each { label, core }. + Pass '[]' to run none. Defaults to WordPress trunk. + type: string + default: '' + unpinned_plugins: + description: >- + Ignore the Advanced Query Loop version pinned in + .wp-env.json and use its latest release instead. + type: boolean + default: false jobs: - test: + # Lanes live here rather than inline so a caller can narrow them without + # duplicating the jobs below. + lanes: + name: Resolve matrix + runs-on: ubuntu-latest + outputs: + blocking: ${{ steps.resolve.outputs.blocking }} + experimental: ${{ steps.resolve.outputs.experimental }} + steps: + - name: Resolve lanes + id: resolve + env: + BLOCKING_OVERRIDE: ${{ inputs.core_matrix }} + EXPERIMENTAL_OVERRIDE: ${{ inputs.experimental_matrix }} + run: | + set -euo pipefail + + # Core is pinned per lane to an exact tag, not a branch, so a + # re-run of an old commit resolves to the same WordPress. + read -r -d '' DEFAULT_BLOCKING <<'JSON' || true + [ + { "label": "6.9", "core": "WordPress/WordPress#6.9.7", "comment": true } + ] + JSON + + # 7.0 and 7.1 run on every PR but do not gate merging: the + # suite has real compatibility gaps on WordPress 7.x that + # predate this matrix (see the WP 7.x tracking issue). They + # move back to DEFAULT_BLOCKING once those are closed. + # nightly tracks trunk for early warning and stays here. + read -r -d '' DEFAULT_EXPERIMENTAL <<'JSON' || true + [ + { "label": "7.0", "core": "WordPress/WordPress#7.0.4", "comment": true }, + { "label": "7.1", "core": "WordPress/WordPress#7.1", "comment": true }, + { "label": "nightly", "core": "WordPress/WordPress#master", "comment": false } + ] + JSON + + blocking="${BLOCKING_OVERRIDE:-$DEFAULT_BLOCKING}" + experimental="${EXPERIMENTAL_OVERRIDE:-$DEFAULT_EXPERIMENTAL}" + + echo "$blocking" | jq -e 'type == "array" and length > 0' > /dev/null + echo "$experimental" | jq -e 'type == "array"' > /dev/null + + echo "blocking=$( echo "$blocking" | jq -c . )" >> "$GITHUB_OUTPUT" + echo "experimental=$( echo "$experimental" | jq -c . )" >> "$GITHUB_OUTPUT" + + echo "$blocking" | jq -r '.[] | "blocking: \(.label) -> \(.core)"' + echo "$experimental" | jq -r '.[] | "experimental: \(.label) -> \(.core)"' + + e2e: + name: WP ${{ matrix.label }} + needs: lanes timeout-minutes: 60 runs-on: ubuntu-latest permissions: contents: read pull-requests: write - + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON( needs.lanes.outputs.blocking ) }} steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 + - name: Run e2e suite + id: suite + uses: ./.github/actions/e2e-suite with: - node-version: 24 - cache: 'npm' - - - name: Install dependencies - run: npm i + label: ${{ matrix.label }} + core: ${{ matrix.core }} + unpinned_plugins: ${{ inputs.unpinned_plugins }} + comment_on_pr: ${{ matrix.comment }} - - name: Build plugin - run: npm run build - - - name: Install Playwright Browsers - run: npx playwright install --with-deps chromium + - name: Fail if tests failed + if: steps.suite.outputs.outcome == 'failure' + run: exit 1 - - name: Start WordPress environment - run: npm run wp-env start + # Separate job, and deliberately NOT in the aggregate's `needs`. Keeping + # trunk in the same matrix meant any failure here — including an infra + # blip in wp-env start, which is what happened — dragged the whole matrix + # result down and blocked the PR. Isolating it makes "non-blocking" + # unconditional rather than dependent on which step failed. + e2e-experimental: + name: WP ${{ matrix.label }} + needs: lanes + if: needs.lanes.outputs.experimental != '[]' + timeout-minutes: 60 + runs-on: ubuntu-latest + continue-on-error: true + permissions: + contents: read + pull-requests: write + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON( needs.lanes.outputs.experimental ) }} + steps: + - name: Checkout + uses: actions/checkout@v4 - - name: Wait for WordPress to be ready - run: | - timeout 60 bash -c 'until curl -sSf http://localhost:8889 > /dev/null 2>&1; do sleep 2; done' || echo "WordPress may not be fully ready" - sleep 5 + - name: Run e2e suite + id: suite + uses: ./.github/actions/e2e-suite + with: + label: ${{ matrix.label }} + core: ${{ matrix.core }} + unpinned_plugins: ${{ inputs.unpinned_plugins }} + comment_on_pr: ${{ matrix.comment }} - - name: Debug - Check WordPress status + - name: Flag experimental failure + if: steps.suite.outputs.outcome == 'failure' run: | - curl -I http://localhost:8889 || echo "WordPress not responding" - docker ps - - - name: Run Playwright tests - id: tests - continue-on-error: true - run: npm run test:e2e + echo "::warning title=WP ${{ matrix.label }} e2e failed::Non-blocking lane — see the job summary." + { + echo "### :warning: WP ${{ matrix.label }} e2e failed (non-blocking)" + echo + echo "This lane runs \`${{ matrix.core }}\` and does not gate merging." + echo "For trunk, a failure is early warning of an upstream change." + echo "For a released version, it is a known compatibility gap — see the WP 7.x tracking issue." + } >> "$GITHUB_STEP_SUMMARY" + + # Aggregate gate. Named `test` so the pre-existing required status check + # keeps resolving: the per-version lanes publish names ("WP 7.1") that + # branch protection does not know about. + test: + name: test + needs: e2e + if: always() + runs-on: ubuntu-latest + steps: + - name: Check lane results env: - CI: true - - - name: Stop WordPress environment - if: always() - run: npm run wp-env stop - - - name: Upload test results - uses: actions/upload-artifact@v4 - if: always() - with: - name: playwright-report - path: test-results/ - retention-days: 30 - if-no-files-found: ignore - - - name: Upload test videos and traces - uses: actions/upload-artifact@v4 - if: failure() - with: - name: test-results - path: test-results/ - retention-days: 7 - if-no-files-found: ignore - - - name: Comment PR with test results - uses: daun/playwright-report-comment@v3 - if: always() && github.event_name == 'pull_request' - with: - report-file: test-results/results.json - - - name: Fail if tests failed - if: steps.tests.outcome == 'failure' - run: exit 1 + RESULT: ${{ needs.e2e.result }} + run: | + set -euo pipefail + echo "Blocking lanes: $RESULT" + [ "$RESULT" = "success" ] diff --git a/.gitignore b/.gitignore index cd284f9..de68df9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ playwright/.cache/ /blob-report/ /playwright/.cache/ /playwright/.auth/ +.wp-env.override.json diff --git a/.wp-env.json b/.wp-env.json index 50d82a1..36a21d4 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,8 +1,8 @@ { - "core": "WordPress/WordPress#6.9", + "core": "WordPress/WordPress#7.1", "plugins": [ ".", - "https://downloads.wordpress.org/plugin/advanced-query-loop.zip" + "https://downloads.wordpress.org/plugin/advanced-query-loop.5.0.0.zip" ], "themes": [ "WordPress/twentytwentyfour", "WordPress/twentytwentyfive" ], "mappings": { diff --git a/CLAUDE.md b/CLAUDE.md index 6c49285..58d5918 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -118,7 +118,36 @@ The plugin provides a PHP API for registering custom query presets that can be s ## Testing Environment -Tests use `@wordpress/env` with WordPress 6.9, configured in `.wp-env.json`. The environment includes TwentyTwentyFour and TwentyTwentyFive themes, and the Advanced Query Loop plugin. Tests run on port 8889 and use Playwright with `@wordpress/e2e-test-utils-playwright`. +Tests use `@wordpress/env`, configured in `.wp-env.json`. The environment includes TwentyTwentyFour and TwentyTwentyFive themes, and the Advanced Query Loop plugin. Tests run on port 8889 and use Playwright with `@wordpress/e2e-test-utils-playwright`. + +**Upstream versions are pinned deliberately.** Pins are exact tags/releases, never branches: floating refs let an upstream release break CI with no change in this repo, and make re-running an old green commit depend on the day it runs. When bumping a pin, expect to update any test that drives third-party UI. + +`.wp-env.json` holds the local development default (current stable core, plus the pinned Advanced Query Loop release). CI overrides only the core axis per matrix lane via the `WP_ENV_CORE` environment variable, which takes precedence over `.wp-env.json` for both the dev and tests environments. + +### CI matrix + +`.github/workflows/playwright-tests.yml` runs the suite across WordPress versions. Lanes are defined as JSON in the `lanes` job, so a caller can narrow them without duplicating anything: + +| Lane | Core | Job | Blocking | +| --- | --- | --- | --- | +| `6.9` | `WordPress/WordPress#6.9.7` | `e2e` | yes | +| `7.0` | `WordPress/WordPress#7.0.4` | `e2e-experimental` | no | +| `7.1` | `WordPress/WordPress#7.1` | `e2e-experimental` | no | +| `nightly` | `WordPress/WordPress#master` | `e2e-experimental` | no | + +A failure in a non-blocking lane surfaces as a `::warning::` annotation and a job summary. For `nightly` that is early warning of an upstream change; for `7.0`/`7.1` it is a known compatibility gap. + +**7.0 and 7.1 are non-blocking only until the WordPress 7.x gaps are closed.** They run on every PR and report, but the suite genuinely fails on 7.x — the plugin's `core/query` inspector panels are not found in the 7.x site editor, and 7.1 additionally fails `multiple-post-templates` and a *frontend* preset assertion that cannot be a selector problem. Move them back into `DEFAULT_BLOCKING` in the `lanes` job once that is fixed. Lanes carry a `comment` flag so `nightly` stays out of the PR thread while the released versions report into it. + +Three structural points, each of which fixes a bug that actually happened: + +- **Experimental lanes are a separate job, deliberately excluded from the aggregate's `needs`.** When trunk shared the blocking matrix, *any* failure in it — including an infrastructure blip in `wp-env start` — dragged the matrix result down and blocked the PR. Exempting only the test step is not enough; the lane has to be out of the gate entirely. +- **The aggregate job id is `test`.** That is the name branch protection resolves; the per-version lanes publish names (`WP 7.1`) it does not know about. Renaming or removing that job silently strands any required status check. +- **Steps live in a composite action** (`.github/actions/e2e-suite`) shared by both jobs, so the blocking and experimental paths cannot drift. `continue-on-error` is unavailable to composite steps, so the suite records its own `outcome` output and each caller decides whether that is fatal. Artifact names and report tags are per lane, because `upload-artifact@v4` rejects duplicate names. + +### Scheduled canary + +The scheduled `E2E (latest AQL)` workflow (`.github/workflows/e2e-latest.yml`) reuses the same Playwright job with a single pinned-core lane and Advanced Query Loop un-pinned, so a breaking AQL release shows up on a schedule instead of mid-PR (which is exactly how AQL 5.0.0 broke the suite). Core trunk is not covered there because the `nightly` matrix lane already does it on every push. It never runs on pull requests, so it cannot block a merge; on failure it opens or comments on a single rolling issue. ## Important Implementation Notes diff --git a/tests/e2e/exclude-with-post-in.spec.js b/tests/e2e/exclude-with-post-in.spec.js index 80ea51a..11d04d5 100644 --- a/tests/e2e/exclude-with-post-in.spec.js +++ b/tests/e2e/exclude-with-post-in.spec.js @@ -8,6 +8,36 @@ */ const { test, expect } = require( './fixtures' ); +/** + * Reveals Advanced Query Loop's "Posts to Include" field. + * + * Since AQL 5.0.0 the post parameter controls live in a ToolsPanel, and + * "Include posts" is not one of the items shown by default — the field is only + * rendered once it has been enabled from that panel's options menu. + * + * @param {import('@playwright/test').Page} page Playwright page object. + */ +const revealPostsToIncludeField = async ( page ) => { + await page.getByRole( 'button', { name: 'AQL: Post options' } ).click(); + await page + .getByRole( 'menuitemcheckbox', { name: 'Include posts' } ) + .click(); + await page.keyboard.press( 'Escape' ); +}; + +/** + * Locates Advanced Query Loop's "Posts to Include" field. + * + * Matched by its label rather than by an implicit ARIA role: the role comes + * from @wordpress/components' FormTokenField and is an upstream implementation + * detail, whereas the label is AQL's own user-facing string. + * + * @param {import('@playwright/test').Page} page Playwright page object. + * @return {import('@playwright/test').Locator} The "Posts to Include" input. + */ +const postsToIncludeField = ( page ) => + page.getByLabel( 'Posts to Include', { exact: true } ); + test.describe( 'Exclude Displayed Posts with post__in', () => { test( 'should exclude displayed posts even when post__in is set via Advanced Query Loop', async ( { page, @@ -86,20 +116,23 @@ test.describe( 'Exclude Displayed Posts with post__in', () => { .click(); // Selecting the pattern leaves a child block focused; re-select the - // AQL root block so its sidebar settings (including the Posts - // combobox) are shown. + // AQL root block so its sidebar settings (including the "AQL: Post" + // panel) are shown. await blockEditor.selectBlock.byName( 'core/query', 0 ); // Check if we need to set this to a custom loop, WP 6.9 does not default to custom. await blockEditor.queryBlock.setAsCustom(); // Set the posts to include - AQL requires typing to search; no posts shown until input has text. + await revealPostsToIncludeField( page ); + await postsToIncludeField( page ).click(); + await page.keyboard.type( 'Post 23' ); await page - .getByRole( 'combobox', { name: 'Posts to Include' } ) + .getByRole( 'option', { name: 'Post 23', exact: true } ) .click(); - await page.keyboard.type( 'Post 23' ); - await page.getByRole( 'option', { name: 'Post 23', exact: true } ).click(); await page.keyboard.type( 'Post 21' ); - await page.getByRole( 'option', { name: 'Post 21', exact: true } ).click(); + await page + .getByRole( 'option', { name: 'Post 21', exact: true } ) + .click(); // Get post titles from first query const canvas = page @@ -273,15 +306,14 @@ test.describe( 'Exclude Displayed Posts with post__in', () => { await editor.openDocumentSettingsSidebar(); // Selecting the pattern leaves a child block focused; re-select the - // AQL root block so its sidebar settings (including the Posts - // combobox) are shown. + // AQL root block so its sidebar settings (including the "AQL: Post" + // panel) are shown. await blockEditor.selectBlock.byName( 'core/query', 0 ); await blockEditor.queryBlock.setAsCustom(); // Set the posts to include - AQL requires typing to search; no posts shown until input has text. - await page - .getByRole( 'combobox', { name: 'Posts to Include' } ) - .click(); + await revealPostsToIncludeField( page ); + await postsToIncludeField( page ).click(); for ( const postName of [ 'Post 23', 'Post 21', diff --git a/tests/e2e/fixtures.js b/tests/e2e/fixtures.js index 88fd6cf..bf1361d 100644 --- a/tests/e2e/fixtures.js +++ b/tests/e2e/fixtures.js @@ -88,9 +88,54 @@ export const test = base.extend( { /** * Open the settings sidebar and wait for it to be ready. + * + * editor.openDocumentSettingsSidebar() insists on a header button + * named exactly "Settings" inside the "Editor top bar" region. That + * button is not reachable in the site editor on WordPress 7.x, so + * every test that opened the sidebar there timed out while the same + * tests passed in the post editor. Check whether the sidebar is + * already open first, and fall back to any Settings toggle if the + * core helper cannot find its own. */ async openSettingsSidebar() { - await editor.openDocumentSettingsSidebar(); + const settingsRegion = page.getByRole( 'region', { + name: 'Editor settings', + } ); + + if ( + await settingsRegion + .isVisible( { timeout: 2000 } ) + .catch( () => false ) + ) { + await page.waitForTimeout( 1000 ); + return; + } + + try { + await editor.openDocumentSettingsSidebar(); + } catch ( error ) { + await page + .getByRole( 'button', { name: 'Settings' } ) + .first() + .click(); + await settingsRegion.waitFor( { timeout: 15000 } ); + } + + // The sidebar can open on the Template/Document tab, which holds + // no block inspector controls — so every panel this plugin adds + // to core/query looks missing. The same panels render fine in the + // post editor on the same WordPress, which is what points at the + // tab rather than at the panels themselves. + const blockTab = page.getByRole( 'tab', { name: 'Block' } ); + if ( + await blockTab + .isVisible( { timeout: 2000 } ) + .catch( () => false ) + ) { + await blockTab.click(); + await page.waitForTimeout( 300 ); + } + await page.waitForTimeout( 1000 ); }, @@ -272,5 +317,12 @@ export function resetDatabase() { wpCli( `wp db import /var/www/html/wp-content/plugins/hm-query-loop/tests/e2e/database.sql` ); + // database.sql is a dump taken on one WordPress version, so it pins + // `db_version` to whatever that was. On any other version core treats the + // database as out of date and redirects every admin request to + // wp-admin/upgrade.php, which makes the login in global-setup.js time out + // before a single test runs. Upgrading keeps the fixture version-agnostic, + // and is a no-op when the versions already match. + wpCli( `wp core update-db` ); wpCli( `wp cache flush` ); } diff --git a/tests/e2e/global-setup.js b/tests/e2e/global-setup.js index bdc387a..9facd7e 100644 --- a/tests/e2e/global-setup.js +++ b/tests/e2e/global-setup.js @@ -32,7 +32,14 @@ module.exports = async () => { page.waitForSelector( '#wpadminbar', { timeout: 15000 } ), ] ); } catch ( error ) { - console.error( 'Failed to log in to WordPress' ); + // Report where login actually landed. WordPress bounces admin requests + // to interstitials (wp-admin/upgrade.php when the database is behind + // core, the admin email confirmation screen, and so on), none of which + // match the conditions above — and without the URL the timeout alone + // gives no clue which one it was. + console.error( + `Failed to log in to WordPress. Landed on: ${ page.url() }` + ); await browser.close(); throw error; }