From ac68f3d79dc08972dc75fff8af436cf480837633 Mon Sep 17 00:00:00 2001 From: Adam G-H <32250-phenaproxima@users.noreply.drupalcode.org> Date: Tue, 25 Feb 2025 17:42:51 +0000 Subject: [PATCH] Issue #3508334 by phenaproxima, tim.plunkett: ProjectBrowserUiTest is order-sensitive when it shouldn't be --- .../ProjectBrowserUiTest.php | 141 ++++++++++-------- .../ProjectBrowserUiTestTrait.php | 38 ----- 2 files changed, 82 insertions(+), 97 deletions(-) diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php index c79c0cc5a..28180b0ab 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php @@ -130,19 +130,8 @@ class ProjectBrowserUiTest extends WebDriverTestBase { $e_commerce = $assert_session->waitForField('E-commerce'); $e_commerce?->check(); - // @todo Move this into a trait for testing Project Browser's UI in a - // stable, consistent fashion. - $assert_category_filters_applied = function (array $expected_categories): void { - $selector = '.filter-applied__label'; - $this->assertElementIsVisible('css', $selector); - $applied_categories = array_map( - fn ($element) => $element->getText(), - $this->getSession()->getPage()->findAll('css', $selector), - ); - $this->assertSame($expected_categories, $applied_categories); - }; // Make sure the 'E-commerce' module category filter is applied. - $assert_category_filters_applied(['E-commerce']); + $this->assertSame(['E-commerce'], $this->getSelectedCategories()); // This call has the second argument, `$reload`, set to TRUE due to it // failing on ~2% of GitLabCI test runs. It is not entirely clear why this @@ -157,7 +146,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { '9 Starts With a Higher Number', 'Helvetica', 'Astronaut Simulator', - ], TRUE); + ]); // Clear the checkbox to verify the results revert to their initial state. $this->clickWithWait('#104', '10 Results'); @@ -177,7 +166,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { $e_commerce?->check(); // Make sure the 'Media' module category filter is applied. - $assert_category_filters_applied(['Media', 'E-commerce']); + $this->assertSame(['Media', 'E-commerce'], $this->getSelectedCategories()); // Assert that only media and administration module categories are shown. $this->assertProjectsVisible([ 'Jazz', @@ -481,7 +470,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { 'Grapefruit', 'Helvetica', 'Ice Ice', - ]); + ], in_order: TRUE); // Select 'Z-A' sorting order. $this->sortBy('z_a'); @@ -499,7 +488,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { 'Mad About You', 'Looper', 'Kangaroo', - ]); + ], in_order: TRUE); // Select 'Active installs' option. $this->sortBy('usage_total'); @@ -518,7 +507,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { 'Mad About You', 'Dancing Queen', 'Kangaroo', - ]); + ], in_order: TRUE); // Select 'Newest First' option. $this->sortBy('created'); @@ -538,7 +527,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { 'Soup', 'Octopus', 'Tooth Fairy', - ]); + ], in_order: TRUE); } /** @@ -699,8 +688,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { ]); $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')); + $this->assertSame(['E-commerce', 'Media'], $this->getSelectedCategories()); $this->clickWithWait('[aria-label="First page"]'); $this->assertProjectsVisible([ @@ -716,10 +704,9 @@ class ProjectBrowserUiTest extends WebDriverTestBase { 'Dancing Queen', 'Cream cheese on a bagel', 'Become a Banana', - ], TRUE); + ]); - $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')); + $this->assertSame(['E-commerce', 'Media'], $this->getSelectedCategories()); } /** @@ -748,7 +735,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { $page = $this->getSession()->getPage(); $assert_session = $this->assertSession(); // Enable module for extra source plugin. - $this->container->get('module_installer')->install(['project_browser_devel'], TRUE); + $this->container->get('module_installer')->install(['project_browser_devel']); // Test categories with multiple plugin enabled. $this->drupalGet('admin/modules/browse'); $this->svelteInitHelper('css', '.pb-filter__checkbox'); @@ -810,22 +797,19 @@ class ProjectBrowserUiTest extends WebDriverTestBase { $this->assertNotSame($second_tab_text, $assert_session->buttonExists('random_data')->getText()); $this->assertSame('1 category selected', $page->find('css', '.pb-filter__multi-dropdown__label')->getText()); // Save the filter applied in second tab. - $applied_filter = $this->getElementText('p.filter-applied:nth-child(1) .filter-applied__label'); + [$applied_filter] = $this->getSelectedCategories(); // Save the number of results. $results_before = count($page->findAll('css', '#project-browser .pb-project.list')); // Switch back to first tab. $this->pressWithWait('project_browser_test_mock'); $this->assertSame('2 categories selected', $page->find('css', '.pb-filter__multi-dropdown__label')->getText()); - $first_filter_element = $page->find('css', 'p.filter-applied:nth-child(1)'); - $this->assertEquals('E-commerce', $first_filter_element->find('css', '.filter-applied__label')->getText()); - $second_filter_element = $page->find('css', 'p.filter-applied:nth-child(2)'); - $this->assertEquals('Media', $second_filter_element->find('css', '.filter-applied__label')->getText()); + $this->assertSame(['E-commerce', 'Media'], $this->getSelectedCategories()); // Again switch to second tab. $this->pressWithWait('random_data'); // Assert that the filters persist. - $this->assertEquals($applied_filter, $this->getElementText('p.filter-applied:nth-child(1) .filter-applied__label')); + $this->assertSame($applied_filter, $this->getSelectedCategories()[0]); $this->assertSame('1 category selected', $page->find('css', '.pb-filter__multi-dropdown__label')->getText()); // Assert that the number of results is the same. @@ -947,36 +931,22 @@ class ProjectBrowserUiTest extends WebDriverTestBase { /** * Tests the visibility of categories in list and grid view. + * + * @testWith ["Grid"] + * ["List"] */ - public function testCategoriesVisibility(): void { - $page = $this->getSession()->getPage(); - $assert_session = $this->assertSession(); - $view_options = [ - [ - 'selector' => '.pb-display__button[value="Grid"]', - 'value' => 'Grid', - ], [ - 'selector' => '.pb-display__button[value="List"]', - 'value' => 'List', - ], - ]; + public function testCategoriesVisibility(string $display_type): void { $this->getSession()->resizeWindow(1300, 1300); + $this->drupalGet('admin/modules/browse/project_browser_test_mock'); + $this->assertSession()->waitForButton($display_type)?->press(); - // Check visibility of categories in each view. - foreach ($view_options as $selector) { - $this->drupalGet('admin/modules/browse/project_browser_test_mock'); - $this->svelteInitHelper('css', $selector['selector']); - $page->pressButton($selector['value']); - $this->svelteInitHelper('text', 'Helvetica'); - $assert_session->elementsCount('css', '#project-browser .pb-layout__main ul li:nth-child(7) .pb-project-categories ul li', 1); - $grid_text = $this->getElementText('#project-browser .pb-layout__main ul li:nth-child(7) .pb-project-categories ul li:nth-child(1)'); - $this->assertEquals('E-commerce', $grid_text); - $assert_session->elementsCount('css', '#project-browser .pb-layout__main ul li:nth-child(10) .pb-project-categories ul li', 2); - $grid_text = $this->getElementText('#project-browser .pb-layout__main ul li:nth-child(7) .pb-project-categories ul li:nth-child(1)'); - $this->assertEquals('E-commerce', $grid_text); - $grid_text = $this->getElementText('#project-browser .pb-layout__main ul li:nth-child(10) .pb-project-categories ul li:nth-child(2)'); - $this->assertEquals('E-commerce', $grid_text); - } + $helvetica = $this->waitForProject('Helvetica'); + $this->assertSame('E-commerce', $helvetica->find('css', '.pb-project-categories ul li')?->getText()); + + $astronaut_simulator_categories = $this->waitForProject('Astronaut Simulator') + ->findAll('css', '.pb-project-categories ul li'); + $this->assertCount(2, $astronaut_simulator_categories); + $this->assertSame('E-commerce', $astronaut_simulator_categories[1]->getText()); } /** @@ -1055,7 +1025,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { // @todo Remove try/catch in https://www.drupal.org/i/3349193. try { - $this->container->get('module_installer')->install(['package_manager'], TRUE); + $this->container->get('module_installer')->install(['package_manager']); } catch (MissingDependencyException $e) { $this->markTestSkipped($e->getMessage()); @@ -1166,7 +1136,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { */ public function testSourcePluginRoutes(): void { // Enable module for extra source plugin. - $this->container->get('module_installer')->install(['project_browser_devel'], TRUE); + $this->container->get('module_installer')->install(['project_browser_devel']); $this->rebuildContainer(); $current_sources = $this->container->get(EnabledSourceHandler::class)->getCurrentSources(); @@ -1322,4 +1292,57 @@ class ProjectBrowserUiTest extends WebDriverTestBase { $this->assertEquals($search_field->getValue(), ''); } + /** + * Asserts that a given list of project titles are visible on the page. + * + * @param array $project_titles + * An array of expected titles. + * @param int $timeout + * (optional) How many seconds to wait before giving up. Defaults to 10. + * @param bool $in_order + * (optional) If TRUE, assert that the projects are visible on the page + * in the same order as $project_titles. Defaults to FALSE. + */ + private function assertProjectsVisible(array $project_titles, int $timeout = 10, bool $in_order = FALSE): void { + $page = $this->getSession()->getPage(); + + $list_visible_projects = function () use ($page): array { + return array_map( + fn (NodeElement $element) => $element->getText(), + $page->findAll('css', '#project-browser .pb-project h3 button'), + ); + }; + + $missing = []; + $success = $this->getSession() + ->getPage() + ->waitFor($timeout, function () use ($project_titles, &$missing, $list_visible_projects): bool { + $missing = array_diff($project_titles, $list_visible_projects()); + return empty($missing); + }); + + $this->assertTrue( + $success, + sprintf('The following projects should have appeared, but did not: %s', implode(', ', $missing)), + ); + if ($in_order) { + $this->assertSame($project_titles, $list_visible_projects()); + } + } + + /** + * Returns the currently selected category names. + * + * @return string[] + * The names of the currently selected categories, in the order they appear + * as lozenges in the search area. + */ + private function getSelectedCategories(): array { + $elements = $this->getSession() + ->getPage() + ->findAll('css', 'p.filter-applied .filter-applied__label'); + + return array_map(fn (NodeElement $element) => $element->getText(), $elements); + } + } diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTestTrait.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTestTrait.php index 4b0488eb4..d24f35955 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserUiTestTrait.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTestTrait.php @@ -123,44 +123,6 @@ trait ProjectBrowserUiTestTrait { $this->assertInstanceOf(NodeElement::class, $indicator); } - /** - * Asserts that a given list of project titles are visible on the page. - * - * @param array $project_titles - * An array of expected titles. - * @param bool $reload - * When TRUE, reload the page if the assertion fails and try again. - * This should typically be kept to the default value of FALSE. It only - * needs to be set to TRUE for calls that intermittently fail on GitLabCI. - */ - protected function assertProjectsVisible(array $project_titles, bool $reload = FALSE): void { - $count = count($project_titles); - - // Create a JavaScript string that checks the titles of the visible - // projects. This is done with JavaScript to avoid issues with PHP - // referencing an element that was rerendered and thus unavailable. - $script = "document.querySelectorAll('#project-browser .pb-project h3 button').length === $count"; - foreach ($project_titles as $key => $value) { - $script .= " && document.querySelectorAll('#project-browser .pb-project h3 button')[$key].textContent === '$value'"; - } - - // It can take a while for all items to render. Wait for the condition to be - // true before asserting it. - $this->getSession()->wait(10000, $script); - - if ($reload) { - try { - $this->assertTrue($this->getSession()->evaluateScript($script), 'Ran:' . $script . 'Svelte did not initialize. Markup: ' . $this->getSession()->evaluateScript('document.querySelector("#project-browser").innerHTML')); - } - catch (\Exception) { - $this->getSession()->reload(); - $this->getSession()->wait(10000, $script); - } - } - - $this->assertTrue($this->getSession()->evaluateScript($script), 'Ran:' . $script . 'Svelte did not initialize. Markup: ' . $this->getSession()->evaluateScript('document.querySelector("#project-browser").innerHTML')); - } - /** * Searches for a term in the search field. * -- GitLab