diff options
author | Alassane Yattara <alassane.yattara@savoirfairelinux.com> | 2023-12-08 02:53:19 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2023-12-08 17:17:42 +0000 |
commit | 23d3e2c718d6eab5151a425e835554bd4fe20a8b (patch) | |
tree | 804d0fa917bf4e03c93d8d3b96ed3a64b5ddc4c0 /bitbake/lib/toaster/tests | |
parent | d5a6e3b546e7d6691150a5906e531e3b3d6919ee (diff) | |
download | poky-23d3e2c718d6eab5151a425e835554bd4fe20a8b.tar.gz |
bitbake: toaster/tests: Bug fixes, functional tests dependent on each other
refactor test_create_project and test_project_page to remove their dependencies
(Bitbake rev: 54f7c0bb6ff435c4936c3422532aa071bd5b66e8)
Signed-off-by: Alassane Yattara <alassane.yattara@savoirfairelinux.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/toaster/tests')
-rw-r--r-- | bitbake/lib/toaster/tests/functional/test_create_new_project.py | 1 | ||||
-rw-r--r-- | bitbake/lib/toaster/tests/functional/test_project_page.py | 164 |
2 files changed, 79 insertions, 86 deletions
diff --git a/bitbake/lib/toaster/tests/functional/test_create_new_project.py b/bitbake/lib/toaster/tests/functional/test_create_new_project.py index dc7d1fc20b..bbda0cf4e6 100644 --- a/bitbake/lib/toaster/tests/functional/test_create_new_project.py +++ b/bitbake/lib/toaster/tests/functional/test_create_new_project.py | |||
@@ -16,6 +16,7 @@ from selenium.webdriver.common.by import By | |||
16 | 16 | ||
17 | 17 | ||
18 | @pytest.mark.django_db | 18 | @pytest.mark.django_db |
19 | @pytest.mark.order("last") | ||
19 | class TestCreateNewProject(SeleniumFunctionalTestCase): | 20 | class TestCreateNewProject(SeleniumFunctionalTestCase): |
20 | 21 | ||
21 | def _create_test_new_project( | 22 | def _create_test_new_project( |
diff --git a/bitbake/lib/toaster/tests/functional/test_project_page.py b/bitbake/lib/toaster/tests/functional/test_project_page.py index 03f64f8fef..077badb0c2 100644 --- a/bitbake/lib/toaster/tests/functional/test_project_page.py +++ b/bitbake/lib/toaster/tests/functional/test_project_page.py | |||
@@ -6,88 +6,89 @@ | |||
6 | # SPDX-License-Identifier: GPL-2.0-only | 6 | # SPDX-License-Identifier: GPL-2.0-only |
7 | # | 7 | # |
8 | 8 | ||
9 | import os | ||
9 | import random | 10 | import random |
10 | import string | 11 | import string |
12 | from unittest import skip | ||
11 | import pytest | 13 | import pytest |
12 | from time import sleep | ||
13 | from django.urls import reverse | 14 | from django.urls import reverse |
14 | from django.utils import timezone | 15 | from django.utils import timezone |
15 | from selenium.webdriver.common.keys import Keys | 16 | from selenium.webdriver.common.keys import Keys |
16 | from selenium.webdriver.support.select import Select | 17 | from selenium.webdriver.support.select import Select |
17 | from selenium.common.exceptions import NoSuchElementException, TimeoutException | 18 | from selenium.common.exceptions import TimeoutException |
18 | from tests.functional.functional_helpers import SeleniumFunctionalTestCase | 19 | from tests.functional.functional_helpers import SeleniumFunctionalTestCase |
19 | from orm.models import Build, Project, Target | 20 | from orm.models import Build, Project, Target |
20 | from selenium.webdriver.common.by import By | 21 | from selenium.webdriver.common.by import By |
21 | 22 | ||
23 | from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled | ||
24 | |||
22 | 25 | ||
23 | @pytest.mark.django_db | 26 | @pytest.mark.django_db |
27 | @pytest.mark.order("last") | ||
24 | class TestProjectPage(SeleniumFunctionalTestCase): | 28 | class TestProjectPage(SeleniumFunctionalTestCase): |
29 | project_id = None | ||
30 | PROJECT_NAME = 'TestProjectPage' | ||
25 | 31 | ||
26 | def setUp(self): | 32 | def _create_project(self, project_name): |
27 | super().setUp() | ||
28 | release = '3' | ||
29 | project_name = 'project_' + self.generate_random_string() | ||
30 | self._create_test_new_project( | ||
31 | project_name, | ||
32 | release, | ||
33 | False, | ||
34 | ) | ||
35 | |||
36 | def generate_random_string(self, length=10): | ||
37 | characters = string.ascii_letters + string.digits # alphabetic and numerical characters | ||
38 | random_string = ''.join(random.choice(characters) for _ in range(length)) | ||
39 | return random_string | ||
40 | |||
41 | def _create_test_new_project( | ||
42 | self, | ||
43 | project_name, | ||
44 | release, | ||
45 | merge_toaster_settings, | ||
46 | ): | ||
47 | """ Create/Test new project using: | 33 | """ Create/Test new project using: |
48 | - Project Name: Any string | 34 | - Project Name: Any string |
49 | - Release: Any string | 35 | - Release: Any string |
50 | - Merge Toaster settings: True or False | 36 | - Merge Toaster settings: True or False |
51 | """ | 37 | """ |
52 | self.get(reverse('newproject')) | 38 | self.get(reverse('newproject')) |
53 | self.driver.find_element(By.ID, | 39 | self.wait_until_visible('#new-project-name') |
54 | "new-project-name").send_keys(project_name) | 40 | self.find("#new-project-name").send_keys(project_name) |
55 | 41 | select = Select(self.find("#projectversion")) | |
56 | select = Select(self.find('#projectversion')) | 42 | select.select_by_value('3') |
57 | select.select_by_value(release) | ||
58 | 43 | ||
59 | # check merge toaster settings | 44 | # check merge toaster settings |
60 | checkbox = self.find('.checkbox-mergeattr') | 45 | checkbox = self.find('.checkbox-mergeattr') |
61 | if merge_toaster_settings: | 46 | if not checkbox.is_selected(): |
62 | if not checkbox.is_selected(): | 47 | checkbox.click() |
63 | checkbox.click() | 48 | |
49 | if self.PROJECT_NAME != 'TestProjectPage': | ||
50 | # Reset project name if it's not the default one | ||
51 | self.PROJECT_NAME = 'TestProjectPage' | ||
52 | |||
53 | self.find("#create-project-button").click() | ||
54 | |||
55 | try: | ||
56 | self.wait_until_visible('#hint-error-project-name') | ||
57 | url = reverse('project', args=(TestProjectPage.project_id, )) | ||
58 | self.get(url) | ||
59 | self.wait_until_visible('#config-nav', poll=3) | ||
60 | except TimeoutException: | ||
61 | self.wait_until_visible('#config-nav', poll=3) | ||
62 | |||
63 | def _random_string(self, length): | ||
64 | return ''.join( | ||
65 | random.choice(string.ascii_letters) for _ in range(length) | ||
66 | ) | ||
67 | |||
68 | def _navigate_to_project_page(self): | ||
69 | # Navigate to project page | ||
70 | if TestProjectPage.project_id is None: | ||
71 | self._create_project(project_name=self._random_string(10)) | ||
72 | current_url = self.driver.current_url | ||
73 | TestProjectPage.project_id = get_projectId_from_url(current_url) | ||
64 | else: | 74 | else: |
65 | if checkbox.is_selected(): | 75 | url = reverse('project', args=(TestProjectPage.project_id,)) |
66 | checkbox.click() | 76 | self.get(url) |
67 | 77 | self.wait_until_visible('#config-nav') | |
68 | self.driver.find_element(By.ID, "create-project-button").click() | ||
69 | 78 | ||
70 | def _get_create_builds(self, **kwargs): | 79 | def _get_create_builds(self, **kwargs): |
71 | """ Create a build and return the build object """ | 80 | """ Create a build and return the build object """ |
72 | # parameters for builds to associate with the projects | 81 | # parameters for builds to associate with the projects |
73 | now = timezone.now() | 82 | now = timezone.now() |
74 | release = '3' | ||
75 | project_name = 'projectmaster' | ||
76 | self._create_test_new_project( | ||
77 | project_name+"2", | ||
78 | release, | ||
79 | False, | ||
80 | ) | ||
81 | |||
82 | self.project1_build_success = { | 83 | self.project1_build_success = { |
83 | 'project': Project.objects.get(id=1), | 84 | 'project': Project.objects.get(id=TestProjectPage.project_id), |
84 | 'started_on': now, | 85 | 'started_on': now, |
85 | 'completed_on': now, | 86 | 'completed_on': now, |
86 | 'outcome': Build.SUCCEEDED | 87 | 'outcome': Build.SUCCEEDED |
87 | } | 88 | } |
88 | 89 | ||
89 | self.project1_build_failure = { | 90 | self.project1_build_failure = { |
90 | 'project': Project.objects.get(id=1), | 91 | 'project': Project.objects.get(id=TestProjectPage.project_id), |
91 | 'started_on': now, | 92 | 'started_on': now, |
92 | 'completed_on': now, | 93 | 'completed_on': now, |
93 | 'outcome': Build.FAILED | 94 | 'outcome': Build.FAILED |
@@ -180,9 +181,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
180 | 181 | ||
181 | def _navigate_to_config_nav(self, nav_id, nav_index): | 182 | def _navigate_to_config_nav(self, nav_id, nav_index): |
182 | # navigate to the project page | 183 | # navigate to the project page |
183 | url = reverse("project", args=(1,)) | 184 | self._navigate_to_project_page() |
184 | self.get(url) | ||
185 | self.wait_until_visible('#config-nav') | ||
186 | # click on "Software recipe" tab | 185 | # click on "Software recipe" tab |
187 | soft_recipe = self._get_config_nav_item(nav_index) | 186 | soft_recipe = self._get_config_nav_item(nav_index) |
188 | soft_recipe.click() | 187 | soft_recipe.click() |
@@ -211,29 +210,6 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
211 | if row_to_show not in to_skip: | 210 | if row_to_show not in to_skip: |
212 | test_show_rows(row_to_show, show_row_link) | 211 | test_show_rows(row_to_show, show_row_link) |
213 | 212 | ||
214 | def _wait_until_build(self, state): | ||
215 | timeout = 10 | ||
216 | start_time = 0 | ||
217 | while True: | ||
218 | if start_time > timeout: | ||
219 | raise TimeoutException( | ||
220 | f'Build did not reach {state} state within {timeout} seconds' | ||
221 | ) | ||
222 | try: | ||
223 | last_build_state = self.driver.find_element( | ||
224 | By.XPATH, | ||
225 | '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]', | ||
226 | ) | ||
227 | build_state = last_build_state.get_attribute( | ||
228 | 'data-build-state') | ||
229 | state_text = state.lower().split() | ||
230 | if any(x in str(build_state).lower() for x in state_text): | ||
231 | break | ||
232 | except NoSuchElementException: | ||
233 | continue | ||
234 | start_time += 1 | ||
235 | sleep(1) # take a breath and try again | ||
236 | |||
237 | def _mixin_test_table_search_input(self, **kwargs): | 213 | def _mixin_test_table_search_input(self, **kwargs): |
238 | input_selector, input_text, searchBtn_selector, table_selector, *_ = kwargs.values() | 214 | input_selector, input_text, searchBtn_selector, table_selector, *_ = kwargs.values() |
239 | # Test search input | 215 | # Test search input |
@@ -245,11 +221,19 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
245 | rows = self.find_all(f'#{table_selector} tbody tr') | 221 | rows = self.find_all(f'#{table_selector} tbody tr') |
246 | self.assertTrue(len(rows) > 0) | 222 | self.assertTrue(len(rows) > 0) |
247 | 223 | ||
224 | def test_create_project(self): | ||
225 | """ Create/Test new project using: | ||
226 | - Project Name: Any string | ||
227 | - Release: Any string | ||
228 | - Merge Toaster settings: True or False | ||
229 | """ | ||
230 | self._create_project(project_name=self.PROJECT_NAME) | ||
231 | |||
248 | def test_image_recipe_editColumn(self): | 232 | def test_image_recipe_editColumn(self): |
249 | """ Test the edit column feature in image recipe table on project page """ | 233 | """ Test the edit column feature in image recipe table on project page """ |
250 | self._get_create_builds(success=10, failure=10) | 234 | self._get_create_builds(success=10, failure=10) |
251 | 235 | ||
252 | url = reverse('projectimagerecipes', args=(1,)) | 236 | url = reverse('projectimagerecipes', args=(TestProjectPage.project_id,)) |
253 | self.get(url) | 237 | self.get(url) |
254 | self.wait_until_present('#imagerecipestable tbody tr') | 238 | self.wait_until_present('#imagerecipestable tbody tr') |
255 | 239 | ||
@@ -276,8 +260,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
276 | - AT RIGHT -> button "New project", displayed, clickable | 260 | - AT RIGHT -> button "New project", displayed, clickable |
277 | """ | 261 | """ |
278 | # navigate to the project page | 262 | # navigate to the project page |
279 | url = reverse("project", args=(1,)) | 263 | self._navigate_to_project_page() |
280 | self.get(url) | ||
281 | 264 | ||
282 | # check page header | 265 | # check page header |
283 | # AT LEFT -> Logo of Yocto project | 266 | # AT LEFT -> Logo of Yocto project |
@@ -360,8 +343,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
360 | - Check project name is changed | 343 | - Check project name is changed |
361 | """ | 344 | """ |
362 | # navigate to the project page | 345 | # navigate to the project page |
363 | url = reverse("project", args=(1,)) | 346 | self._navigate_to_project_page() |
364 | self.get(url) | ||
365 | 347 | ||
366 | # click on "Edit" icon button | 348 | # click on "Edit" icon button |
367 | self.wait_until_visible('#project-name-container') | 349 | self.wait_until_visible('#project-name-container') |
@@ -388,8 +370,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
388 | Check search box used to build recipes | 370 | Check search box used to build recipes |
389 | """ | 371 | """ |
390 | # navigate to the project page | 372 | # navigate to the project page |
391 | url = reverse("project", args=(1,)) | 373 | self._navigate_to_project_page() |
392 | self.get(url) | ||
393 | 374 | ||
394 | # check "configuration" tab | 375 | # check "configuration" tab |
395 | self.wait_until_visible('#topbar-configuration-tab') | 376 | self.wait_until_visible('#topbar-configuration-tab') |
@@ -397,7 +378,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
397 | self.assertTrue(config_tab.get_attribute('class') == 'active') | 378 | self.assertTrue(config_tab.get_attribute('class') == 'active') |
398 | self.assertTrue('Configuration' in str(config_tab.text)) | 379 | self.assertTrue('Configuration' in str(config_tab.text)) |
399 | self.assertTrue( | 380 | self.assertTrue( |
400 | f"/toastergui/project/1" in str(self.driver.current_url) | 381 | f"/toastergui/project/{TestProjectPage.project_id}" in str(self.driver.current_url) |
401 | ) | 382 | ) |
402 | 383 | ||
403 | def get_tabs(): | 384 | def get_tabs(): |
@@ -420,7 +401,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
420 | check_tab_link( | 401 | check_tab_link( |
421 | 1, | 402 | 1, |
422 | 'Builds', | 403 | 'Builds', |
423 | f"/toastergui/project/1/builds" | 404 | f"/toastergui/project/{TestProjectPage.project_id}/builds" |
424 | ) | 405 | ) |
425 | 406 | ||
426 | # check "Import layers" tab | 407 | # check "Import layers" tab |
@@ -429,7 +410,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
429 | check_tab_link( | 410 | check_tab_link( |
430 | 2, | 411 | 2, |
431 | 'Import layer', | 412 | 'Import layer', |
432 | f"/toastergui/project/1/importlayer" | 413 | f"/toastergui/project/{TestProjectPage.project_id}/importlayer" |
433 | ) | 414 | ) |
434 | 415 | ||
435 | # check "New custom image" tab | 416 | # check "New custom image" tab |
@@ -438,7 +419,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
438 | check_tab_link( | 419 | check_tab_link( |
439 | 3, | 420 | 3, |
440 | 'New custom image', | 421 | 'New custom image', |
441 | f"/toastergui/project/1/newcustomimage" | 422 | f"/toastergui/project/{TestProjectPage.project_id}/newcustomimage" |
442 | ) | 423 | ) |
443 | 424 | ||
444 | # check search box can be use to build recipes | 425 | # check search box can be use to build recipes |
@@ -480,12 +461,20 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
480 | '//td[@class="add-del-layers"]//a[1]' | 461 | '//td[@class="add-del-layers"]//a[1]' |
481 | ) | 462 | ) |
482 | build_btn.click() | 463 | build_btn.click() |
483 | self._wait_until_build('parsing starting cloning queued') | 464 | build_state = wait_until_build(self, 'parsing starting cloning queued') |
484 | lastest_builds = self.driver.find_elements( | 465 | lastest_builds = self.driver.find_elements( |
485 | By.XPATH, | 466 | By.XPATH, |
486 | '//div[@id="latest-builds"]/div' | 467 | '//div[@id="latest-builds"]/div' |
487 | ) | 468 | ) |
488 | self.assertTrue(len(lastest_builds) > 0) | 469 | self.assertTrue(len(lastest_builds) > 0) |
470 | last_build = lastest_builds[0] | ||
471 | cancel_button = last_build.find_element( | ||
472 | By.XPATH, | ||
473 | '//span[@class="cancel-build-btn pull-right alert-link"]', | ||
474 | ) | ||
475 | cancel_button.click() | ||
476 | if 'starting' not in build_state: # change build state when cancelled in starting state | ||
477 | wait_until_build_cancelled(self) | ||
489 | 478 | ||
490 | # check software recipe table feature(show/hide column, pagination) | 479 | # check software recipe table feature(show/hide column, pagination) |
491 | self._navigate_to_config_nav('softwarerecipestable', 4) | 480 | self._navigate_to_config_nav('softwarerecipestable', 4) |
@@ -547,6 +536,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
547 | searchBtn_selector='search-submit-machinestable', | 536 | searchBtn_selector='search-submit-machinestable', |
548 | table_selector='machinestable' | 537 | table_selector='machinestable' |
549 | ) | 538 | ) |
539 | self.wait_until_visible('#machinestable tbody tr', poll=3) | ||
550 | rows = self.find_all('#machinestable tbody tr') | 540 | rows = self.find_all('#machinestable tbody tr') |
551 | machine_to_add = rows[0] | 541 | machine_to_add = rows[0] |
552 | add_btn = machine_to_add.find_element(By.XPATH, '//td[@class="add-del-layers"]') | 542 | add_btn = machine_to_add.find_element(By.XPATH, '//td[@class="add-del-layers"]') |
@@ -593,6 +583,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
593 | table_selector='layerstable' | 583 | table_selector='layerstable' |
594 | ) | 584 | ) |
595 | # check "Add layer" button works | 585 | # check "Add layer" button works |
586 | self.wait_until_visible('#layerstable tbody tr', poll=3) | ||
596 | rows = self.find_all('#layerstable tbody tr') | 587 | rows = self.find_all('#layerstable tbody tr') |
597 | layer_to_add = rows[0] | 588 | layer_to_add = rows[0] |
598 | add_btn = layer_to_add.find_element( | 589 | add_btn = layer_to_add.find_element( |
@@ -601,7 +592,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
601 | ) | 592 | ) |
602 | add_btn.click() | 593 | add_btn.click() |
603 | # check modal is displayed | 594 | # check modal is displayed |
604 | self.wait_until_visible('#dependencies-modal', poll=2) | 595 | self.wait_until_visible('#dependencies-modal', poll=3) |
605 | list_dependencies = self.find_all('#dependencies-list li') | 596 | list_dependencies = self.find_all('#dependencies-list li') |
606 | # click on add-layers button | 597 | # click on add-layers button |
607 | add_layers_btn = self.driver.find_element( | 598 | add_layers_btn = self.driver.find_element( |
@@ -615,6 +606,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
615 | f'You have added {len(list_dependencies)+1} layers to your project: {input_text} and its dependencies' in str(change_notification.text) | 606 | f'You have added {len(list_dependencies)+1} layers to your project: {input_text} and its dependencies' in str(change_notification.text) |
616 | ) | 607 | ) |
617 | # check "Remove layer" button works | 608 | # check "Remove layer" button works |
609 | self.wait_until_visible('#layerstable tbody tr', poll=3) | ||
618 | rows = self.find_all('#layerstable tbody tr') | 610 | rows = self.find_all('#layerstable tbody tr') |
619 | layer_to_remove = rows[0] | 611 | layer_to_remove = rows[0] |
620 | remove_btn = layer_to_remove.find_element( | 612 | remove_btn = layer_to_remove.find_element( |
@@ -706,7 +698,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
706 | - Check layer summary | 698 | - Check layer summary |
707 | - Check layer description | 699 | - Check layer description |
708 | """ | 700 | """ |
709 | url = reverse("layerdetails", args=(1, 8)) | 701 | url = reverse("layerdetails", args=(TestProjectPage.project_id, 8)) |
710 | self.get(url) | 702 | self.get(url) |
711 | self.wait_until_visible('.page-header') | 703 | self.wait_until_visible('.page-header') |
712 | # check title is displayed | 704 | # check title is displayed |
@@ -765,7 +757,7 @@ class TestProjectPage(SeleniumFunctionalTestCase): | |||
765 | - Check recipe: name, summary, description, Version, Section, | 757 | - Check recipe: name, summary, description, Version, Section, |
766 | License, Approx. packages included, Approx. size, Recipe file | 758 | License, Approx. packages included, Approx. size, Recipe file |
767 | """ | 759 | """ |
768 | url = reverse("recipedetails", args=(1, 53428)) | 760 | url = reverse("recipedetails", args=(TestProjectPage.project_id, 53428)) |
769 | self.get(url) | 761 | self.get(url) |
770 | self.wait_until_visible('.page-header') | 762 | self.wait_until_visible('.page-header') |
771 | # check title is displayed | 763 | # check title is displayed |