From 7dc16e390bce6f4a907512ddc8f9bc5801086d30 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 23 Sep 2026 16:27:27 +0800 Subject: [PATCH 1/9] Tests: Improve Reliability --- .../elementor/ElementorBroadcastsCest.php | 8 +++++++ .../elementor/ElementorFormCest.php | 4 ++++ .../elementor/ElementorFormTriggerCest.php | 4 ++++ .../ElementorMemberContentLoginCest.php | 4 ++++ .../elementor/ElementorProductCest.php | 4 ++++ tests/Support/Helper/Divi5Theme.php | 10 ++++++++- tests/Support/Helper/ThirdPartyPlugin.php | 22 ++++++++++++++----- tests/Support/Helper/WPWidget.php | 5 +++++ 8 files changed, 55 insertions(+), 6 deletions(-) diff --git a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php index c429cde3f..19717f2e7 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php @@ -44,6 +44,10 @@ public function testBroadcastsWidgetIsRegistered(EndToEndTester $I) // Click Edit with Elementor button. $I->click('#elementor-switch-mode-button'); + // Wait for Elementor to load, as its loading overlay covers the widget panel, + // which makes the panel's search field not interactable. + $I->waitForElementNotVisible('#elementor-loading'); + // Search for the Kit Broadcasts block. $I->waitForElementVisible('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Broadcasts'); @@ -70,6 +74,10 @@ public function testBroadcastsWidgetConditionalFields(EndToEndTester $I) // Click Edit with Elementor button. $I->click('#elementor-switch-mode-button'); + // Wait for Elementor to load, as its loading overlay covers the widget panel, + // which makes the panel's search field not interactable. + $I->waitForElementNotVisible('#elementor-loading'); + // Search for the Kit Broadcasts block. $I->waitForElementVisible('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Broadcasts'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php index 405cccc2e..ee584ec0f 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php @@ -46,6 +46,10 @@ public function testFormWidgetIsRegistered(EndToEndTester $I) // Click Edit with Elementor button. $I->click('#elementor-switch-mode-button'); + // Wait for Elementor to load, as its loading overlay covers the widget panel, + // which makes the panel's search field not interactable. + $I->waitForElementNotVisible('#elementor-loading'); + // Search for the Kit Form block. $I->waitForElementVisible('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Form'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php index 05fa479d5..fc15b3630 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php @@ -44,6 +44,10 @@ public function testFormTriggerWidgetIsRegistered(EndToEndTester $I) // Click Edit with Elementor button. $I->click('#elementor-switch-mode-button'); + // Wait for Elementor to load, as its loading overlay covers the widget panel, + // which makes the panel's search field not interactable. + $I->waitForElementNotVisible('#elementor-loading'); + // Search for the Kit Form Trigger block. $I->waitForElementVisible('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Form Trigger'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php index fa6c73c80..926676e0d 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php @@ -44,6 +44,10 @@ public function testMemberContentLoginWidgetIsRegistered(EndToEndTester $I) // Click Edit with Elementor button. $I->click('#elementor-switch-mode-button'); + // Wait for Elementor to load, as its loading overlay covers the widget panel, + // which makes the panel's search field not interactable. + $I->waitForElementNotVisible('#elementor-loading'); + // Search for the Kit Member Content Login block. $I->waitForElementVisible('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Member Content Login'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php index 920e700c4..62119453d 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php @@ -44,6 +44,10 @@ public function testProductWidgetIsRegistered(EndToEndTester $I) // Click Edit with Elementor button. $I->click('#elementor-switch-mode-button'); + // Wait for Elementor to load, as its loading overlay covers the widget panel, + // which makes the panel's search field not interactable. + $I->waitForElementNotVisible('#elementor-loading'); + // Search for the Kit Product block. $I->waitForElementVisible('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Product'); diff --git a/tests/Support/Helper/Divi5Theme.php b/tests/Support/Helper/Divi5Theme.php index 78cd4fe53..43add45ef 100644 --- a/tests/Support/Helper/Divi5Theme.php +++ b/tests/Support/Helper/Divi5Theme.php @@ -38,7 +38,14 @@ public function createDivi5Page($I, $title) ); // Edit Page. - $I->amOnPage('/wp-admin/post.php?post=' . $pageID . '&action=edit'); + // Chrome waits for the load event before returning, which external resources on the + // editor screen (such as Divi's Google Fonts stylesheet) can delay indefinitely. + try { + $I->amOnPage('/wp-admin/post.php?post=' . $pageID . '&action=edit'); + } catch (\Facebook\WebDriver\Exception\TimeoutException $e) { + // Stop loading the outstanding resources, as the editor has rendered by this point. + $I->executeJS('window.stop();'); + } // Switch to the Gutenberg IFrame. if ($I->isGutenbergIFrameEditorEnabled()) { @@ -46,6 +53,7 @@ public function createDivi5Page($I, $title) } // Click "Use The Divi Builder" button. + $I->waitForElementVisible('#et-switch-to-divi'); $I->click('#et-switch-to-divi'); // Switch back to main window. diff --git a/tests/Support/Helper/ThirdPartyPlugin.php b/tests/Support/Helper/ThirdPartyPlugin.php index 93c91205e..adc1d7188 100644 --- a/tests/Support/Helper/ThirdPartyPlugin.php +++ b/tests/Support/Helper/ThirdPartyPlugin.php @@ -192,12 +192,24 @@ public function doLoginAsAdmin($I) $I->waitForElementVisible('#wp-submit'); $I->waitForElementVisible('#backtoblog'); - // Fill in the login form. - $I->click('#user_login'); - $I->fillField('#user_login', $_ENV['WORDPRESS_ADMIN_USER']); + // WordPress' login screen focuses and selects the username field 200ms after it loads. + // Wait for that to happen before typing, otherwise focus is stolen part way through + // completing the form, and the password is typed over the selected username. + $I->wait(1); + + // Fill in the login form, retrying if focus was stolen while typing. + for ($attempt = 0; $attempt < 3; $attempt++) { + $I->fillField('#user_login', $_ENV['WORDPRESS_ADMIN_USER']); + $I->fillField('#user_pass', $_ENV['WORDPRESS_ADMIN_PASSWORD']); + + if ($I->grabValueFrom('#user_login') === $_ENV['WORDPRESS_ADMIN_USER'] + && $I->grabValueFrom('#user_pass') === $_ENV['WORDPRESS_ADMIN_PASSWORD']) { + break; + } + } + + // Confirm the login form is completed. $I->seeInField('#user_login', $_ENV['WORDPRESS_ADMIN_USER']); - $I->click('#user_pass'); - $I->fillField('#user_pass', $_ENV['WORDPRESS_ADMIN_PASSWORD']); $I->seeInField('#user_pass', $_ENV['WORDPRESS_ADMIN_PASSWORD']); // Submit. diff --git a/tests/Support/Helper/WPWidget.php b/tests/Support/Helper/WPWidget.php index ef38eb353..2a1885bf9 100644 --- a/tests/Support/Helper/WPWidget.php +++ b/tests/Support/Helper/WPWidget.php @@ -115,6 +115,11 @@ public function addBlockWidget($I, $blockName, $blockProgrammaticName, $blockCon $I->waitForElementVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); $I->seeElementInDOM('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); $I->fillField('.block-editor-inserter__menu input[type=search]', $blockName); + + // Let WordPress load any matching block patterns, which reloads the DOM elements. + // If we don't do this, we get stale reference errors when trying to click a block to insert. + $I->wait(2); + $I->waitForElementVisible('.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName); $I->seeElementInDOM('.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName); $I->click('.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName); From 43e92896801ee043c3bce60925e0861e6e1eb23a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 23 Sep 2026 17:45:42 +0800 Subject: [PATCH 2/9] Fix tests --- tests/Support/Helper/WPGutenberg.php | 9 +++++++++ tests/Support/Helper/WPWidget.php | 23 +++++++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/tests/Support/Helper/WPGutenberg.php b/tests/Support/Helper/WPGutenberg.php index a4d9d4b2a..44dbe59ef 100644 --- a/tests/Support/Helper/WPGutenberg.php +++ b/tests/Support/Helper/WPGutenberg.php @@ -162,6 +162,9 @@ public function addGutenbergBlock($I, $blockName, $blockProgrammaticName, $block // Close block inserter. $this->clickAddGutenbergBlockButton($I); + // Wait for the block inserter to close. + $I->waitForElementNotVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); + // If a Block configuration is specified, apply it to the Block now. if ($blockConfiguration) { $I->waitForElementVisible('.interface-interface-skeleton__sidebar[aria-label="Editor settings"]'); @@ -451,6 +454,9 @@ public function seeGutenbergBlockAvailable($I, $blockName, $blockProgrammaticNam // Close block inserter. $this->clickAddGutenbergBlockButton($I); + + // Wait for the block inserter to close. + $I->waitForElementNotVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); } /** @@ -484,6 +490,9 @@ public function dontSeeGutenbergBlockAvailable($I, $blockName, $blockProgrammati // Close block inserter. $this->clickAddGutenbergBlockButton($I); + + // Wait for the block inserter to close. + $I->waitForElementNotVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); } /** diff --git a/tests/Support/Helper/WPWidget.php b/tests/Support/Helper/WPWidget.php index 2a1885bf9..e4b8acdf9 100644 --- a/tests/Support/Helper/WPWidget.php +++ b/tests/Support/Helper/WPWidget.php @@ -172,23 +172,30 @@ public function addBlockWidget($I, $blockName, $blockProgrammaticName, $blockCon } // Save. - $I->click('Update'); - - // Wait for save to complete. - $I->waitForElementVisible('.components-snackbar__content'); - $result = $I->grabTextFrom('.components-snackbar__content'); - // Sometimes, WordPress throws an intermittent "There was an error. The response is not a valid JSON response." // when saving Widgets in WordPress 5.8+ using the block editor // It's not clear why - see https://wordpress.org/support/topic/widget-config-json-error/, https://wordpress.org/support/topic/there-was-an-error-the-response-is-not-a-valid-json-response/ // If this happens, attempt to save again after a couple of seconds. - if ($result !== 'Widgets saved.') { - $I->wait(5); + $result = ''; + for ($attempt = 0; $attempt < 3; $attempt++) { $I->click('Update'); // Wait for save to complete. $I->waitForElementVisible('.components-snackbar__content'); + $result = $I->grabTextFrom('.components-snackbar__content'); + + if ($result === 'Widgets saved.') { + break; + } + + // Wait for this notice to clear, so the next attempt doesn't read it again. + $I->waitForElementNotVisible('.components-snackbar__content', 15); + $I->wait(5); } + + // Confirm the widgets saved, so a failed save is reported here, and not as + // missing output when viewing the frontend site. + $I->assertEquals('Widgets saved.', $result); } /** From 9ec1097b519da9e272cb4c69faaddb2ddc818be6 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 23 Sep 2026 17:54:16 +0800 Subject: [PATCH 3/9] Fix Elementor Tests --- .../EndToEnd/integrations/elementor/ElementorBroadcastsCest.php | 2 ++ tests/EndToEnd/integrations/elementor/ElementorFormCest.php | 1 + .../integrations/elementor/ElementorFormTriggerCest.php | 1 + .../integrations/elementor/ElementorMemberContentLoginCest.php | 1 + tests/EndToEnd/integrations/elementor/ElementorProductCest.php | 1 + 5 files changed, 6 insertions(+) diff --git a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php index 19717f2e7..5261b9213 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php @@ -46,6 +46,7 @@ public function testBroadcastsWidgetIsRegistered(EndToEndTester $I) // Wait for Elementor to load, as its loading overlay covers the widget panel, // which makes the panel's search field not interactable. + $I->waitForElementVisible('#elementor-preview-iframe'); $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Broadcasts block. @@ -76,6 +77,7 @@ public function testBroadcastsWidgetConditionalFields(EndToEndTester $I) // Wait for Elementor to load, as its loading overlay covers the widget panel, // which makes the panel's search field not interactable. + $I->waitForElementVisible('#elementor-preview-iframe'); $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Broadcasts block. diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php index ee584ec0f..7cfceae9a 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php @@ -48,6 +48,7 @@ public function testFormWidgetIsRegistered(EndToEndTester $I) // Wait for Elementor to load, as its loading overlay covers the widget panel, // which makes the panel's search field not interactable. + $I->waitForElementVisible('#elementor-preview-iframe'); $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Form block. diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php index fc15b3630..5fbe0ae93 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php @@ -46,6 +46,7 @@ public function testFormTriggerWidgetIsRegistered(EndToEndTester $I) // Wait for Elementor to load, as its loading overlay covers the widget panel, // which makes the panel's search field not interactable. + $I->waitForElementVisible('#elementor-preview-iframe'); $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Form Trigger block. diff --git a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php index 926676e0d..cf7e11e73 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php @@ -46,6 +46,7 @@ public function testMemberContentLoginWidgetIsRegistered(EndToEndTester $I) // Wait for Elementor to load, as its loading overlay covers the widget panel, // which makes the panel's search field not interactable. + $I->waitForElementVisible('#elementor-preview-iframe'); $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Member Content Login block. diff --git a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php index 62119453d..879946898 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php @@ -46,6 +46,7 @@ public function testProductWidgetIsRegistered(EndToEndTester $I) // Wait for Elementor to load, as its loading overlay covers the widget panel, // which makes the panel's search field not interactable. + $I->waitForElementVisible('#elementor-preview-iframe'); $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Product block. From 240c6a481258a3cee524bc3846c5ec5d63efe517 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 23 Sep 2026 17:57:06 +0800 Subject: [PATCH 4/9] Fix block tests --- tests/Support/Helper/WPGutenberg.php | 3 +++ tests/Support/Helper/WPWidget.php | 2 ++ 2 files changed, 5 insertions(+) diff --git a/tests/Support/Helper/WPGutenberg.php b/tests/Support/Helper/WPGutenberg.php index 44dbe59ef..5ce35f9d0 100644 --- a/tests/Support/Helper/WPGutenberg.php +++ b/tests/Support/Helper/WPGutenberg.php @@ -117,6 +117,7 @@ public function addGutenbergBlock($I, $blockName, $blockProgrammaticName, $block // When the Blocks sidebar appears, search for the block. $I->waitForElementVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); $I->seeElementInDOM('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); + $I->waitForElementClickable('.block-editor-inserter__menu input[type=search]'); $I->fillField('.block-editor-inserter__menu input[type=search]', $blockName); // Let WordPress load any matching block patterns, which reloads the DOM elements. @@ -397,6 +398,7 @@ public function seeGutenbergBlockAvailable($I, $blockName, $blockProgrammaticNam // When the Blocks sidebar appears, search for the block. $I->waitForElementVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); $I->seeElementInDOM('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); + $I->waitForElementClickable('.block-editor-inserter__menu input[type=search]'); $I->fillField('.block-editor-inserter__menu input[type=search]', $blockName); // Let WordPress load any matching block patterns, which reloads the DOM elements. @@ -476,6 +478,7 @@ public function dontSeeGutenbergBlockAvailable($I, $blockName, $blockProgrammati // When the Blocks sidebar appears, search for the block. $I->waitForElementVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); $I->seeElementInDOM('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); + $I->waitForElementClickable('.block-editor-inserter__menu input[type=search]'); $I->fillField('.block-editor-inserter__menu input[type=search]', $blockName); // Let WordPress load any matching block patterns, which reloads the DOM elements. diff --git a/tests/Support/Helper/WPWidget.php b/tests/Support/Helper/WPWidget.php index e4b8acdf9..1e0115405 100644 --- a/tests/Support/Helper/WPWidget.php +++ b/tests/Support/Helper/WPWidget.php @@ -29,6 +29,7 @@ public function addLegacyWidget($I, $blockName, $blockProgrammaticName, $blockCo // When the Blocks sidebar appears, search for the legacy widget. $I->waitForElementVisible('.interface-interface-skeleton__secondary-sidebar'); + $I->waitForElementClickable('.block-editor-inserter__menu input[type=search]'); $I->fillField('.block-editor-inserter__menu input[type=search]', $blockName); // First matching item will be the legacy widget; any blocks will follow. @@ -114,6 +115,7 @@ public function addBlockWidget($I, $blockName, $blockProgrammaticName, $blockCon // When the Blocks sidebar appears, search for the block. $I->waitForElementVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); $I->seeElementInDOM('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); + $I->waitForElementClickable('.block-editor-inserter__menu input[type=search]'); $I->fillField('.block-editor-inserter__menu input[type=search]', $blockName); // Let WordPress load any matching block patterns, which reloads the DOM elements. From df46594c2b862576b5d0f88d5b537dbd5240af77 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 23 Sep 2026 19:16:37 +0800 Subject: [PATCH 5/9] Improve test reliability --- .../BroadcastsToPostsSettingsCest.php | 9 +++ .../other/ActivateDeactivatePluginCest.php | 3 + .../general/other/LegacyFormDropdownCest.php | 3 + .../general/other/ReviewRequestCest.php | 6 ++ .../PluginSettingsGeneralCest.php | 63 +++++++++++++++++++ .../plugin-screens/PluginSettingsMCPCest.php | 9 +++ .../elementor/ElementorBroadcastsCest.php | 4 +- .../elementor/ElementorFormCest.php | 2 +- .../elementor/ElementorFormTriggerCest.php | 2 +- .../ElementorMemberContentLoginCest.php | 2 +- .../elementor/ElementorProductCest.php | 2 +- .../other/ContactForm7FormCest.php | 6 ++ .../integrations/other/ForminatorCest.php | 9 +++ .../general/RestrictContentSettingsCest.php | 6 ++ tests/Support/Helper/KitAPI.php | 4 +- tests/Support/Helper/WPWidget.php | 29 +++++---- 16 files changed, 136 insertions(+), 23 deletions(-) diff --git a/tests/EndToEnd/broadcasts/import-export/BroadcastsToPostsSettingsCest.php b/tests/EndToEnd/broadcasts/import-export/BroadcastsToPostsSettingsCest.php index c295cfad3..81210d21d 100644 --- a/tests/EndToEnd/broadcasts/import-export/BroadcastsToPostsSettingsCest.php +++ b/tests/EndToEnd/broadcasts/import-export/BroadcastsToPostsSettingsCest.php @@ -76,6 +76,9 @@ public function testEnableDisableImport(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -102,6 +105,9 @@ public function testEnableDisableImport(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -145,6 +151,9 @@ public function testSaveSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); diff --git a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php index 82760c62a..c143d1632 100644 --- a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php +++ b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php @@ -53,6 +53,9 @@ public function testPluginActivationAndDeactivationWithOtherPlugins(EndToEndTest // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); diff --git a/tests/EndToEnd/general/other/LegacyFormDropdownCest.php b/tests/EndToEnd/general/other/LegacyFormDropdownCest.php index ccf7047f5..05a768fed 100644 --- a/tests/EndToEnd/general/other/LegacyFormDropdownCest.php +++ b/tests/EndToEnd/general/other/LegacyFormDropdownCest.php @@ -139,6 +139,9 @@ public function testWarningDisplayedForLegacyDefaultFormsSetting(EndToEndTester ); $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Confirm the warning is no longer displayed. $I->waitForElementNotVisible('#convertkit-legacy-settings-warning'); } diff --git a/tests/EndToEnd/general/other/ReviewRequestCest.php b/tests/EndToEnd/general/other/ReviewRequestCest.php index 8f34351c3..d64cdd503 100644 --- a/tests/EndToEnd/general/other/ReviewRequestCest.php +++ b/tests/EndToEnd/general/other/ReviewRequestCest.php @@ -54,6 +54,9 @@ public function testReviewRequestOnSaveSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -83,6 +86,9 @@ public function testReviewRequestOnSaveBlankSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); diff --git a/tests/EndToEnd/general/plugin-screens/PluginSettingsGeneralCest.php b/tests/EndToEnd/general/plugin-screens/PluginSettingsGeneralCest.php index f7c705e98..86858dc68 100644 --- a/tests/EndToEnd/general/plugin-screens/PluginSettingsGeneralCest.php +++ b/tests/EndToEnd/general/plugin-screens/PluginSettingsGeneralCest.php @@ -149,6 +149,9 @@ public function testValidCredentials(EndToEndTester $I) // Save Changes to confirm credentials are not lost. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -326,6 +329,9 @@ public function testChangeDefaultFormSettingAndPreviewFormLinks(EndToEndTester $ // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -404,6 +410,9 @@ public function testChangeDefaultSiteWideFormsSettingAndPreviewFormLinks(EndToEn // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -476,6 +485,9 @@ public function testChangeDefaultFormPositionAfterElementSetting(EndToEndTester // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -547,6 +559,9 @@ public function testPublicPrivateCustomPostTypeSettingsExist(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -638,6 +653,9 @@ public function testRecaptchaSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -654,6 +672,9 @@ public function testRecaptchaSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -704,6 +725,9 @@ public function testCloudflareTurnstileSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -718,6 +742,9 @@ public function testCloudflareTurnstileSettings(EndToEndTester $I) $I->clearField('#cloudflare_turnstile_secret_key'); $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -748,6 +775,9 @@ public function testEnableAndDisableDebugSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -760,6 +790,9 @@ public function testEnableAndDisableDebugSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -789,6 +822,9 @@ public function testEnableAndDisableJavaScriptSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -801,6 +837,9 @@ public function testEnableAndDisableJavaScriptSettings(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -831,6 +870,9 @@ public function testEnableAndDisableCSSSetting(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -852,6 +894,9 @@ public function testEnableAndDisableCSSSetting(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -888,6 +933,9 @@ public function testEnableAndDisableAddNewLandingPageMemberContentButtonSetting( // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -900,6 +948,9 @@ public function testEnableAndDisableAddNewLandingPageMemberContentButtonSetting( // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -930,6 +981,9 @@ public function testEnableAndDisableUsageTrackingSetting(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -942,6 +996,9 @@ public function testEnableAndDisableUsageTrackingSetting(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -993,6 +1050,9 @@ public function testSettingsScreenWhenNoResources(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -1011,6 +1071,9 @@ public function testSettingsScreenWhenNoResources(EndToEndTester $I) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); diff --git a/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php b/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php index 976306ba7..d41501db4 100644 --- a/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php +++ b/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php @@ -55,6 +55,9 @@ public function testEnableAndDisableMCPServerSetting(EndToEndTester $I) $I->checkOption('#enabled'); $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -70,6 +73,9 @@ public function testEnableAndDisableMCPServerSetting(EndToEndTester $I) $I->uncheckOption('#enabled'); $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -115,6 +121,9 @@ public function testGenerateAndRevokeApplicationPassword(EndToEndTester $I) $I->checkOption('#enabled'); $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); diff --git a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php index 5261b9213..58e1ad94f 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php @@ -50,7 +50,7 @@ public function testBroadcastsWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Broadcasts block. - $I->waitForElementVisible('#elementor-panel-elements-search-input'); + $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Broadcasts'); // Confirm that the Broadcasts widget is displayed as an option. @@ -81,7 +81,7 @@ public function testBroadcastsWidgetConditionalFields(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Broadcasts block. - $I->waitForElementVisible('#elementor-panel-elements-search-input'); + $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Broadcasts'); // Insert the Broadcasts widget. diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php index 7cfceae9a..57e15f575 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php @@ -52,7 +52,7 @@ public function testFormWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Form block. - $I->waitForElementVisible('#elementor-panel-elements-search-input'); + $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Form'); // Confirm that the Form widget is displayed as an option. diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php index 5fbe0ae93..9622cb2b6 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php @@ -50,7 +50,7 @@ public function testFormTriggerWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Form Trigger block. - $I->waitForElementVisible('#elementor-panel-elements-search-input'); + $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Form Trigger'); // Confirm that the Form Trigger widget is displayed as an option. diff --git a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php index cf7e11e73..091b373ba 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php @@ -50,7 +50,7 @@ public function testMemberContentLoginWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Member Content Login block. - $I->waitForElementVisible('#elementor-panel-elements-search-input'); + $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Member Content Login'); // Confirm that the Member Content Login widget is displayed as an option. diff --git a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php index 879946898..652bb9a7a 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php @@ -50,7 +50,7 @@ public function testProductWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Product block. - $I->waitForElementVisible('#elementor-panel-elements-search-input'); + $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Product'); // Confirm that the Product widget is displayed as an option. diff --git a/tests/EndToEnd/integrations/other/ContactForm7FormCest.php b/tests/EndToEnd/integrations/other/ContactForm7FormCest.php index af15beb77..d68395af4 100644 --- a/tests/EndToEnd/integrations/other/ContactForm7FormCest.php +++ b/tests/EndToEnd/integrations/other/ContactForm7FormCest.php @@ -323,6 +323,9 @@ public function testSettingsContactForm7CreatorNetworkRecommendationsWhenEnabled // Save. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -470,6 +473,9 @@ private function _contactForm7SetupForm(EndToEndTester $I, string $optionName) $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); diff --git a/tests/EndToEnd/integrations/other/ForminatorCest.php b/tests/EndToEnd/integrations/other/ForminatorCest.php index 23aa6ebfe..912da6e1d 100644 --- a/tests/EndToEnd/integrations/other/ForminatorCest.php +++ b/tests/EndToEnd/integrations/other/ForminatorCest.php @@ -533,6 +533,9 @@ public function testSettingsForminatorCreatorNetworkRecommendationsWhenEnabledOn // Save. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -830,6 +833,9 @@ private function _forminatorSetupForm(EndToEndTester $I, string $optionName) $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); @@ -879,6 +885,9 @@ private function _forminatorSetupQuiz(EndToEndTester $I, string $optionName) $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); diff --git a/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php b/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php index ca4b9626e..0f649f12c 100644 --- a/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php +++ b/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php @@ -207,6 +207,9 @@ public function testDisableCSSSetting(EndToEndTester $I) $I->checkOption('#no_css'); $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Create Restricted Content Page. $pageID = $I->createRestrictedContentPage( $I, @@ -255,6 +258,9 @@ public function _setupKitPluginRestrictContent($I, $settings = false) // Click the Save Changes button. $I->click('Save Changes'); + // Wait for the settings to save. + $I->waitForElementVisible('#setting-error-settings_updated'); + // Check that no PHP warnings or notices were output. $I->checkNoWarningsAndNoticesOnScreen($I); } diff --git a/tests/Support/Helper/KitAPI.php b/tests/Support/Helper/KitAPI.php index b331e0f28..2f3d2bc74 100644 --- a/tests/Support/Helper/KitAPI.php +++ b/tests/Support/Helper/KitAPI.php @@ -432,7 +432,7 @@ public function apiRequest($endpoint, $method = 'GET', $params = array()) 'headers' => [ 'Authorization' => 'Bearer ' . $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'], ], - 'timeout' => 5, + 'timeout' => 30, ] ); break; @@ -447,7 +447,7 @@ public function apiRequest($endpoint, $method = 'GET', $params = array()) 'Content-Type' => 'application/json; charset=utf-8', 'Authorization' => 'Bearer ' . $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'], ], - 'timeout' => 5, + 'timeout' => 30, 'body' => (string) json_encode($params), // phpcs:ignore WordPress.WP.AlternativeFunctions ] ); diff --git a/tests/Support/Helper/WPWidget.php b/tests/Support/Helper/WPWidget.php index 1e0115405..4816a7eec 100644 --- a/tests/Support/Helper/WPWidget.php +++ b/tests/Support/Helper/WPWidget.php @@ -122,10 +122,13 @@ public function addBlockWidget($I, $blockName, $blockProgrammaticName, $blockCon // If we don't do this, we get stale reference errors when trying to click a block to insert. $I->wait(2); - $I->waitForElementVisible('.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName); - $I->seeElementInDOM('.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName); + $I->waitForElementClickable('.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName); $I->click('.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName); + // Confirm the block inserted, so a failed insertion is reported here, and not as + // missing output when viewing the frontend site. + $I->waitForElementVisible('.wp-block-' . $blockProgrammaticName); + // Close block inserter. $I->click('button.edit-widgets-header-toolbar__inserter-toggle'); @@ -178,26 +181,22 @@ public function addBlockWidget($I, $blockName, $blockProgrammaticName, $blockCon // when saving Widgets in WordPress 5.8+ using the block editor // It's not clear why - see https://wordpress.org/support/topic/widget-config-json-error/, https://wordpress.org/support/topic/there-was-an-error-the-response-is-not-a-valid-json-response/ // If this happens, attempt to save again after a couple of seconds. - $result = ''; - for ($attempt = 0; $attempt < 3; $attempt++) { - $I->click('Update'); - - // Wait for save to complete. - $I->waitForElementVisible('.components-snackbar__content'); - $result = $I->grabTextFrom('.components-snackbar__content'); + $I->click('Update'); - if ($result === 'Widgets saved.') { - break; - } + // Wait for the save to complete. + $I->waitForElementVisible('.components-snackbar__content'); - // Wait for this notice to clear, so the next attempt doesn't read it again. + // If this happens, attempt to save again. + if ($I->grabTextFrom('.components-snackbar__content') !== 'Widgets saved.') { + // Wait for this notice to clear, so we read the next save's notice and not this one. $I->waitForElementNotVisible('.components-snackbar__content', 15); - $I->wait(5); + $I->click('Update'); + $I->waitForElementVisible('.components-snackbar__content'); } // Confirm the widgets saved, so a failed save is reported here, and not as // missing output when viewing the frontend site. - $I->assertEquals('Widgets saved.', $result); + $I->see('Widgets saved.', '.components-snackbar__content'); } /** From 9af2496010b4d1e454243a2bdfd5b18402b71e0a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 24 Sep 2026 11:02:30 +0800 Subject: [PATCH 6/9] Isolate failing Elementor Test --- .github/workflows/tests.yml | 24 ++----------------- .../elementor/ElementorBroadcastsCest.php | 8 +++++++ .../elementor/ElementorFormCest.php | 4 ++++ .../elementor/ElementorFormTriggerCest.php | 4 ++++ .../ElementorMemberContentLoginCest.php | 4 ++++ .../elementor/ElementorProductCest.php | 4 ++++ 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3705919b6..fd330626b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,29 +33,9 @@ jobs: fail-fast: false matrix: wp-version: [ 'latest' ] - php-version: [ '8.2', '8.3', '8.4', '8.5' ] + php-version: [ '8.2' ] test-group: - - 'EndToEnd/broadcasts/blocks-shortcodes' - - 'EndToEnd/broadcasts/import-export' - - 'EndToEnd/forms/blocks-shortcodes' - - 'EndToEnd/forms/general' - - 'EndToEnd/forms/post-types' - - 'EndToEnd/general/other' - - 'EndToEnd/general/uninstall' - - 'EndToEnd/general/plugin-screens' - - 'EndToEnd/integrations/divi-builder' - - 'EndToEnd/integrations/divi-theme' - - 'EndToEnd/integrations/elementor' - - 'EndToEnd/integrations/other' - - 'EndToEnd/integrations/wlm' - - 'EndToEnd/integrations/woocommerce' - - 'EndToEnd/landing-pages' - - 'EndToEnd/products' - - 'EndToEnd/restrict-content/blocks-shortcodes' - - 'EndToEnd/restrict-content/general' - - 'EndToEnd/restrict-content/post-types' - - 'EndToEnd/tags' - - 'Integration' + - 'EndToEnd/integrations/elementor/ElementorFormCest' uses: ./.github/workflows/_run-tests.yml secrets: inherit diff --git a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php index 58e1ad94f..b42ff6ec5 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php @@ -50,6 +50,10 @@ public function testBroadcastsWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Broadcasts block. + // Wait for Elementor to render its widgets. It renders the widget category + // containers first, and its search field isn't interactable until the widgets + // within them have rendered. + $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Broadcasts'); @@ -81,6 +85,10 @@ public function testBroadcastsWidgetConditionalFields(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Broadcasts block. + // Wait for Elementor to render its widgets. It renders the widget category + // containers first, and its search field isn't interactable until the widgets + // within them have rendered. + $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Broadcasts'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php index 57e15f575..d5f24396d 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php @@ -52,6 +52,10 @@ public function testFormWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Form block. + // Wait for Elementor to render its widgets. It renders the widget category + // containers first, and its search field isn't interactable until the widgets + // within them have rendered. + $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Form'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php index 9622cb2b6..4193acb1c 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php @@ -50,6 +50,10 @@ public function testFormTriggerWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Form Trigger block. + // Wait for Elementor to render its widgets. It renders the widget category + // containers first, and its search field isn't interactable until the widgets + // within them have rendered. + $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Form Trigger'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php index 091b373ba..e3b8d5bf5 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php @@ -50,6 +50,10 @@ public function testMemberContentLoginWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Member Content Login block. + // Wait for Elementor to render its widgets. It renders the widget category + // containers first, and its search field isn't interactable until the widgets + // within them have rendered. + $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Member Content Login'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php index 652bb9a7a..2d20d5b8d 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php @@ -50,6 +50,10 @@ public function testProductWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Product block. + // Wait for Elementor to render its widgets. It renders the widget category + // containers first, and its search field isn't interactable until the widgets + // within them have rendered. + $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Product'); From 018e9e48231a716ada201987fd8294791f436472 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 24 Sep 2026 11:09:03 +0800 Subject: [PATCH 7/9] Run all Elementor tests --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fd330626b..c55eef550 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,7 +35,7 @@ jobs: wp-version: [ 'latest' ] php-version: [ '8.2' ] test-group: - - 'EndToEnd/integrations/elementor/ElementorFormCest' + - 'EndToEnd/integrations/elementor' uses: ./.github/workflows/_run-tests.yml secrets: inherit From deb78c9a43b792b579acdc2abf3ac94cd8cfc449 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 24 Sep 2026 11:48:20 +0800 Subject: [PATCH 8/9] Fix Tests --- .github/workflows/tests.yml | 22 ++++++++++++++++++- .../elementor/ElementorBroadcastsCest.php | 6 ----- .../elementor/ElementorFormCest.php | 3 --- .../elementor/ElementorFormTriggerCest.php | 3 --- .../ElementorMemberContentLoginCest.php | 3 --- .../elementor/ElementorProductCest.php | 3 --- tests/Support/Data/dump.sql | 4 +++- 7 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c55eef550..3705919b6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,9 +33,29 @@ jobs: fail-fast: false matrix: wp-version: [ 'latest' ] - php-version: [ '8.2' ] + php-version: [ '8.2', '8.3', '8.4', '8.5' ] test-group: + - 'EndToEnd/broadcasts/blocks-shortcodes' + - 'EndToEnd/broadcasts/import-export' + - 'EndToEnd/forms/blocks-shortcodes' + - 'EndToEnd/forms/general' + - 'EndToEnd/forms/post-types' + - 'EndToEnd/general/other' + - 'EndToEnd/general/uninstall' + - 'EndToEnd/general/plugin-screens' + - 'EndToEnd/integrations/divi-builder' + - 'EndToEnd/integrations/divi-theme' - 'EndToEnd/integrations/elementor' + - 'EndToEnd/integrations/other' + - 'EndToEnd/integrations/wlm' + - 'EndToEnd/integrations/woocommerce' + - 'EndToEnd/landing-pages' + - 'EndToEnd/products' + - 'EndToEnd/restrict-content/blocks-shortcodes' + - 'EndToEnd/restrict-content/general' + - 'EndToEnd/restrict-content/post-types' + - 'EndToEnd/tags' + - 'Integration' uses: ./.github/workflows/_run-tests.yml secrets: inherit diff --git a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php index b42ff6ec5..463595a40 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorBroadcastsCest.php @@ -50,9 +50,6 @@ public function testBroadcastsWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Broadcasts block. - // Wait for Elementor to render its widgets. It renders the widget category - // containers first, and its search field isn't interactable until the widgets - // within them have rendered. $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Broadcasts'); @@ -85,9 +82,6 @@ public function testBroadcastsWidgetConditionalFields(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Broadcasts block. - // Wait for Elementor to render its widgets. It renders the widget category - // containers first, and its search field isn't interactable until the widgets - // within them have rendered. $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Broadcasts'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php index d5f24396d..3dd870992 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormCest.php @@ -52,9 +52,6 @@ public function testFormWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Form block. - // Wait for Elementor to render its widgets. It renders the widget category - // containers first, and its search field isn't interactable until the widgets - // within them have rendered. $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Form'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php index 4193acb1c..0fb13bb0c 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorFormTriggerCest.php @@ -50,9 +50,6 @@ public function testFormTriggerWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Form Trigger block. - // Wait for Elementor to render its widgets. It renders the widget category - // containers first, and its search field isn't interactable until the widgets - // within them have rendered. $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Form Trigger'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php index e3b8d5bf5..1af51a480 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorMemberContentLoginCest.php @@ -50,9 +50,6 @@ public function testMemberContentLoginWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Member Content Login block. - // Wait for Elementor to render its widgets. It renders the widget category - // containers first, and its search field isn't interactable until the widgets - // within them have rendered. $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Member Content Login'); diff --git a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php index 2d20d5b8d..f29b59a0b 100644 --- a/tests/EndToEnd/integrations/elementor/ElementorProductCest.php +++ b/tests/EndToEnd/integrations/elementor/ElementorProductCest.php @@ -50,9 +50,6 @@ public function testProductWidgetIsRegistered(EndToEndTester $I) $I->waitForElementNotVisible('#elementor-loading'); // Search for the Kit Product block. - // Wait for Elementor to render its widgets. It renders the widget category - // containers first, and its search field isn't interactable until the widgets - // within them have rendered. $I->waitForElementVisible('#elementor-panel-page-elements .elementor-element'); $I->waitForElementClickable('#elementor-panel-elements-search-input'); $I->fillField('#elementor-panel-elements-search-input', 'Kit Product'); diff --git a/tests/Support/Data/dump.sql b/tests/Support/Data/dump.sql index 3f24ebc81..1af2b3da8 100644 --- a/tests/Support/Data/dump.sql +++ b/tests/Support/Data/dump.sql @@ -253,7 +253,9 @@ INSERT INTO `wp_options` (`option_id`, `option_name`, `option_value`, `autoload` (128, 'WishListMemberOptions_MigrateContentLevelData', '1', 'on'), (129, 'et_support_center_setup_done', 'processed', 'on'), (130, 'et_support_center_installed', 'true', 'on'), -(131, 'woocommerce_version', '9.7.1', 'on'); +(131, 'woocommerce_version', '9.7.1', 'on'), +(132, 'elementor_angie_guide_auto_shown', 'yes', 'on'), +(133, 'elementor_checklist', '{"last_opened_timestamp":1700000000,"first_closed_checklist_in_editor":true,"is_popup_minimized":true,"steps":[]}', 'on'); DROP TABLE IF EXISTS `wp_postmeta`; CREATE TABLE `wp_postmeta` ( From fe83249c812cdc0812f8e323e227806c98d2176b Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 24 Sep 2026 14:01:17 +0800 Subject: [PATCH 9/9] Improve flaky tests --- .../general/plugin-screens/PluginSettingsGeneralCest.php | 4 ++++ tests/Support/Helper/ThirdPartyPlugin.php | 4 +++- tests/Support/Helper/WPClassicEditor.php | 3 +++ tests/Support/Helper/WPWidget.php | 8 ++++---- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/EndToEnd/general/plugin-screens/PluginSettingsGeneralCest.php b/tests/EndToEnd/general/plugin-screens/PluginSettingsGeneralCest.php index 86858dc68..b02c7a762 100644 --- a/tests/EndToEnd/general/plugin-screens/PluginSettingsGeneralCest.php +++ b/tests/EndToEnd/general/plugin-screens/PluginSettingsGeneralCest.php @@ -273,6 +273,7 @@ public function testChangeDefaultFormSettingAndPreviewFormLinks(EndToEndTester $ // Setup Plugin, without defining default Forms. $I->setupKitPluginNoDefaultForms($I); + $I->setupKitPluginResources($I); // Go to the Plugin's Settings Screen. $I->loadKitSettingsGeneralScreen($I); @@ -355,6 +356,7 @@ public function testChangeDefaultSiteWideFormsSettingAndPreviewFormLinks(EndToEn { // Setup Plugin, without defining default Forms. $I->setupKitPluginNoDefaultForms($I); + $I->setupKitPluginResources($I); // Go to the Plugin's Settings Screen. $I->loadKitSettingsGeneralScreen($I); @@ -440,6 +442,7 @@ public function testChangeDefaultFormPositionAfterElementSetting(EndToEndTester { // Setup Plugin, without defining default Forms. $I->setupKitPluginNoDefaultForms($I); + $I->setupKitPluginResources($I); // Go to the Plugin's Settings Screen. $I->loadKitSettingsGeneralScreen($I); @@ -542,6 +545,7 @@ public function testPublicPrivateCustomPostTypeSettingsExist(EndToEndTester $I) // Setup Plugin, without defining default Forms. $I->setupKitPluginNoDefaultForms($I); + $I->setupKitPluginResources($I); // Go to the Plugin's Settings Screen. $I->loadKitSettingsGeneralScreen($I); diff --git a/tests/Support/Helper/ThirdPartyPlugin.php b/tests/Support/Helper/ThirdPartyPlugin.php index adc1d7188..45a5ee498 100644 --- a/tests/Support/Helper/ThirdPartyPlugin.php +++ b/tests/Support/Helper/ThirdPartyPlugin.php @@ -53,8 +53,10 @@ public function activateThirdPartyPlugin($I, $name, $wizardExpectsToDisplay = tr switch ($name) { case 'convertkit': // Wait for the Plugin Setup Wizard screen to load, if it's expected to display. + // The Setup Wizard's first screen builds an OAuth URL using the API, so allow + // longer than the default timeout for it to render. if ( $wizardExpectsToDisplay ) { - $I->waitForElementVisible('body.convertkit'); + $I->waitForElementVisible('body.convertkit', 30); } break; diff --git a/tests/Support/Helper/WPClassicEditor.php b/tests/Support/Helper/WPClassicEditor.php index c8a1aca82..1509cb95e 100644 --- a/tests/Support/Helper/WPClassicEditor.php +++ b/tests/Support/Helper/WPClassicEditor.php @@ -134,6 +134,9 @@ public function openVisualEditorShortcodeModal($I, $shortcodeName, $targetEditor // Click the Visual tab on the applicable TinyMCE editor. $I->click('button#' . $targetEditor . '-tmce'); + // Wait for TinyMCE to initialise, as its toolbar isn't in the DOM until it has. + $I->waitForElementVisible('#wp-' . $targetEditor . '-editor-container div.mce-container div[aria-label="' . $shortcodeName . '"] button'); + // Click the TinyMCE Button for this shortcode. $I->click('#wp-' . $targetEditor . '-editor-container div.mce-container div[aria-label="' . $shortcodeName . '"] button'); diff --git a/tests/Support/Helper/WPWidget.php b/tests/Support/Helper/WPWidget.php index 4816a7eec..cde22a97b 100644 --- a/tests/Support/Helper/WPWidget.php +++ b/tests/Support/Helper/WPWidget.php @@ -125,12 +125,12 @@ public function addBlockWidget($I, $blockName, $blockProgrammaticName, $blockCon $I->waitForElementClickable('.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName); $I->click('.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName); - // Confirm the block inserted, so a failed insertion is reported here, and not as - // missing output when viewing the frontend site. - $I->waitForElementVisible('.wp-block-' . $blockProgrammaticName); - // Close block inserter. $I->click('button.edit-widgets-header-toolbar__inserter-toggle'); + $I->waitForElementNotVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); + + // Confirm the block inserted. + $I->waitForElementVisible('.interface-interface-skeleton__content .wp-block-' . $blockProgrammaticName); // If a Block configuration is specified, apply it to the Block now. if ($blockConfiguration) {