diff options
| author | Richard Purdie <richard.purdie@linuxfoundation.org> | 2024-10-18 22:37:27 +0100 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2024-10-24 11:24:03 +0100 |
| commit | bac01b075611065a087aeb6efe1f85ee751737d8 (patch) | |
| tree | 9317ee2918ded19d99356a17dcae9c660889ed8e | |
| parent | d0c0c00f6cc693a70ba4cb1e0834a1f75a7fef75 (diff) | |
| download | poky-bac01b075611065a087aeb6efe1f85ee751737d8.tar.gz | |
bitbake: toaster/tests/functiona/project_page_tab_config: Switch to using library create_project function
Switch this test module to use the common project creation code which contains
race fixes. That code requires the database access wrapper be dropped and
we no longer have ordering constraints.
There is one test that does require database access. Move this to a separate class
and allow database access there. Use ordering constraints to allow them to run
after the main code. They depend on the project creation from the other class which
isn't ideal but good enough for now.
(Bitbake rev: fa10ba2a8749415d8f06cfc15c228c6eb7df1bcf)
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
| -rw-r--r-- | bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py | 213 |
1 files changed, 90 insertions, 123 deletions
diff --git a/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py b/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py index 5da9706b29..daf00d8f1d 100644 --- a/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py +++ b/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py | |||
| @@ -7,7 +7,6 @@ | |||
| 7 | # | 7 | # |
| 8 | 8 | ||
| 9 | import string | 9 | import string |
| 10 | import random | ||
| 11 | import pytest | 10 | import pytest |
| 12 | from django.urls import reverse | 11 | from django.urls import reverse |
| 13 | from selenium.webdriver import Keys | 12 | from selenium.webdriver import Keys |
| @@ -19,60 +18,16 @@ from selenium.webdriver.common.by import By | |||
| 19 | 18 | ||
| 20 | from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled | 19 | from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled |
| 21 | 20 | ||
| 22 | 21 | class TestProjectConfigTabBase(SeleniumFunctionalTestCase): | |
| 23 | @pytest.mark.django_db | ||
| 24 | @pytest.mark.order("last") | ||
| 25 | class TestProjectConfigTab(SeleniumFunctionalTestCase): | ||
| 26 | PROJECT_NAME = 'TestProjectConfigTab' | 22 | PROJECT_NAME = 'TestProjectConfigTab' |
| 27 | project_id = None | 23 | project_id = None |
| 28 | 24 | ||
| 29 | def _create_project(self, project_name, **kwargs): | ||
| 30 | """ Create/Test new project using: | ||
| 31 | - Project Name: Any string | ||
| 32 | - Release: Any string | ||
| 33 | - Merge Toaster settings: True or False | ||
| 34 | """ | ||
| 35 | release = kwargs.get('release', '3') | ||
| 36 | self.get(reverse('newproject')) | ||
| 37 | self.wait_until_visible('#new-project-name') | ||
| 38 | self.find("#new-project-name").send_keys(project_name) | ||
| 39 | select = Select(self.find("#projectversion")) | ||
| 40 | select.select_by_value(release) | ||
| 41 | |||
| 42 | # check merge toaster settings | ||
| 43 | checkbox = self.find('.checkbox-mergeattr') | ||
| 44 | if not checkbox.is_selected(): | ||
| 45 | checkbox.click() | ||
| 46 | |||
| 47 | if self.PROJECT_NAME != 'TestProjectConfigTab': | ||
| 48 | # Reset project name if it's not the default one | ||
| 49 | self.PROJECT_NAME = 'TestProjectConfigTab' | ||
| 50 | |||
| 51 | self.find("#create-project-button").click() | ||
| 52 | |||
| 53 | try: | ||
| 54 | self.wait_until_visible('#hint-error-project-name', poll=3) | ||
| 55 | url = reverse('project', args=(TestProjectConfigTab.project_id, )) | ||
| 56 | self.get(url) | ||
| 57 | self.wait_until_visible('#config-nav', poll=3) | ||
| 58 | except TimeoutException: | ||
| 59 | self.wait_until_visible('#config-nav', poll=3) | ||
| 60 | |||
| 61 | def _random_string(self, length): | ||
| 62 | return ''.join( | ||
| 63 | random.choice(string.ascii_letters) for _ in range(length) | ||
| 64 | ) | ||
| 65 | |||
| 66 | def _navigate_to_project_page(self): | 25 | def _navigate_to_project_page(self): |
| 67 | # Navigate to project page | 26 | # Navigate to project page |
| 68 | if TestProjectConfigTab.project_id is None: | 27 | if TestProjectConfigTabBase.project_id is None: |
| 69 | self._create_project(project_name=self._random_string(10)) | 28 | TestProjectConfigTabBase.project_id = self.create_new_project(self.PROJECT_NAME, '3', None, True) |
| 70 | current_url = self.driver.current_url | 29 | url = reverse('project', args=(TestProjectConfigTabBase.project_id,)) |
| 71 | TestProjectConfigTab.project_id = get_projectId_from_url( | 30 | self.get(url) |
| 72 | current_url) | ||
| 73 | else: | ||
| 74 | url = reverse('project', args=(TestProjectConfigTab.project_id,)) | ||
| 75 | self.get(url) | ||
| 76 | self.wait_until_visible('#config-nav') | 31 | self.wait_until_visible('#config-nav') |
| 77 | 32 | ||
| 78 | def _create_builds(self): | 33 | def _create_builds(self): |
| @@ -114,6 +69,8 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 114 | config_nav = self.find('#config-nav') | 69 | config_nav = self.find('#config-nav') |
| 115 | return config_nav.find_elements(By.TAG_NAME, 'li')[index] | 70 | return config_nav.find_elements(By.TAG_NAME, 'li')[index] |
| 116 | 71 | ||
| 72 | class TestProjectConfigTab(TestProjectConfigTabBase): | ||
| 73 | |||
| 117 | def test_project_config_nav(self): | 74 | def test_project_config_nav(self): |
| 118 | """ Test project config tab navigation: | 75 | """ Test project config tab navigation: |
| 119 | - Check if the menu is displayed and contains the right elements: | 76 | - Check if the menu is displayed and contains the right elements: |
| @@ -160,26 +117,26 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 160 | conf_nav_list = [ | 117 | conf_nav_list = [ |
| 161 | # config | 118 | # config |
| 162 | [0, 'Configuration', | 119 | [0, 'Configuration', |
| 163 | f"/toastergui/project/{TestProjectConfigTab.project_id}"], | 120 | f"/toastergui/project/{TestProjectConfigTabBase.project_id}"], |
| 164 | # custom images | 121 | # custom images |
| 165 | [2, 'Custom images', | 122 | [2, 'Custom images', |
| 166 | f"/toastergui/project/{TestProjectConfigTab.project_id}/customimages"], | 123 | f"/toastergui/project/{TestProjectConfigTabBase.project_id}/customimages"], |
| 167 | # image recipes | 124 | # image recipes |
| 168 | [3, 'Image recipes', | 125 | [3, 'Image recipes', |
| 169 | f"/toastergui/project/{TestProjectConfigTab.project_id}/images"], | 126 | f"/toastergui/project/{TestProjectConfigTabBase.project_id}/images"], |
| 170 | # software recipes | 127 | # software recipes |
| 171 | [4, 'Software recipes', | 128 | [4, 'Software recipes', |
| 172 | f"/toastergui/project/{TestProjectConfigTab.project_id}/softwarerecipes"], | 129 | f"/toastergui/project/{TestProjectConfigTabBase.project_id}/softwarerecipes"], |
| 173 | # machines | 130 | # machines |
| 174 | [5, 'Machines', | 131 | [5, 'Machines', |
| 175 | f"/toastergui/project/{TestProjectConfigTab.project_id}/machines"], | 132 | f"/toastergui/project/{TestProjectConfigTabBase.project_id}/machines"], |
| 176 | # layers | 133 | # layers |
| 177 | [6, 'Layers', | 134 | [6, 'Layers', |
| 178 | f"/toastergui/project/{TestProjectConfigTab.project_id}/layers"], | 135 | f"/toastergui/project/{TestProjectConfigTabBase.project_id}/layers"], |
| 179 | # distro | 136 | # distro |
| 180 | [7, 'Distros', | 137 | [7, 'Distros', |
| 181 | f"/toastergui/project/{TestProjectConfigTab.project_id}/distros"], | 138 | f"/toastergui/project/{TestProjectConfigTabBase.project_id}/distros"], |
| 182 | # [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTab.project_id}/configuration"], # bitbake variables | 139 | # [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTabBase.project_id}/configuration"], # bitbake variables |
| 183 | ] | 140 | ] |
| 184 | for index, item_name, url in conf_nav_list: | 141 | for index, item_name, url in conf_nav_list: |
| 185 | item = _get_config_nav_item(index) | 142 | item = _get_config_nav_item(index) |
| @@ -299,9 +256,11 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 299 | - meta-poky | 256 | - meta-poky |
| 300 | - meta-yocto-bsp | 257 | - meta-yocto-bsp |
| 301 | """ | 258 | """ |
| 302 | # Create a new project for this test | 259 | project_id = self.create_new_project(self.PROJECT_NAME + "-ST", '3', None, True) |
| 303 | project_name = self._random_string(10) | 260 | url = reverse('project', args=(project_id,)) |
| 304 | self._create_project(project_name=project_name) | 261 | self.get(url) |
| 262 | self.wait_until_visible('#config-nav') | ||
| 263 | |||
| 305 | # check if the menu is displayed | 264 | # check if the menu is displayed |
| 306 | self.wait_until_visible('#project-page') | 265 | self.wait_until_visible('#project-page') |
| 307 | block_l = self.driver.find_element( | 266 | block_l = self.driver.find_element( |
| @@ -374,61 +333,6 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 374 | layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') | 333 | layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') |
| 375 | self.assertEqual(len(layers_list_items), 4) | 334 | self.assertEqual(len(layers_list_items), 4) |
| 376 | 335 | ||
| 377 | def test_most_build_recipes(self): | ||
| 378 | """ Test most build recipes block contains""" | ||
| 379 | def rebuild_from_most_build_recipes(recipe_list_items): | ||
| 380 | checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input') | ||
| 381 | checkbox.click() | ||
| 382 | build_btn = self.find('#freq-build-btn') | ||
| 383 | build_btn.click() | ||
| 384 | self.wait_until_visible('#latest-builds') | ||
| 385 | wait_until_build(self, 'queued cloning starting parsing failed') | ||
| 386 | lastest_builds = self.driver.find_elements( | ||
| 387 | By.XPATH, | ||
| 388 | '//div[@id="latest-builds"]/div' | ||
| 389 | ) | ||
| 390 | self.assertTrue(len(lastest_builds) >= 2) | ||
| 391 | last_build = lastest_builds[0] | ||
| 392 | try: | ||
| 393 | cancel_button = last_build.find_element( | ||
| 394 | By.XPATH, | ||
| 395 | '//span[@class="cancel-build-btn pull-right alert-link"]', | ||
| 396 | ) | ||
| 397 | cancel_button.click() | ||
| 398 | except NoSuchElementException: | ||
| 399 | # Skip if the build is already cancelled | ||
| 400 | pass | ||
| 401 | wait_until_build_cancelled(self) | ||
| 402 | # Create a new project for remaining asserts | ||
| 403 | project_name = self._random_string(10) | ||
| 404 | self._create_project(project_name=project_name, release='2') | ||
| 405 | current_url = self.driver.current_url | ||
| 406 | TestProjectConfigTab.project_id = get_projectId_from_url(current_url) | ||
| 407 | url = current_url.split('?')[0] | ||
| 408 | |||
| 409 | # Create a new builds | ||
| 410 | self._create_builds() | ||
| 411 | |||
| 412 | # back to project page | ||
| 413 | self.driver.get(url) | ||
| 414 | |||
| 415 | self.wait_until_visible('#project-page', poll=3) | ||
| 416 | |||
| 417 | # Most built recipes | ||
| 418 | most_built_recipes = self.driver.find_element( | ||
| 419 | By.XPATH, '//*[@id="project-page"]/div[1]/div[3]') | ||
| 420 | title = most_built_recipes.find_element(By.TAG_NAME, 'h3') | ||
| 421 | self.assertIn("Most built recipes", title.text) | ||
| 422 | # check can select a recipe and build it | ||
| 423 | self.wait_until_visible('#freq-build-list', poll=3) | ||
| 424 | recipe_list = self.find('#freq-build-list') | ||
| 425 | recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li') | ||
| 426 | self.assertTrue( | ||
| 427 | len(recipe_list_items) > 0, | ||
| 428 | msg="No recipes found in the most built recipes list", | ||
| 429 | ) | ||
| 430 | rebuild_from_most_build_recipes(recipe_list_items) | ||
| 431 | TestProjectConfigTab.project_id = None # reset project id | ||
| 432 | 336 | ||
| 433 | def test_project_page_tab_importlayer(self): | 337 | def test_project_page_tab_importlayer(self): |
| 434 | """ Test project page tab import layer """ | 338 | """ Test project page tab import layer """ |
| @@ -472,10 +376,11 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 472 | 376 | ||
| 473 | def test_project_page_custom_image_no_image(self): | 377 | def test_project_page_custom_image_no_image(self): |
| 474 | """ Test project page tab "New custom image" when no custom image """ | 378 | """ Test project page tab "New custom image" when no custom image """ |
| 475 | project_name = self._random_string(10) | 379 | project_id = self.create_new_project(self.PROJECT_NAME + "-CustomImage", '3', None, True) |
| 476 | self._create_project(project_name=project_name) | 380 | url = reverse('project', args=(project_id,)) |
| 477 | current_url = self.driver.current_url | 381 | self.get(url) |
| 478 | TestProjectConfigTab.project_id = get_projectId_from_url(current_url) | 382 | self.wait_until_visible('#config-nav') |
| 383 | |||
| 479 | # navigate to "Custom image" tab | 384 | # navigate to "Custom image" tab |
| 480 | custom_image_section = self._get_config_nav_item(2) | 385 | custom_image_section = self._get_config_nav_item(2) |
| 481 | custom_image_section.click() | 386 | custom_image_section.click() |
| @@ -490,9 +395,9 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 490 | div_empty_msg = self.find('#empty-state-customimagestable') | 395 | div_empty_msg = self.find('#empty-state-customimagestable') |
| 491 | link_create_custom_image = div_empty_msg.find_element( | 396 | link_create_custom_image = div_empty_msg.find_element( |
| 492 | By.TAG_NAME, 'a') | 397 | By.TAG_NAME, 'a') |
| 493 | self.assertTrue(TestProjectConfigTab.project_id is not None) | 398 | self.assertTrue(TestProjectConfigTabBase.project_id is not None) |
| 494 | self.assertIn( | 399 | self.assertIn( |
| 495 | f"/toastergui/project/{TestProjectConfigTab.project_id}/newcustomimage", str( | 400 | f"/toastergui/project/{project_id}/newcustomimage", str( |
| 496 | link_create_custom_image.get_attribute('href') | 401 | link_create_custom_image.get_attribute('href') |
| 497 | ) | 402 | ) |
| 498 | ) | 403 | ) |
| @@ -501,7 +406,6 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 501 | link_create_custom_image.text | 406 | link_create_custom_image.text |
| 502 | ) | 407 | ) |
| 503 | ) | 408 | ) |
| 504 | TestProjectConfigTab.project_id = None # reset project id | ||
| 505 | 409 | ||
| 506 | def test_project_page_image_recipe(self): | 410 | def test_project_page_image_recipe(self): |
| 507 | """ Test project page section images | 411 | """ Test project page section images |
| @@ -526,3 +430,66 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 526 | self.wait_until_visible('#imagerecipestable tbody tr') | 430 | self.wait_until_visible('#imagerecipestable tbody tr') |
| 527 | rows = self.find_all('#imagerecipestable tbody tr') | 431 | rows = self.find_all('#imagerecipestable tbody tr') |
| 528 | self.assertTrue(len(rows) > 0) | 432 | self.assertTrue(len(rows) > 0) |
| 433 | |||
| 434 | @pytest.mark.django_db | ||
| 435 | @pytest.mark.order("last") | ||
| 436 | class TestProjectConfigTabDB(TestProjectConfigTabBase): | ||
| 437 | |||
| 438 | def test_most_build_recipes(self): | ||
| 439 | """ Test most build recipes block contains""" | ||
| 440 | def rebuild_from_most_build_recipes(recipe_list_items): | ||
| 441 | checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input') | ||
| 442 | checkbox.click() | ||
| 443 | build_btn = self.find('#freq-build-btn') | ||
| 444 | build_btn.click() | ||
| 445 | self.wait_until_visible('#latest-builds') | ||
| 446 | wait_until_build(self, 'queued cloning starting parsing failed') | ||
| 447 | lastest_builds = self.driver.find_elements( | ||
| 448 | By.XPATH, | ||
| 449 | '//div[@id="latest-builds"]/div' | ||
| 450 | ) | ||
| 451 | self.assertTrue(len(lastest_builds) >= 2) | ||
| 452 | last_build = lastest_builds[0] | ||
| 453 | try: | ||
| 454 | cancel_button = last_build.find_element( | ||
| 455 | By.XPATH, | ||
| 456 | '//span[@class="cancel-build-btn pull-right alert-link"]', | ||
| 457 | ) | ||
| 458 | cancel_button.click() | ||
| 459 | except NoSuchElementException: | ||
| 460 | # Skip if the build is already cancelled | ||
| 461 | pass | ||
| 462 | wait_until_build_cancelled(self) | ||
| 463 | |||
| 464 | # Create a new project for remaining asserts | ||
| 465 | project_id = self.create_new_project(self.PROJECT_NAME + "-MostBuilt", '2', None, True) | ||
| 466 | url = reverse('project', args=(project_id,)) | ||
| 467 | self.get(url) | ||
| 468 | self.wait_until_visible('#config-nav') | ||
| 469 | |||
| 470 | current_url = self.driver.current_url | ||
| 471 | url = current_url.split('?')[0] | ||
| 472 | |||
| 473 | # Create a new builds | ||
| 474 | self._create_builds() | ||
| 475 | |||
| 476 | # back to project page | ||
| 477 | self.driver.get(url) | ||
| 478 | |||
| 479 | self.wait_until_visible('#project-page', poll=3) | ||
| 480 | |||
| 481 | # Most built recipes | ||
| 482 | most_built_recipes = self.driver.find_element( | ||
| 483 | By.XPATH, '//*[@id="project-page"]/div[1]/div[3]') | ||
| 484 | title = most_built_recipes.find_element(By.TAG_NAME, 'h3') | ||
| 485 | self.assertIn("Most built recipes", title.text) | ||
| 486 | # check can select a recipe and build it | ||
| 487 | self.wait_until_visible('#freq-build-list', poll=3) | ||
| 488 | recipe_list = self.find('#freq-build-list') | ||
| 489 | recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li') | ||
| 490 | self.assertTrue( | ||
| 491 | len(recipe_list_items) > 0, | ||
| 492 | msg="No recipes found in the most built recipes list", | ||
| 493 | ) | ||
| 494 | rebuild_from_most_build_recipes(recipe_list_items) | ||
| 495 | |||
