diff options
author | Alassane Yattara <alassane.yattara@savoirfairelinux.com> | 2023-12-14 23:11:32 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2023-12-15 14:37:28 +0000 |
commit | 81a0110ca532fb4f60b3ea4cd6a977e9c967ac62 (patch) | |
tree | 306c4128b720ab8b0dc06ada4384109f87f71196 /bitbake/lib | |
parent | c8382b35e843591a34f867c5eb34a24cc17634e2 (diff) | |
download | poky-81a0110ca532fb4f60b3ea4cd6a977e9c967ac62.tar.gz |
bitbake: toaster/tests: Bug-Fix testcase functional/test_project_page_tab_config.py
All issues and failures stemmed from a specific test case:
test_project_config_tab_right_section in the file
bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py.
This test was designed to verify whether the "Most built recipes"
section on the project page correctly displays the latest and oldest
recipes built by the user, irrespective of the build outcome (failed,
cancelled, succeeded, or errored).
The errors and failures arose because the build process did not
terminate as expected, particularly when attempting to build recipe
images such as "core-image-minimal" or "bash." It was discovered that
building a real recipe/image was unnecessary for the test's purpose.
Instead, building a fake recipe like "foo" provided a reliable way to
ensure the build would fail or be interrupted.
(Bitbake rev: 5162db5305826235c09d9fcd38b5fb48ded31622)
Signed-off-by: Alassane Yattara <alassane.yattara@savoirfairelinux.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib')
-rw-r--r-- | bitbake/lib/toaster/tests/functional/test_project_page.py | 2 | ||||
-rw-r--r-- | bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py | 186 |
2 files changed, 95 insertions, 93 deletions
diff --git a/bitbake/lib/toaster/tests/functional/test_project_page.py b/bitbake/lib/toaster/tests/functional/test_project_page.py index 077badb0c2..82dca442f9 100644 --- a/bitbake/lib/toaster/tests/functional/test_project_page.py +++ b/bitbake/lib/toaster/tests/functional/test_project_page.py | |||
@@ -461,7 +461,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
461 | '//td[@class="add-del-layers"]//a[1]' | 461 | '//td[@class="add-del-layers"]//a[1]' |
462 | ) | 462 | ) |
463 | build_btn.click() | 463 | build_btn.click() |
464 | build_state = wait_until_build(self, 'parsing starting cloning queued') | 464 | build_state = wait_until_build(self, 'queued cloning starting parsing failed') |
465 | lastest_builds = self.driver.find_elements( | 465 | lastest_builds = self.driver.find_elements( |
466 | By.XPATH, | 466 | By.XPATH, |
467 | '//div[@id="latest-builds"]/div' | 467 | '//div[@id="latest-builds"]/div' |
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 d911ff00d4..4dbf5aeba4 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 | |||
@@ -12,7 +12,7 @@ import pytest | |||
12 | from django.urls import reverse | 12 | from django.urls import reverse |
13 | from selenium.webdriver import Keys | 13 | from selenium.webdriver import Keys |
14 | from selenium.webdriver.support.select import Select | 14 | from selenium.webdriver.support.select import Select |
15 | from selenium.common.exceptions import TimeoutException | 15 | from selenium.common.exceptions import NoSuchElementException, TimeoutException |
16 | from orm.models import Project | 16 | from orm.models import Project |
17 | from tests.functional.functional_helpers import SeleniumFunctionalTestCase | 17 | from tests.functional.functional_helpers import SeleniumFunctionalTestCase |
18 | from selenium.webdriver.common.by import By | 18 | from selenium.webdriver.common.by import By |
@@ -26,17 +26,18 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
26 | PROJECT_NAME = 'TestProjectConfigTab' | 26 | PROJECT_NAME = 'TestProjectConfigTab' |
27 | project_id = None | 27 | project_id = None |
28 | 28 | ||
29 | def _create_project(self, project_name): | 29 | def _create_project(self, project_name, **kwargs): |
30 | """ Create/Test new project using: | 30 | """ Create/Test new project using: |
31 | - Project Name: Any string | 31 | - Project Name: Any string |
32 | - Release: Any string | 32 | - Release: Any string |
33 | - Merge Toaster settings: True or False | 33 | - Merge Toaster settings: True or False |
34 | """ | 34 | """ |
35 | release = kwargs.get('release', '3') | ||
35 | self.get(reverse('newproject')) | 36 | self.get(reverse('newproject')) |
36 | self.wait_until_visible('#new-project-name') | 37 | self.wait_until_visible('#new-project-name') |
37 | self.find("#new-project-name").send_keys(project_name) | 38 | self.find("#new-project-name").send_keys(project_name) |
38 | select = Select(self.find("#projectversion")) | 39 | select = Select(self.find("#projectversion")) |
39 | select.select_by_value('3') | 40 | select.select_by_value(release) |
40 | 41 | ||
41 | # check merge toaster settings | 42 | # check merge toaster settings |
42 | checkbox = self.find('.checkbox-mergeattr') | 43 | checkbox = self.find('.checkbox-mergeattr') |
@@ -50,7 +51,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
50 | self.find("#create-project-button").click() | 51 | self.find("#create-project-button").click() |
51 | 52 | ||
52 | try: | 53 | try: |
53 | self.wait_until_visible('#hint-error-project-name') | 54 | self.wait_until_visible('#hint-error-project-name', poll=3) |
54 | url = reverse('project', args=(TestProjectConfigTab.project_id, )) | 55 | url = reverse('project', args=(TestProjectConfigTab.project_id, )) |
55 | self.get(url) | 56 | self.get(url) |
56 | self.wait_until_visible('#config-nav', poll=3) | 57 | self.wait_until_visible('#config-nav', poll=3) |
@@ -67,7 +68,8 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
67 | if TestProjectConfigTab.project_id is None: | 68 | if TestProjectConfigTab.project_id is None: |
68 | self._create_project(project_name=self._random_string(10)) | 69 | self._create_project(project_name=self._random_string(10)) |
69 | current_url = self.driver.current_url | 70 | current_url = self.driver.current_url |
70 | TestProjectConfigTab.project_id = get_projectId_from_url(current_url) | 71 | TestProjectConfigTab.project_id = get_projectId_from_url( |
72 | current_url) | ||
71 | else: | 73 | else: |
72 | url = reverse('project', args=(TestProjectConfigTab.project_id,)) | 74 | url = reverse('project', args=(TestProjectConfigTab.project_id,)) |
73 | self.get(url) | 75 | self.get(url) |
@@ -76,27 +78,30 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
76 | def _create_builds(self): | 78 | def _create_builds(self): |
77 | # check search box can be use to build recipes | 79 | # check search box can be use to build recipes |
78 | search_box = self.find('#build-input') | 80 | search_box = self.find('#build-input') |
79 | search_box.send_keys('core-image-minimal') | 81 | search_box.send_keys('foo') |
80 | self.find('#build-button').click() | 82 | self.find('#build-button').click() |
81 | self.wait_until_visible('#latest-builds') | 83 | self.wait_until_present('#latest-builds') |
82 | # loop until reach the parsing state | 84 | # loop until reach the parsing state |
83 | build_state = wait_until_build(self, 'parsing starting cloning') | 85 | wait_until_build(self, 'queued cloning starting parsing failed') |
84 | lastest_builds = self.driver.find_elements( | 86 | lastest_builds = self.driver.find_elements( |
85 | By.XPATH, | 87 | By.XPATH, |
86 | '//div[@id="latest-builds"]/div', | 88 | '//div[@id="latest-builds"]/div', |
87 | ) | 89 | ) |
88 | last_build = lastest_builds[0] | 90 | last_build = lastest_builds[0] |
89 | self.assertTrue( | 91 | self.assertTrue( |
90 | 'core-image-minimal' in str(last_build.text) | 92 | 'foo' in str(last_build.text) |
91 | ) | 93 | ) |
92 | cancel_button = last_build.find_element( | 94 | last_build = lastest_builds[0] |
93 | By.XPATH, | 95 | try: |
94 | '//span[@class="cancel-build-btn pull-right alert-link"]', | 96 | cancel_button = last_build.find_element( |
95 | ) | 97 | By.XPATH, |
96 | cancel_button.click() | 98 | '//span[@class="cancel-build-btn pull-right alert-link"]', |
97 | if 'starting' not in build_state: # change build state when cancelled in starting state | 99 | ) |
98 | wait_until_build_cancelled(self) | 100 | cancel_button.click() |
99 | return build_state | 101 | except NoSuchElementException: |
102 | # Skip if the build is already cancelled | ||
103 | pass | ||
104 | wait_until_build_cancelled(self) | ||
100 | 105 | ||
101 | def _get_tabs(self): | 106 | def _get_tabs(self): |
102 | # tabs links list | 107 | # tabs links list |
@@ -126,6 +131,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
126 | - Delete project | 131 | - Delete project |
127 | """ | 132 | """ |
128 | self._navigate_to_project_page() | 133 | self._navigate_to_project_page() |
134 | |||
129 | def _get_config_nav_item(index): | 135 | def _get_config_nav_item(index): |
130 | config_nav = self.find('#config-nav') | 136 | config_nav = self.find('#config-nav') |
131 | return config_nav.find_elements(By.TAG_NAME, 'li')[index] | 137 | return config_nav.find_elements(By.TAG_NAME, 'li')[index] |
@@ -152,13 +158,27 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
152 | self.assertTrue("actions" in str(actions.text).lower()) | 158 | self.assertTrue("actions" in str(actions.text).lower()) |
153 | 159 | ||
154 | conf_nav_list = [ | 160 | conf_nav_list = [ |
155 | [0, 'Configuration', f"/toastergui/project/{TestProjectConfigTab.project_id}"], # config | 161 | # config |
156 | [2, 'Custom images', f"/toastergui/project/{TestProjectConfigTab.project_id}/customimages"], # custom images | 162 | [0, 'Configuration', |
157 | [3, 'Image recipes', f"/toastergui/project/{TestProjectConfigTab.project_id}/images"], # image recipes | 163 | f"/toastergui/project/{TestProjectConfigTab.project_id}"], |
158 | [4, 'Software recipes', f"/toastergui/project/{TestProjectConfigTab.project_id}/softwarerecipes"], # software recipes | 164 | # custom images |
159 | [5, 'Machines', f"/toastergui/project/{TestProjectConfigTab.project_id}/machines"], # machines | 165 | [2, 'Custom images', |
160 | [6, 'Layers', f"/toastergui/project/{TestProjectConfigTab.project_id}/layers"], # layers | 166 | f"/toastergui/project/{TestProjectConfigTab.project_id}/customimages"], |
161 | [7, 'Distros', f"/toastergui/project/{TestProjectConfigTab.project_id}/distros"], # distro | 167 | # image recipes |
168 | [3, 'Image recipes', | ||
169 | f"/toastergui/project/{TestProjectConfigTab.project_id}/images"], | ||
170 | # software recipes | ||
171 | [4, 'Software recipes', | ||
172 | f"/toastergui/project/{TestProjectConfigTab.project_id}/softwarerecipes"], | ||
173 | # machines | ||
174 | [5, 'Machines', | ||
175 | f"/toastergui/project/{TestProjectConfigTab.project_id}/machines"], | ||
176 | # layers | ||
177 | [6, 'Layers', | ||
178 | f"/toastergui/project/{TestProjectConfigTab.project_id}/layers"], | ||
179 | # distro | ||
180 | [7, 'Distros', | ||
181 | f"/toastergui/project/{TestProjectConfigTab.project_id}/distros"], | ||
162 | # [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTab.project_id}/configuration"], # bitbake variables | 182 | # [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTab.project_id}/configuration"], # bitbake variables |
163 | ] | 183 | ] |
164 | for index, item_name, url in conf_nav_list: | 184 | for index, item_name, url in conf_nav_list: |
@@ -281,15 +301,10 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
281 | # Create a new project for this test | 301 | # Create a new project for this test |
282 | project_name = self._random_string(10) | 302 | project_name = self._random_string(10) |
283 | self._create_project(project_name=project_name) | 303 | self._create_project(project_name=project_name) |
284 | current_url = self.driver.current_url | ||
285 | TestProjectConfigTab.project_id = get_projectId_from_url(current_url) | ||
286 | url = current_url.split('?')[0] | ||
287 | # check if the menu is displayed | 304 | # check if the menu is displayed |
288 | self.wait_until_visible('#project-page') | 305 | self.wait_until_visible('#project-page') |
289 | block_l = self.driver.find_element( | 306 | block_l = self.driver.find_element( |
290 | By.XPATH, '//*[@id="project-page"]/div[2]') | 307 | By.XPATH, '//*[@id="project-page"]/div[2]') |
291 | most_built_recipes = self.driver.find_element( | ||
292 | By.XPATH, '//*[@id="project-page"]/div[1]/div[3]') | ||
293 | project_release = self.driver.find_element( | 308 | project_release = self.driver.find_element( |
294 | By.XPATH, '//*[@id="project-page"]/div[1]/div[4]') | 309 | By.XPATH, '//*[@id="project-page"]/div[1]/div[4]') |
295 | layers = block_l.find_element(By.ID, 'layer-container') | 310 | layers = block_l.find_element(By.ID, 'layer-container') |
@@ -315,26 +330,6 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
315 | f'You have changed the {item_name} to: {new_item_name}' in change_notification.text | 330 | f'You have changed the {item_name} to: {new_item_name}' in change_notification.text |
316 | ) | 331 | ) |
317 | 332 | ||
318 | def rebuild_from_most_build_recipes(recipe_list_items): | ||
319 | checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input') | ||
320 | checkbox.click() | ||
321 | build_btn = self.find('#freq-build-btn') | ||
322 | build_btn.click() | ||
323 | self.wait_until_visible('#latest-builds') | ||
324 | build_state = wait_until_build(self, 'parsing starting cloning queued') | ||
325 | lastest_builds = self.driver.find_elements( | ||
326 | By.XPATH, | ||
327 | '//div[@id="latest-builds"]/div' | ||
328 | ) | ||
329 | last_build = lastest_builds[0] | ||
330 | self.assertTrue(len(lastest_builds) >= 2) | ||
331 | cancel_button = last_build.find_element( | ||
332 | By.XPATH, | ||
333 | '//span[@class="cancel-build-btn pull-right alert-link"]', | ||
334 | ) | ||
335 | cancel_button.click() | ||
336 | if 'starting' not in build_state: # change build state when cancelled in starting state | ||
337 | wait_until_build_cancelled(self) | ||
338 | # Machine | 333 | # Machine |
339 | check_machine_distro(self, 'machine', 'qemux86-64', 'machine-section') | 334 | check_machine_distro(self, 'machine', 'qemux86-64', 'machine-section') |
340 | # Distro | 335 | # Distro |
@@ -374,32 +369,61 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
374 | layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') | 369 | layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') |
375 | self.assertTrue(len(layers_list_items) == 4) | 370 | self.assertTrue(len(layers_list_items) == 4) |
376 | 371 | ||
377 | # Most built recipes | 372 | def test_most_build_recipes(self): |
378 | title = most_built_recipes.find_element(By.TAG_NAME, 'h3') | 373 | """ Test most build recipes block contains""" |
379 | self.assertTrue("Most built recipes" in title.text) | 374 | def rebuild_from_most_build_recipes(recipe_list_items): |
375 | checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input') | ||
376 | checkbox.click() | ||
377 | build_btn = self.find('#freq-build-btn') | ||
378 | build_btn.click() | ||
379 | self.wait_until_present('#latest-builds') | ||
380 | wait_until_build(self, 'queued cloning starting parsing failed') | ||
381 | lastest_builds = self.driver.find_elements( | ||
382 | By.XPATH, | ||
383 | '//div[@id="latest-builds"]/div' | ||
384 | ) | ||
385 | self.assertTrue(len(lastest_builds) >= 2) | ||
386 | last_build = lastest_builds[0] | ||
387 | try: | ||
388 | cancel_button = last_build.find_element( | ||
389 | By.XPATH, | ||
390 | '//span[@class="cancel-build-btn pull-right alert-link"]', | ||
391 | ) | ||
392 | cancel_button.click() | ||
393 | except NoSuchElementException: | ||
394 | # Skip if the build is already cancelled | ||
395 | pass | ||
396 | wait_until_build_cancelled(self) | ||
397 | # Create a new project for remaining asserts | ||
398 | project_name = self._random_string(10) | ||
399 | self._create_project(project_name=project_name, release='2') | ||
400 | current_url = self.driver.current_url | ||
401 | TestProjectConfigTab.project_id = get_projectId_from_url(current_url) | ||
402 | url = current_url.split('?')[0] | ||
403 | |||
380 | # Create a new builds | 404 | # Create a new builds |
381 | build_state = self._create_builds() | 405 | self._create_builds() |
382 | 406 | ||
383 | # Refresh the page | 407 | # back to project page |
384 | self.driver.get(url) | 408 | self.driver.get(url) |
385 | 409 | ||
386 | self.wait_until_visible('#project-page', poll=3) | 410 | self.wait_until_visible('#project-page', poll=3) |
387 | # check can select a recipe and build it | 411 | |
412 | # Most built recipes | ||
388 | most_built_recipes = self.driver.find_element( | 413 | most_built_recipes = self.driver.find_element( |
389 | By.XPATH, '//*[@id="project-page"]/div[1]/div[3]') | 414 | By.XPATH, '//*[@id="project-page"]/div[1]/div[3]') |
390 | recipe_list = most_built_recipes.find_element(By.ID, 'freq-build-list') | 415 | title = most_built_recipes.find_element(By.TAG_NAME, 'h3') |
416 | self.assertTrue("Most built recipes" in title.text) | ||
417 | # check can select a recipe and build it | ||
418 | self.wait_until_visible('#freq-build-list', poll=3) | ||
419 | recipe_list = self.find('#freq-build-list') | ||
391 | recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li') | 420 | recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li') |
392 | if 'starting' not in build_state: # Build will not appear in the list if canceled in starting state | 421 | self.assertTrue( |
393 | self.assertTrue( | 422 | len(recipe_list_items) > 0, |
394 | len(recipe_list_items) > 0, | 423 | msg="Any recipes found in the most built recipes list", |
395 | msg="No recipes found in the most built recipes list", | 424 | ) |
396 | ) | 425 | rebuild_from_most_build_recipes(recipe_list_items) |
397 | rebuild_from_most_build_recipes(recipe_list_items) | 426 | TestProjectConfigTab.project_id = None # reset project id |
398 | else: | ||
399 | self.assertTrue( | ||
400 | len(recipe_list_items) == 0, | ||
401 | msg="Recipes found in the most built recipes list", | ||
402 | ) | ||
403 | 427 | ||
404 | def test_project_page_tab_importlayer(self): | 428 | def test_project_page_tab_importlayer(self): |
405 | """ Test project page tab import layer """ | 429 | """ Test project page tab import layer """ |
@@ -461,10 +485,9 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
461 | div_empty_msg = self.find('#empty-state-customimagestable') | 485 | div_empty_msg = self.find('#empty-state-customimagestable') |
462 | link_create_custom_image = div_empty_msg.find_element( | 486 | link_create_custom_image = div_empty_msg.find_element( |
463 | By.TAG_NAME, 'a') | 487 | By.TAG_NAME, 'a') |
464 | last_project_id = Project.objects.get(name=project_name).id | 488 | self.assertTrue(TestProjectConfigTab.project_id is not None) |
465 | self.assertTrue(last_project_id is not None) | ||
466 | self.assertTrue( | 489 | self.assertTrue( |
467 | f"/toastergui/project/{last_project_id}/newcustomimage" in str( | 490 | f"/toastergui/project/{TestProjectConfigTab.project_id}/newcustomimage" in str( |
468 | link_create_custom_image.get_attribute('href') | 491 | link_create_custom_image.get_attribute('href') |
469 | ) | 492 | ) |
470 | ) | 493 | ) |
@@ -473,6 +496,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
473 | link_create_custom_image.text | 496 | link_create_custom_image.text |
474 | ) | 497 | ) |
475 | ) | 498 | ) |
499 | TestProjectConfigTab.project_id = None # reset project id | ||
476 | 500 | ||
477 | def test_project_page_image_recipe(self): | 501 | def test_project_page_image_recipe(self): |
478 | """ Test project page section images | 502 | """ Test project page section images |
@@ -497,25 +521,3 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
497 | self.wait_until_visible('#imagerecipestable tbody tr') | 521 | self.wait_until_visible('#imagerecipestable tbody tr') |
498 | rows = self.find_all('#imagerecipestable tbody tr') | 522 | rows = self.find_all('#imagerecipestable tbody tr') |
499 | self.assertTrue(len(rows) > 0) | 523 | self.assertTrue(len(rows) > 0) |
500 | |||
501 | # Test build button | ||
502 | image_to_build = rows[0] | ||
503 | build_btn = image_to_build.find_element( | ||
504 | By.XPATH, | ||
505 | '//td[@class="add-del-layers"]' | ||
506 | ) | ||
507 | build_btn.click() | ||
508 | build_state = wait_until_build(self, 'parsing starting cloning queued') | ||
509 | lastest_builds = self.driver.find_elements( | ||
510 | By.XPATH, | ||
511 | '//div[@id="latest-builds"]/div' | ||
512 | ) | ||
513 | self.assertTrue(len(lastest_builds) > 0) | ||
514 | last_build = lastest_builds[0] | ||
515 | cancel_button = last_build.find_element( | ||
516 | By.XPATH, | ||
517 | '//span[@class="cancel-build-btn pull-right alert-link"]', | ||
518 | ) | ||
519 | cancel_button.click() | ||
520 | if 'starting' not in build_state: # change build state when cancelled in starting state | ||
521 | wait_until_build_cancelled(self) | ||