From 8a55c1a0c27425afb05c51b951e7d889f42afcd5 Mon Sep 17 00:00:00 2001
From: Adam G-H <32250-phenaproxima@users.noreply.drupalcode.org>
Date: Fri, 21 Feb 2025 19:42:46 +0000
Subject: [PATCH] Issue #3507922 by phenaproxima, tim.plunkett: Add
 assertPageHasText() as a more assertive waitForText()

---
 .../ProjectBrowserExamplePluginTest.php       |  2 +-
 .../ProjectBrowserInstallerUiTest.php         | 11 ++---
 .../ProjectBrowserPluginTest.php              |  8 ++--
 .../ProjectBrowserUiTest.php                  | 42 ++++++++++---------
 .../ProjectBrowserUiTestJsonApi.php           | 17 ++++----
 .../ProjectBrowserUiTestTrait.php             | 27 ++++++------
 6 files changed, 50 insertions(+), 57 deletions(-)

diff --git a/tests/src/FunctionalJavascript/ProjectBrowserExamplePluginTest.php b/tests/src/FunctionalJavascript/ProjectBrowserExamplePluginTest.php
index 29c01642e..280858eff 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserExamplePluginTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserExamplePluginTest.php
@@ -52,7 +52,7 @@ class ProjectBrowserExamplePluginTest extends WebDriverTestBase {
     $this->svelteInitHelper('css', '#project-browser .pb-project--grid');
     $this->assertEquals('Grid', $this->getElementText('#project-browser .pb-display__button[value="Grid"]'));
     $this->assertElementIsVisible('css', '#project-browser .pb-project');
-    $this->assertTrue($assert_session->waitForText('Project 1'));
+    $this->assertPageHasText('Project 1');
     $assert_session->pageTextNotContains('No modules found');
     $this->svelteInitHelper('css', '.pb-filter__checkbox');
     $assert_session->elementsCount('css', '.pb-filter__checkbox', 2);
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
index eaf989a41..f06182970 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php
@@ -175,8 +175,6 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
    * @covers ::unlock
    */
   public function testCanBreakStageWithMissingProjectBrowserLock(): void {
-    $assert_session = $this->assertSession();
-
     // Start install begin.
     $this->drupalGet('admin/modules/project_browser/install-begin', [
       'query' => ['source' => 'project_browser_test_mock'],
@@ -188,7 +186,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     $this->waitForProject('Cream cheese on a bagel')
       ->pressButton('Install Cream cheese on a bagel');
 
-    $this->assertTrue($assert_session->waitForText('The process for adding projects is locked, but that lock has expired. Use unlock link to unlock the process and try to add the project again.'));
+    $this->assertPageHasText('The process for adding projects is locked, but that lock has expired. Use unlock link to unlock the process and try to add the project again.');
 
     // Click Unlock Install Stage link.
     $this->clickWithWait('#ui-id-1 > p > a');
@@ -204,8 +202,6 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
    * @covers ::unlock
    */
   public function testCanBreakLock(): void {
-    $assert_session = $this->assertSession();
-
     // Start install begin.
     $this->drupalGet('admin/modules/project_browser/install-begin', [
       'query' => ['source' => 'project_browser_test_mock'],
@@ -215,7 +211,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     // the applying stage.
     $this->waitForProject('Cream cheese on a bagel')
       ->pressButton('Install Cream cheese on a bagel');
-    $this->assertTrue($assert_session->waitForText('The process for adding projects is locked, but that lock has expired. Use unlock link to unlock the process and try to add the project again.'));
+    $this->assertPageHasText('The process for adding projects is locked, but that lock has expired. Use unlock link to unlock the process and try to add the project again.');
     // Click Unlock Install Stage link.
     $this->clickWithWait('#ui-id-1 > p > a');
     // Try beginning another install after breaking lock.
@@ -230,11 +226,10 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase {
     $this->container->get(StateInterface::class)
       ->set('project_browser_test.simulated_result_severity', SystemManager::REQUIREMENT_ERROR);
 
-    $assert_session = $this->assertSession();
     $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $cream_cheese = $this->waitForProject('Cream cheese on a bagel');
     $cream_cheese->pressButton('Install Cream cheese on a bagel');
-    $this->assertTrue($assert_session->waitForText('Simulate an error message for the project browser.'));
+    $this->assertPageHasText('Simulate an error message for the project browser.');
     $this->assertTrue($cream_cheese->hasButton('Install Cream cheese on a bagel'));
   }
 
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php b/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php
index d0ea21375..4e0221e44 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php
@@ -60,7 +60,7 @@ class ProjectBrowserPluginTest extends WebDriverTestBase {
     $this->svelteInitHelper('css', '#project-browser .pb-project--grid');
     $this->assertEquals('Grid', $this->getElementText('#project-browser .pb-display__button[value="Grid"]'));
     $this->assertElementIsVisible('css', '#project-browser .pb-project');
-    $this->assertTrue($assert_session->waitForText('Results'));
+    $this->assertPageHasText('Results');
     $assert_session->pageTextNotContains('No modules found');
   }
 
@@ -154,15 +154,13 @@ class ProjectBrowserPluginTest extends WebDriverTestBase {
    * Tests the detail page.
    */
   public function testDetailPageRandomDataPlugin(): void {
-    $assert_session = $this->assertSession();
-
     $this->drupalGet('admin/modules/browse/random_data');
     $this->assertElementIsVisible('css', '#project-browser .pb-project');
-    $this->assertTrue($assert_session->waitForText('Results'));
+    $this->assertPageHasText('Results');
 
     $this->assertElementIsVisible('css', '.pb-project .pb-project__title .pb-project__link')
       ->click();
-    $this->assertTrue($assert_session->waitForText('sites report using this module'));
+    $this->assertPageHasText('sites report using this module');
   }
 
 }
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
index 5966812df..c79c0cc5a 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\project_browser\FunctionalJavascript;
 
+use Behat\Mink\Element\DocumentElement;
 use Behat\Mink\Element\NodeElement;
 use Drupal\Core\Extension\MissingDependencyException;
 use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
@@ -72,7 +73,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->assertNotEmpty($assert_session->waitForButton('Grid'));
     $this->svelteInitHelper('text', '10 Results');
     $assert_session->elementsCount('css', '#project-browser .pb-project.pb-project--grid', 10);
-    $this->assertTrue($assert_session->waitForText('Results'));
+    $this->assertPageHasText('Results');
     $assert_session->pageTextNotContains('No modules found');
     $page->pressButton('List');
     $this->assertElementIsVisible('css', '#project-browser .pb-project.pb-project--list');
@@ -121,7 +122,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-filter__multi-dropdown');
     // Initial results count on page load.
-    $this->assertTrue($assert_session->waitForText('10 Results'));
+    $this->assertPageHasText('10 Results');
     // Open category drop-down.
     $this->clickWithWait('.pb-filter__multi-dropdown', 'E-commerce', TRUE);
 
@@ -192,7 +193,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
       'Kangaroo',
       '9 Starts With a Higher Number',
     ]);
-    $this->assertTrue($assert_session->waitForText('20 Results'));
+    $this->assertPageHasText('20 Results');
   }
 
   /**
@@ -259,7 +260,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->assertPagerItems([]);
 
     $page->pressButton('Clear filters');
-    $this->assertTrue($assert_session->waitForText('25 Results'));
+    $this->assertPageHasText('25 Results');
     $this->assertProjectsVisible([
       'Jazz',
       'Eggman',
@@ -327,17 +328,19 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    */
   public function testPagingOptions(): void {
     $page = $this->getSession()->getPage();
-    $assert_session = $this->assertSession();
 
+    $await_n_projects = function (int $count) use ($page): void {
+      $this->assertTrue($page->waitFor(
+        10,
+        fn (DocumentElement $page) => count($page->findAll('css', '#project-browser .pb-project.pb-project--list')) === $count,
+      ));
+    };
     $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('css', '.pb-project.pb-project--list');
     $this->pressWithWait('Clear filters');
-    $assert_session->waitForText('Modules per page');
-    $assert_session->elementsCount('css', '#project-browser .pb-project.pb-project--list', 12);
-    $assert_session->waitForText('Modules per page');
+    $await_n_projects(12);
     $page->selectFieldOption('num-projects', '24');
-    $this->assertElementIsVisible('css', '#project-browser .pb-project.pb-project--list');
-    $assert_session->elementsCount('css', '#project-browser .pb-project.pb-project--list', 24);
+    $await_n_projects(24);
   }
 
   /**
@@ -664,7 +667,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->assertElementIsVisible('css', '#67');
     $this->clickWithWait('#67', '', TRUE);
 
-    $this->assertTrue($assert_session->waitForText('15 Results'));
+    $this->assertPageHasText('15 Results');
     $this->assertProjectsVisible([
       'Octopus',
       'No Scrubs',
@@ -694,7 +697,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
       '9 Starts With a Higher Number',
       '1 Starts With a Number',
     ]);
-    $this->assertTrue($assert_session->waitForText('15 Results'));
+    $this->assertPageHasText('15 Results');
 
     $this->assertEquals('E-commerce', $this->getElementText('p.filter-applied:first-child .filter-applied__label'));
     $this->assertEquals('Media', $this->getElementText('p.filter-applied:nth-child(2) .filter-applied__label'));
@@ -723,7 +726,6 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
    * Tests recommended filters.
    */
   public function testRecommendedFilter(): void {
-    $assert_session = $this->assertSession();
     // Clear filters.
     $this->drupalGet('admin/modules/browse/project_browser_test_mock');
     $this->svelteInitHelper('text', 'Clear Filters');
@@ -734,7 +736,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->assertEquals('Show actively maintained projects', $this->getElementText(self::MAINTENANCE_OPTION_SELECTOR . self::OPTION_CHECKED));
     // Make sure the second filter applied is the security covered filter.
     $this->assertEquals('Show projects covered by a security policy', $this->getElementText(self::SECURITY_OPTION_SELECTOR . self::OPTION_CHECKED));
-    $this->assertTrue($assert_session->waitForText('10 Results'));
+    $this->assertPageHasText('10 Results');
   }
 
   /**
@@ -835,7 +837,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     // Filter by search text.
     $this->inputSearchField('Number', TRUE);
     $this->assertElementIsVisible('css', ".search__search-submit")->click();
-    $this->assertTrue($assert_session->waitForText('2 Results'));
+    $this->assertPageHasText('2 Results');
     $this->assertProjectsVisible([
       '9 Starts With a Higher Number',
       '1 Starts With a Number',
@@ -847,7 +849,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->pressWithWait('project_browser_test_mock');
     $this->svelteInitHelper('css', '#project-browser .pb-project');
     // Assert that the filters persist.
-    $this->assertTrue($assert_session->waitForText('2 Results'));
+    $this->assertPageHasText('2 Results');
     $this->assertProjectsVisible([
       '9 Starts With a Higher Number',
       '1 Starts With a Number',
@@ -1189,7 +1191,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     // This asserts that status icon is present on the cards.
     $this->assertElementIsVisible('css', '.pb-project__maintenance-icon .pb-project__status-icon-btn');
     $assert_session->waitForButton('Helvetica')?->click();
-    $this->assertTrue($assert_session->waitForText('The module is actively maintained by the maintainers'));
+    $this->assertPageHasText('The module is actively maintained by the maintainers');
     // This asserts that status icon is present in detail's modal.
     $this->assertElementIsVisible('css', '.pb-detail-modal__sidebar .pb-project__status-icon-btn');
     $page->find('css', '.ui-dialog-titlebar-close')?->click();
@@ -1212,7 +1214,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
 
     // Ensure the project list is loaded.
     $this->assertElementIsVisible('css', '#project-browser .pb-project');
-    $this->assertTrue($assert_session->waitForText('Results'));
+    $this->assertPageHasText('Results');
 
     // Expect Grapefruit to have 1 install.
     $this->assertElementIsVisible('xpath', '//span[contains(@class, "pb-project__install-count") and text()="1 install"]');
@@ -1222,7 +1224,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $grapefruit_link?->click();
 
     // Verify the text for Grapefruit (singular case).
-    $this->assertTrue($assert_session->waitForText('site reports using this module'));
+    $this->assertPageHasText('site reports using this module');
 
     // Go back to the project list.
     $close_button = $page->find('xpath', '//button[contains(@class, "ui-dialog-titlebar-close") and contains(text(), "Close")]');
@@ -1236,7 +1238,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $octopus_link?->click();
 
     // Verify the text for Octopus (plural case).
-    $this->assertTrue($assert_session->waitForText('sites report using this module'));
+    $this->assertPageHasText('sites report using this module');
   }
 
   /**
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php
index 33d802b69..8ecae7289 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php
@@ -77,7 +77,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $this->assertElementIsVisible('css', '#project-browser .pb-display__button[value="Grid"]');
     $grid_text = $this->getElementText('#project-browser .pb-display__button[value="Grid"]');
     $this->assertEquals('Grid', $grid_text);
-    $this->assertTrue($assert_session->waitForText('Results'));
+    $this->assertPageHasText('Results');
     $assert_session->pageTextNotContains('No records available');
     $page->pressButton('List');
     $this->assertElementIsVisible('css', '#project-browser .pb-project.pb-project--list');
@@ -125,7 +125,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $this->drupalGet('admin/modules/browse/drupalorg_jsonapi');
     $this->svelteInitHelper('css', '.pb-filter__multi-dropdown');
     // Initial results count on page load.
-    $this->assertTrue($assert_session->waitForText(' Results'));
+    $this->assertPageHasText(' Results');
     $assert_session->pageTextNotContains(' 0 Results');
     // Open category drop-down.
     $this->clickWithWait('.pb-filter__multi-dropdown', 'E-commerce', TRUE);
@@ -158,7 +158,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
 
     // Click 'Media' checkbox.
     $this->clickWithWait('#68428c33-1db7-438d-b1b3-e23004e0982b');
-    $this->assertTrue($assert_session->waitForText(' Results'));
+    $this->assertPageHasText(' Results');
     $assert_session->pageTextNotContains(' 0 Results');
 
     // Click 'Developer tools' checkbox.
@@ -166,7 +166,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
 
     // Make sure the 'Media' module category filter is applied.
     $this->assertEquals('Media', $this->getElementText('p.filter-applied:nth-child(2) .filter-applied__label'));
-    $this->assertTrue($assert_session->waitForText(' Results'));
+    $this->assertPageHasText(' Results');
     $assert_session->pageTextNotContains(' 0 Results');
   }
 
@@ -193,7 +193,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $this->assertPagerItems(['1', '2', '3', '4', '5', '…', 'Next', 'Last']);
 
     $page->pressButton('Clear filters');
-    $this->assertTrue($assert_session->waitForText(' Results'));
+    $this->assertPageHasText(' Results');
     $assert_session->pageTextNotContains(' 0 Results');
     $this->assertPagerItems(['1', '2', '3', '4', '5', '…', 'Next', 'Last']);
     $assert_session->elementExists('css', '.pager__item--active > .is-active[aria-label="Page 1"]');
@@ -254,7 +254,6 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
 
     // Make sure the correct filter was applied.
     $this->assertEquals('Show projects under active development', $this->getElementText(self::DEVELOPMENT_OPTION_SELECTOR . self::OPTION_CHECKED));
-    $assert_session->waitForText('No records available');
 
     // Clear all filters.
     $this->pressWithWait('Clear filters', 'Results');
@@ -323,7 +322,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     $this->assertTrue($assert_session->optionExists('maintenance_status', 'Show actively maintained projects')->isSelected());
     // Make sure the second filter applied is the security covered filter.
     $this->assertTrue($assert_session->optionExists('security_advisory_coverage', 'Show projects covered by a security policy')->isSelected());
-    $this->assertTrue($assert_session->waitForText(' Results'));
+    $this->assertPageHasText(' Results');
     $assert_session->pageTextNotContains(' 0 Results');
   }
 
@@ -354,9 +353,9 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase {
     // Drupal.org test mock defines only two filters (actively maintained filter
     // and security coverage filter).
     $this->assertElementIsVisible('css', '.search__form-filters-container');
-    $this->assertTrue($assert_session->waitForText('Maintenance status'));
+    $this->assertPageHasText('Maintenance status');
     $this->assertElementIsVisible('css', self::MAINTENANCE_OPTION_SELECTOR);
-    $this->assertTrue($assert_session->waitForText('Security advisory coverage'));
+    $this->assertPageHasText('Security advisory coverage');
     $this->assertElementIsVisible('css', self::SECURITY_OPTION_SELECTOR);
     // Make sure no other filters are displayed.
     $this->assertFalse($assert_session->waitForText('Development status'));
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTestTrait.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTestTrait.php
index 0816594aa..4b0488eb4 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserUiTestTrait.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTestTrait.php
@@ -11,6 +11,16 @@ use Behat\Mink\Element\NodeElement;
  */
 trait ProjectBrowserUiTestTrait {
 
+  /**
+   * Waits for specific text to appear on the page.
+   *
+   * @param string $text
+   *   The text we're waiting for.
+   */
+  protected function assertPageHasText(string $text): void {
+    $this->assertTrue($this->assertSession()->waitForText($text), "Expected '$text' to appear on the page but it didn't.");
+  }
+
   /**
    * Waits for an element to be visible, and returns it.
    *
@@ -192,7 +202,7 @@ trait ProjectBrowserUiTestTrait {
     }
 
     if (!empty($wait_for_text)) {
-      $this->waitForPageToContainText($wait_for_text);
+      $this->assertPageHasText($wait_for_text);
     }
   }
 
@@ -231,7 +241,7 @@ trait ProjectBrowserUiTestTrait {
     }
 
     if (!empty($wait_for_text)) {
-      $this->assertTrue($this->assertSession()->waitForText($wait_for_text));
+      $this->assertPageHasText($wait_for_text);
     }
   }
 
@@ -331,9 +341,8 @@ trait ProjectBrowserUiTestTrait {
    */
   protected function assertPagerItems(array $pager_items): void {
     $page = $this->getSession()->getPage();
-    $assert_session = $this->assertSession();
 
-    $assert_session->waitForElementVisible('css', '#project-browser .pb-project');
+    $this->assertElementIsVisible('css', '#project-browser .pb-project');
     $items = array_map(function ($element) {
       return $element->getText();
     }, $page->findAll('css', '#project-browser .pager__item'));
@@ -343,16 +352,6 @@ trait ProjectBrowserUiTestTrait {
     $this->assertSame($pager_items, $items);
   }
 
-  /**
-   * Helper to wait for text on page.
-   *
-   * @param string $text
-   *   The expected text.
-   */
-  protected function waitForPageToContainText(string $text): void {
-    $this->assertTrue($this->assertSession()->waitForText($text), "Expected '$text' to appear on the page but it didn't.");
-  }
-
   /**
    * Helper to wait for a field to appear on the page.
    *
-- 
GitLab