From 5a895776f13c5930f589f8a496c2d1019fbca7c9 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 9 Sep 2026 19:09:09 +0100 Subject: [PATCH 1/4] Tests: handle WP 7.x's pattern overview panel and tour modal WordPress 7.x's site editor shows an abbreviated "Pattern" overview panel instead of the full block inspector when a block came from the template's default content pattern (e.g. twentytwentyfive's index template inserts its Query Loop as the "List of posts" pattern). That overview has no InspectorControls slot, so every panel this plugin adds was invisible until entering the pattern's own editing context via "Edit pattern". Separately, WP 7.x's "Edit your site" onboarding tour can reappear later in the session (not just on first page load) and sits on top of everything, so a click underneath it hangs instead of failing fast. Extracted the existing single dismissal into a shared helper and call it at each point the tour has been observed to reappear. Together these accounted for 8 of the 11 e2e failures on WP 7.0/7.1 tracked in #38. Co-Authored-By: Claude Sonnet 5 --- tests/e2e/fixtures.js | 106 +++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 33 deletions(-) diff --git a/tests/e2e/fixtures.js b/tests/e2e/fixtures.js index bf1361d..f9207b7 100644 --- a/tests/e2e/fixtures.js +++ b/tests/e2e/fixtures.js @@ -43,23 +43,7 @@ export const test = base.extend( { ); // Dismiss "Edit your site" modal if it appears - const editSiteModalVisible = await page - .locator( 'text=Edit your site' ) - .isVisible( { timeout: 2000 } ) - .catch( () => false ); - - if ( editSiteModalVisible ) { - const getStartedButton = page.locator( - 'button:has-text("Get started")' - ); - const isGetStartedVisible = await getStartedButton - .isVisible( { timeout: 1000 } ) - .catch( () => false ); - if ( isGetStartedVisible ) { - await getStartedButton.click(); - await page.waitForTimeout( 500 ); - } - } + await this.dismissEditSiteTour(); // Close welcome guide if it appears const welcomeGuideVisible = await page @@ -86,6 +70,39 @@ export const test = base.extend( { await page.waitForTimeout( 1000 ); }, + /** + * Dismiss the "Edit your site" site editor tour, if present. + * + * On WordPress 7.x this can appear on first page load (already + * handled in visitSiteEditor()) but also reappears later, on + * demand, the first time certain interactions happen — e.g. + * opening the settings sidebar or entering pattern-editing mode. + * It sits on top of everything, so any click underneath it times + * out instead of failing fast. + */ + async dismissEditSiteTour() { + const editSiteModalVisible = await page + .locator( 'text=Edit your site' ) + .isVisible( { timeout: 1000 } ) + .catch( () => false ); + + if ( ! editSiteModalVisible ) { + return; + } + + const getStartedButton = page.locator( + 'button:has-text("Get started")' + ); + if ( + await getStartedButton + .isVisible( { timeout: 1000 } ) + .catch( () => false ) + ) { + await getStartedButton.click(); + await page.waitForTimeout( 500 ); + } + }, + /** * Open the settings sidebar and wait for it to be ready. * @@ -98,29 +115,31 @@ export const test = base.extend( { * core helper cannot find its own. */ async openSettingsSidebar() { + await this.dismissEditSiteTour(); + const settingsRegion = page.getByRole( 'region', { name: 'Editor settings', } ); - if ( - await settingsRegion - .isVisible( { timeout: 2000 } ) - .catch( () => false ) - ) { - await page.waitForTimeout( 1000 ); - return; - } + const alreadyOpen = await settingsRegion + .isVisible( { timeout: 2000 } ) + .catch( () => false ); - try { - await editor.openDocumentSettingsSidebar(); - } catch ( error ) { - await page - .getByRole( 'button', { name: 'Settings' } ) - .first() - .click(); - await settingsRegion.waitFor( { timeout: 15000 } ); + if ( ! alreadyOpen ) { + try { + await editor.openDocumentSettingsSidebar(); + } catch ( error ) { + await this.dismissEditSiteTour(); + await page + .getByRole( 'button', { name: 'Settings' } ) + .first() + .click(); + await settingsRegion.waitFor( { timeout: 15000 } ); + } } + await this.dismissEditSiteTour(); + // 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 @@ -136,6 +155,27 @@ export const test = base.extend( { await page.waitForTimeout( 300 ); } + // WP 7.x's site editor shows an abbreviated "Pattern" overview + // panel — a curated Content-only summary — instead of the full + // block inspector when a block came from the template's default + // content pattern (e.g. the Query Loop in twentytwentyfive's + // index template is inserted as the "List of posts" pattern). + // That overview has no InspectorControls slot, so every panel + // this plugin adds is missing until you enter the pattern's own + // editing context via "Edit pattern". + const editPatternButton = page + .getByRole( 'button', { name: 'Edit pattern' } ) + .first(); + if ( + await editPatternButton + .isVisible( { timeout: 1000 } ) + .catch( () => false ) + ) { + await editPatternButton.click(); + await page.waitForTimeout( 500 ); + await this.dismissEditSiteTour(); + } + await page.waitForTimeout( 1000 ); }, From afa78f36da039ede9f8279a429de602755370ea6 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 9 Sep 2026 19:09:14 +0100 Subject: [PATCH 2/4] Tests: drop the Columns interaction from multiple-post-templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WordPress 7.x no longer exposes the post-template block's grid "Columns" control as a spinbutton — confirmed against the bundled Gutenberg JS, where it's now a RangeControl with the paired number input conditionally hidden. That control was only ever incidental scaffolding in these tests: nothing asserts on column count, only on post counts per template. Dropping the interaction removes a dependency on WordPress core UI this plugin doesn't control, without weakening what the tests actually check. Part of the WP 7.0/7.1 failures tracked in #38. Co-Authored-By: Claude Sonnet 5 --- tests/e2e/multiple-post-templates.spec.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/e2e/multiple-post-templates.spec.js b/tests/e2e/multiple-post-templates.spec.js index 2e942f0..915c6db 100644 --- a/tests/e2e/multiple-post-templates.spec.js +++ b/tests/e2e/multiple-post-templates.spec.js @@ -134,21 +134,11 @@ test.describe( 'Multiple Post Templates', () => { await page .getByRole( 'spinbutton', { name: 'Posts per template' } ) .fill( '2' ); - await page.getByRole( 'spinbutton', { name: 'Columns' } ).click(); - await page - .getByRole( 'spinbutton', { name: 'Columns' } ) - .press( 'Shift+ArrowLeft' ); - await page.getByRole( 'spinbutton', { name: 'Columns' } ).fill( '2' ); await page .getByRole( 'toolbar', { name: 'Block tools' } ) .getByLabel( 'Options' ) .click(); await page.getByRole( 'menuitem', { name: /^Duplicate / } ).click(); - await page.getByRole( 'spinbutton', { name: 'Columns' } ).click(); - await page - .getByRole( 'spinbutton', { name: 'Columns' } ) - .press( 'Shift+ArrowLeft' ); - await page.getByRole( 'spinbutton', { name: 'Columns' } ).fill( '3' ); await page .getByRole( 'button', { name: 'Post Template Settings' } ) .click(); @@ -257,11 +247,6 @@ test.describe( 'Multiple Post Templates', () => { await page .getByRole( 'spinbutton', { name: 'Posts per template' } ) .fill( '2' ); - await page.getByRole( 'spinbutton', { name: 'Columns' } ).click(); - await page - .getByRole( 'spinbutton', { name: 'Columns' } ) - .press( 'Shift+ArrowLeft' ); - await page.getByRole( 'spinbutton', { name: 'Columns' } ).fill( '2' ); // Duplicate to create third post template await page @@ -271,11 +256,6 @@ test.describe( 'Multiple Post Templates', () => { await page.getByRole( 'menuitem', { name: /^Duplicate / } ).click(); // Configure third post template: leave Posts per template EMPTY (should auto-calculate to 7) - await page.getByRole( 'spinbutton', { name: 'Columns' } ).click(); - await page - .getByRole( 'spinbutton', { name: 'Columns' } ) - .press( 'Shift+ArrowLeft' ); - await page.getByRole( 'spinbutton', { name: 'Columns' } ).fill( '3' ); await page .getByRole( 'button', { name: 'Post Template Settings' } ) .click(); From cde80b823e9897e263e2ea075ef5d0b71d7e7395 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 9 Sep 2026 19:09:21 +0100 Subject: [PATCH 3/4] Tests: avoid a WP 7.1 core race when inserting a custom query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend preset test inserted core/query already non-inherited (inherit: false) in its initial attributes. On WordPress 7.1 this reliably crashes core's own Query block inspector — confirmed by deactivating hm-query-loop entirely and reproducing the identical error — with "Cannot read properties of undefined (reading 'singular_name')" from block-library's QueryInspectorControls, which calls getPostType(postType)?.labels.singular_name without guarding `.labels`. The post type entity record isn't always hydrated by the time a non-inherited query first mounts. Inserting inherited (the block's default) and switching to a custom query afterwards, once the block has settled, avoids the race — this already matches the pattern used elsewhere in the suite (see queryBlock.setAsCustom()) and sidesteps a WordPress core bug rather than working around it. Fixes the last of the 11 WP 7.0/7.1 failures tracked in #38. Co-Authored-By: Claude Sonnet 5 --- tests/e2e/query-presets.spec.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/e2e/query-presets.spec.js b/tests/e2e/query-presets.spec.js index ac86d89..c3ae086 100644 --- a/tests/e2e/query-presets.spec.js +++ b/tests/e2e/query-presets.spec.js @@ -165,10 +165,16 @@ test.describe( 'Query Presets', () => { title: 'Preset Test Page', } ); - // Insert a Query Loop block with inner blocks to bypass the pattern chooser + // Insert a Query Loop block with inner blocks to bypass the pattern + // chooser. Inserted already inherit: false, WordPress 7.x's core + // Query block inspector can crash on mount — it reads + // getPostType( postType )?.labels.singular_name without guarding + // `.labels`, and that record isn't always hydrated yet the moment a + // non-inherited query first renders. Insert inheriting (the default) + // and switch to a custom query afterwards, once the block has + // settled, to avoid the race. await editor.insertBlock( { name: 'core/query', - attributes: { query: { inherit: false, perPage: 5 } }, innerBlocks: [ { name: 'core/post-template', @@ -184,6 +190,10 @@ test.describe( 'Query Presets', () => { // Open settings sidebar await blockEditor.openSettingsSidebar(); + // Switch to a custom (non-inherited) query now that the block has + // mounted and settled. + await blockEditor.queryBlock.setAsCustom(); + // Expand Extra Query Loop Settings await blockEditor.queryBlock.openSettingsPanel(); From f796f7a6b8b511254094c2232967d0cbe8676ae2 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 9 Sep 2026 19:09:24 +0100 Subject: [PATCH 4/4] CI: move WP 7.0 and 7.1 back into the blocking matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #38. The suite now passes 22/22 on WordPress 6.9.7, 7.0.4, and 7.1 — verified locally against real wp-env instances for each version, run three times per lane. The compatibility gaps tracked in #38 were all in the test suite itself (WP 7.x site editor UI changes) or a WordPress core bug unrelated to this plugin, not in plugin behavior. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/playwright-tests.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/playwright-tests.yml b/.github/workflows/playwright-tests.yml index 124f982..19be60f 100644 --- a/.github/workflows/playwright-tests.yml +++ b/.github/workflows/playwright-tests.yml @@ -50,19 +50,16 @@ jobs: # 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 } + { "label": "6.9", "core": "WordPress/WordPress#6.9.7", "comment": true }, + { "label": "7.0", "core": "WordPress/WordPress#7.0.4", "comment": true }, + { "label": "7.1", "core": "WordPress/WordPress#7.1", "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. + # nightly tracks trunk for early warning only: a nightly-only + # regression isn't yet released, so it shouldn't block a PR. 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