diff options
| author | Alassane Yattara <alassane.yattara@savoirfairelinux.com> | 2023-12-08 02:53:18 +0100 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2023-12-08 17:17:42 +0000 |
| commit | d5a6e3b546e7d6691150a5906e531e3b3d6919ee (patch) | |
| tree | 10f87b155d065a3fbd5cd3e85e392129040c98fc /bitbake/lib/toaster/tests/functional | |
| parent | 49128ef8bad44bcd863548b240940d2b5a38e32e (diff) | |
| download | poky-d5a6e3b546e7d6691150a5906e531e3b3d6919ee.tar.gz | |
bitbake: toaster/tests: Refactorize tests/functional
- Split testcases from test_project_page_tab_config into tow files
- Added new testcases in test_project_config
- Test changing distro variable
- Test setting IMAGE_INSTALL:append variable
- Test setting PACKAGE_CLASSES variable
- Test creating new bitbake variable
(Bitbake rev: 649218c648b79a89b0e91aa80d8c9bf8fa2de645)
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/functional')
| -rw-r--r-- | bitbake/lib/toaster/tests/functional/test_project_config.py | 335 | ||||
| -rw-r--r-- | bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py | 495 |
2 files changed, 554 insertions, 276 deletions
diff --git a/bitbake/lib/toaster/tests/functional/test_project_config.py b/bitbake/lib/toaster/tests/functional/test_project_config.py new file mode 100644 index 0000000000..2d162d81e7 --- /dev/null +++ b/bitbake/lib/toaster/tests/functional/test_project_config.py | |||
| @@ -0,0 +1,335 @@ | |||
| 1 | #! /usr/bin/env python3 # | ||
| 2 | # BitBake Toaster UI tests implementation | ||
| 3 | # | ||
| 4 | # Copyright (C) 2023 Savoir-faire Linux | ||
| 5 | # | ||
| 6 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 7 | # | ||
| 8 | |||
| 9 | import string | ||
| 10 | import random | ||
| 11 | import pytest | ||
| 12 | from django.urls import reverse | ||
| 13 | from selenium.webdriver import Keys | ||
| 14 | from selenium.webdriver.support.select import Select | ||
| 15 | from selenium.common.exceptions import TimeoutException | ||
| 16 | from tests.functional.functional_helpers import SeleniumFunctionalTestCase | ||
| 17 | from selenium.webdriver.common.by import By | ||
| 18 | |||
| 19 | from .utils import get_projectId_from_url | ||
| 20 | |||
| 21 | |||
| 22 | @pytest.mark.django_db | ||
| 23 | @pytest.mark.order("last") | ||
| 24 | class TestProjectConfig(SeleniumFunctionalTestCase): | ||
| 25 | project_id = None | ||
| 26 | PROJECT_NAME = 'TestProjectConfig' | ||
| 27 | INVALID_PATH_START_TEXT = 'The directory path should either start with a /' | ||
| 28 | INVALID_PATH_CHAR_TEXT = 'The directory path cannot include spaces or ' \ | ||
| 29 | 'any of these characters' | ||
| 30 | |||
| 31 | def _create_project(self, project_name): | ||
| 32 | """ Create/Test new project using: | ||
| 33 | - Project Name: Any string | ||
| 34 | - Release: Any string | ||
| 35 | - Merge Toaster settings: True or False | ||
| 36 | """ | ||
| 37 | self.get(reverse('newproject')) | ||
| 38 | self.wait_until_visible('#new-project-name', poll=2) | ||
| 39 | self.find("#new-project-name").send_keys(project_name) | ||
| 40 | select = Select(self.find("#projectversion")) | ||
| 41 | select.select_by_value('3') | ||
| 42 | |||
| 43 | # check merge toaster settings | ||
| 44 | checkbox = self.find('.checkbox-mergeattr') | ||
| 45 | if not checkbox.is_selected(): | ||
| 46 | checkbox.click() | ||
| 47 | |||
| 48 | if self.PROJECT_NAME != 'TestProjectConfig': | ||
| 49 | # Reset project name if it's not the default one | ||
| 50 | self.PROJECT_NAME = 'TestProjectConfig' | ||
| 51 | |||
| 52 | self.find("#create-project-button").click() | ||
| 53 | |||
| 54 | try: | ||
| 55 | self.wait_until_visible('#hint-error-project-name', poll=2) | ||
| 56 | url = reverse('project', args=(TestProjectConfig.project_id, )) | ||
| 57 | self.get(url) | ||
| 58 | self.wait_until_visible('#config-nav', poll=3) | ||
| 59 | except TimeoutException: | ||
| 60 | self.wait_until_visible('#config-nav', poll=3) | ||
| 61 | |||
| 62 | def _random_string(self, length): | ||
| 63 | return ''.join( | ||
| 64 | random.choice(string.ascii_letters) for _ in range(length) | ||
| 65 | ) | ||
| 66 | |||
| 67 | def _get_config_nav_item(self, index): | ||
| 68 | config_nav = self.find('#config-nav') | ||
| 69 | return config_nav.find_elements(By.TAG_NAME, 'li')[index] | ||
| 70 | |||
| 71 | def _navigate_bbv_page(self): | ||
| 72 | """ Navigate to project BitBake variables page """ | ||
| 73 | # check if the menu is displayed | ||
| 74 | if TestProjectConfig.project_id is None: | ||
| 75 | self._create_project(project_name=self._random_string(10)) | ||
| 76 | current_url = self.driver.current_url | ||
| 77 | TestProjectConfig.project_id = get_projectId_from_url(current_url) | ||
| 78 | else: | ||
| 79 | url = reverse('projectconf', args=(TestProjectConfig.project_id,)) | ||
| 80 | self.get(url) | ||
| 81 | self.wait_until_visible('#config-nav', poll=3) | ||
| 82 | bbv_page_link = self._get_config_nav_item(9) | ||
| 83 | bbv_page_link.click() | ||
| 84 | self.wait_until_visible('#config-nav', poll=3) | ||
| 85 | |||
| 86 | def test_no_underscore_iamgefs_type(self): | ||
| 87 | """ | ||
| 88 | Should not accept IMAGEFS_TYPE with an underscore | ||
| 89 | """ | ||
| 90 | self._navigate_bbv_page() | ||
| 91 | imagefs_type = "foo_bar" | ||
| 92 | |||
| 93 | self.wait_until_visible('#change-image_fstypes-icon', poll=2) | ||
| 94 | |||
| 95 | self.click('#change-image_fstypes-icon') | ||
| 96 | |||
| 97 | self.enter_text('#new-imagefs_types', imagefs_type) | ||
| 98 | |||
| 99 | element = self.wait_until_visible('#hintError-image-fs_type', poll=2) | ||
| 100 | |||
| 101 | self.assertTrue(("A valid image type cannot include underscores" in element.text), | ||
| 102 | "Did not find underscore error message") | ||
| 103 | |||
| 104 | def test_checkbox_verification(self): | ||
| 105 | """ | ||
| 106 | Should automatically check the checkbox if user enters value | ||
| 107 | text box, if value is there in the checkbox. | ||
| 108 | """ | ||
| 109 | self._navigate_bbv_page() | ||
| 110 | |||
| 111 | imagefs_type = "btrfs" | ||
| 112 | |||
| 113 | self.wait_until_visible('#change-image_fstypes-icon', poll=2) | ||
| 114 | |||
| 115 | self.click('#change-image_fstypes-icon') | ||
| 116 | |||
| 117 | self.enter_text('#new-imagefs_types', imagefs_type) | ||
| 118 | |||
| 119 | checkboxes = self.driver.find_elements(By.XPATH, "//input[@class='fs-checkbox-fstypes']") | ||
| 120 | |||
| 121 | for checkbox in checkboxes: | ||
| 122 | if checkbox.get_attribute("value") == "btrfs": | ||
| 123 | self.assertEqual(checkbox.is_selected(), True) | ||
| 124 | |||
| 125 | def test_textbox_with_checkbox_verification(self): | ||
| 126 | """ | ||
| 127 | Should automatically add or remove value in textbox, if user checks | ||
| 128 | or unchecks checkboxes. | ||
| 129 | """ | ||
| 130 | self._navigate_bbv_page() | ||
| 131 | |||
| 132 | self.wait_until_visible('#change-image_fstypes-icon', poll=2) | ||
| 133 | |||
| 134 | self.click('#change-image_fstypes-icon') | ||
| 135 | |||
| 136 | checkboxes_selector = '.fs-checkbox-fstypes' | ||
| 137 | |||
| 138 | self.wait_until_visible(checkboxes_selector, poll=2) | ||
| 139 | checkboxes = self.find_all(checkboxes_selector) | ||
| 140 | |||
| 141 | for checkbox in checkboxes: | ||
| 142 | if checkbox.get_attribute("value") == "cpio": | ||
| 143 | checkbox.click() | ||
| 144 | element = self.driver.find_element(By.ID, 'new-imagefs_types') | ||
| 145 | |||
| 146 | self.wait_until_visible('#new-imagefs_types', poll=2) | ||
| 147 | |||
| 148 | self.assertTrue(("cpio" in element.get_attribute('value'), | ||
| 149 | "Imagefs not added into the textbox")) | ||
| 150 | checkbox.click() | ||
| 151 | self.assertTrue(("cpio" not in element.text), | ||
| 152 | "Image still present in the textbox") | ||
| 153 | |||
| 154 | def test_set_download_dir(self): | ||
| 155 | """ | ||
| 156 | Validate the allowed and disallowed types in the directory field for | ||
| 157 | DL_DIR | ||
| 158 | """ | ||
| 159 | self._navigate_bbv_page() | ||
| 160 | |||
| 161 | # activate the input to edit download dir | ||
| 162 | try: | ||
| 163 | change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2) | ||
| 164 | except TimeoutException: | ||
| 165 | # If download dir is not displayed, test is skipped | ||
| 166 | return True | ||
| 167 | change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2) | ||
| 168 | change_dl_dir_btn.click() | ||
| 169 | |||
| 170 | # downloads dir path doesn't start with / or ${...} | ||
| 171 | input_field = self.wait_until_visible('#new-dl_dir', poll=2) | ||
| 172 | input_field.clear() | ||
| 173 | self.enter_text('#new-dl_dir', 'home/foo') | ||
| 174 | element = self.wait_until_visible('#hintError-initialChar-dl_dir', poll=2) | ||
| 175 | |||
| 176 | msg = 'downloads directory path starts with invalid character but ' \ | ||
| 177 | 'treated as valid' | ||
| 178 | self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg) | ||
| 179 | |||
| 180 | # downloads dir path has a space | ||
| 181 | self.driver.find_element(By.ID, 'new-dl_dir').clear() | ||
| 182 | self.enter_text('#new-dl_dir', '/foo/bar a') | ||
| 183 | |||
| 184 | element = self.wait_until_visible('#hintError-dl_dir', poll=2) | ||
| 185 | msg = 'downloads directory path characters invalid but treated as valid' | ||
| 186 | self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) | ||
| 187 | |||
| 188 | # downloads dir path starts with ${...} but has a space | ||
| 189 | self.driver.find_element(By.ID,'new-dl_dir').clear() | ||
| 190 | self.enter_text('#new-dl_dir', '${TOPDIR}/down foo') | ||
| 191 | |||
| 192 | element = self.wait_until_visible('#hintError-dl_dir', poll=2) | ||
| 193 | msg = 'downloads directory path characters invalid but treated as valid' | ||
| 194 | self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) | ||
| 195 | |||
| 196 | # downloads dir path starts with / | ||
| 197 | self.driver.find_element(By.ID,'new-dl_dir').clear() | ||
| 198 | self.enter_text('#new-dl_dir', '/bar/foo') | ||
| 199 | |||
| 200 | hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir') | ||
| 201 | self.assertEqual(hidden_element.is_displayed(), False, | ||
| 202 | 'downloads directory path valid but treated as invalid') | ||
| 203 | |||
| 204 | # downloads dir path starts with ${...} | ||
| 205 | self.driver.find_element(By.ID,'new-dl_dir').clear() | ||
| 206 | self.enter_text('#new-dl_dir', '${TOPDIR}/down') | ||
| 207 | |||
| 208 | hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir') | ||
| 209 | self.assertEqual(hidden_element.is_displayed(), False, | ||
| 210 | 'downloads directory path valid but treated as invalid') | ||
| 211 | |||
| 212 | def test_set_sstate_dir(self): | ||
| 213 | """ | ||
| 214 | Validate the allowed and disallowed types in the directory field for | ||
| 215 | SSTATE_DIR | ||
| 216 | """ | ||
| 217 | self._navigate_bbv_page() | ||
| 218 | |||
| 219 | try: | ||
| 220 | self.wait_until_visible('#change-sstate_dir-icon', poll=2) | ||
| 221 | self.click('#change-sstate_dir-icon') | ||
| 222 | except TimeoutException: | ||
| 223 | # If sstate_dir is not displayed, test is skipped | ||
| 224 | return True | ||
| 225 | |||
| 226 | # path doesn't start with / or ${...} | ||
| 227 | input_field = self.wait_until_visible('#new-sstate_dir', poll=2) | ||
| 228 | input_field.clear() | ||
| 229 | self.enter_text('#new-sstate_dir', 'home/foo') | ||
| 230 | element = self.wait_until_visible('#hintError-initialChar-sstate_dir', poll=2) | ||
| 231 | |||
| 232 | msg = 'sstate directory path starts with invalid character but ' \ | ||
| 233 | 'treated as valid' | ||
| 234 | self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg) | ||
| 235 | |||
| 236 | # path has a space | ||
| 237 | self.driver.find_element(By.ID, 'new-sstate_dir').clear() | ||
| 238 | self.enter_text('#new-sstate_dir', '/foo/bar a') | ||
| 239 | |||
| 240 | element = self.wait_until_visible('#hintError-sstate_dir', poll=2) | ||
| 241 | msg = 'sstate directory path characters invalid but treated as valid' | ||
| 242 | self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) | ||
| 243 | |||
| 244 | # path starts with ${...} but has a space | ||
| 245 | self.driver.find_element(By.ID,'new-sstate_dir').clear() | ||
| 246 | self.enter_text('#new-sstate_dir', '${TOPDIR}/down foo') | ||
| 247 | |||
| 248 | element = self.wait_until_visible('#hintError-sstate_dir', poll=2) | ||
| 249 | msg = 'sstate directory path characters invalid but treated as valid' | ||
| 250 | self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) | ||
| 251 | |||
| 252 | # path starts with / | ||
| 253 | self.driver.find_element(By.ID,'new-sstate_dir').clear() | ||
| 254 | self.enter_text('#new-sstate_dir', '/bar/foo') | ||
| 255 | |||
| 256 | hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir') | ||
| 257 | self.assertEqual(hidden_element.is_displayed(), False, | ||
| 258 | 'sstate directory path valid but treated as invalid') | ||
| 259 | |||
| 260 | # paths starts with ${...} | ||
| 261 | self.driver.find_element(By.ID, 'new-sstate_dir').clear() | ||
| 262 | self.enter_text('#new-sstate_dir', '${TOPDIR}/down') | ||
| 263 | |||
| 264 | hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir') | ||
| 265 | self.assertEqual(hidden_element.is_displayed(), False, | ||
| 266 | 'sstate directory path valid but treated as invalid') | ||
| 267 | |||
| 268 | def _change_bbv_value(self, **kwargs): | ||
| 269 | var_name, field, btn_id, input_id, value, save_btn, *_ = kwargs.values() | ||
| 270 | """ Change bitbake variable value """ | ||
| 271 | self._navigate_bbv_page() | ||
| 272 | self.wait_until_visible(f'#{btn_id}', poll=2) | ||
| 273 | if kwargs.get('new_variable'): | ||
| 274 | self.find(f"#{btn_id}").clear() | ||
| 275 | self.enter_text(f"#{btn_id}", f"{var_name}") | ||
| 276 | else: | ||
| 277 | self.click(f'#{btn_id}') | ||
| 278 | self.wait_until_visible(f'#{input_id}', poll=2) | ||
| 279 | |||
| 280 | if kwargs.get('is_select'): | ||
| 281 | select = Select(self.find(f'#{input_id}')) | ||
| 282 | select.select_by_visible_text(value) | ||
| 283 | else: | ||
| 284 | self.find(f"#{input_id}").clear() | ||
| 285 | self.enter_text(f'#{input_id}', f'{value}') | ||
| 286 | self.click(f'#{save_btn}') | ||
| 287 | value_displayed = str(self.wait_until_visible(f'#{field}').text).lower() | ||
| 288 | msg = f'{var_name} variable not changed' | ||
| 289 | self.assertTrue(str(value).lower() in value_displayed, msg) | ||
| 290 | |||
| 291 | def test_change_distro_var(self): | ||
| 292 | """ Test changing distro variable """ | ||
| 293 | self._change_bbv_value( | ||
| 294 | var_name='DISTRO', | ||
| 295 | field='distro', | ||
| 296 | btn_id='change-distro-icon', | ||
| 297 | input_id='new-distro', | ||
| 298 | value='poky-changed', | ||
| 299 | save_btn="apply-change-distro", | ||
| 300 | ) | ||
| 301 | |||
| 302 | def test_set_image_install_append_var(self): | ||
| 303 | """ Test setting IMAGE_INSTALL:append variable """ | ||
| 304 | self._change_bbv_value( | ||
| 305 | var_name='IMAGE_INSTALL:append', | ||
| 306 | field='image_install', | ||
| 307 | btn_id='change-image_install-icon', | ||
| 308 | input_id='new-image_install', | ||
| 309 | value='bash, apt, busybox', | ||
| 310 | save_btn="apply-change-image_install", | ||
| 311 | ) | ||
| 312 | |||
| 313 | def test_set_package_classes_var(self): | ||
| 314 | """ Test setting PACKAGE_CLASSES variable """ | ||
| 315 | self._change_bbv_value( | ||
| 316 | var_name='PACKAGE_CLASSES', | ||
| 317 | field='package_classes', | ||
| 318 | btn_id='change-package_classes-icon', | ||
| 319 | input_id='package_classes-select', | ||
| 320 | value='package_deb', | ||
| 321 | save_btn="apply-change-package_classes", | ||
| 322 | is_select=True, | ||
| 323 | ) | ||
| 324 | |||
| 325 | def test_create_new_bbv(self): | ||
| 326 | """ Test creating new bitbake variable """ | ||
| 327 | self._change_bbv_value( | ||
| 328 | var_name='New_Custom_Variable', | ||
| 329 | field='configvar-list', | ||
| 330 | btn_id='variable', | ||
| 331 | input_id='value', | ||
| 332 | value='new variable value', | ||
| 333 | save_btn="add-configvar-button", | ||
| 334 | new_variable=True | ||
| 335 | ) | ||
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 23012d7865..d911ff00d4 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 | |||
| @@ -6,87 +6,81 @@ | |||
| 6 | # SPDX-License-Identifier: GPL-2.0-only | 6 | # SPDX-License-Identifier: GPL-2.0-only |
| 7 | # | 7 | # |
| 8 | 8 | ||
| 9 | from time import sleep | 9 | import string |
| 10 | import random | ||
| 10 | import pytest | 11 | import pytest |
| 11 | from django.utils import timezone | ||
| 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 NoSuchElementException | 15 | from selenium.common.exceptions import TimeoutException |
| 16 | from orm.models import Build, Project, Target | 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 |
| 19 | 19 | ||
| 20 | from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled | ||
| 21 | |||
| 20 | 22 | ||
| 21 | @pytest.mark.django_db | 23 | @pytest.mark.django_db |
| 24 | @pytest.mark.order("last") | ||
| 22 | class TestProjectConfigTab(SeleniumFunctionalTestCase): | 25 | class TestProjectConfigTab(SeleniumFunctionalTestCase): |
| 26 | PROJECT_NAME = 'TestProjectConfigTab' | ||
| 27 | project_id = None | ||
| 23 | 28 | ||
| 24 | def setUp(self): | 29 | def _create_project(self, project_name): |
| 25 | self.recipe = None | ||
| 26 | super().setUp() | ||
| 27 | release = '3' | ||
| 28 | project_name = 'projectmaster' | ||
| 29 | self._create_test_new_project( | ||
| 30 | project_name, | ||
| 31 | release, | ||
| 32 | False, | ||
| 33 | ) | ||
| 34 | |||
| 35 | def _create_test_new_project( | ||
| 36 | self, | ||
| 37 | project_name, | ||
| 38 | release, | ||
| 39 | merge_toaster_settings, | ||
| 40 | ): | ||
| 41 | """ Create/Test new project using: | 30 | """ Create/Test new project using: |
| 42 | - Project Name: Any string | 31 | - Project Name: Any string |
| 43 | - Release: Any string | 32 | - Release: Any string |
| 44 | - Merge Toaster settings: True or False | 33 | - Merge Toaster settings: True or False |
| 45 | """ | 34 | """ |
| 46 | self.get(reverse('newproject')) | 35 | self.get(reverse('newproject')) |
| 47 | self.driver.find_element(By.ID, | 36 | self.wait_until_visible('#new-project-name') |
| 48 | "new-project-name").send_keys(project_name) | 37 | self.find("#new-project-name").send_keys(project_name) |
| 49 | 38 | select = Select(self.find("#projectversion")) | |
| 50 | select = Select(self.find('#projectversion')) | 39 | select.select_by_value('3') |
| 51 | select.select_by_value(release) | ||
| 52 | 40 | ||
| 53 | # check merge toaster settings | 41 | # check merge toaster settings |
| 54 | checkbox = self.find('.checkbox-mergeattr') | 42 | checkbox = self.find('.checkbox-mergeattr') |
| 55 | if merge_toaster_settings: | 43 | if not checkbox.is_selected(): |
| 56 | if not checkbox.is_selected(): | 44 | checkbox.click() |
| 57 | checkbox.click() | 45 | |
| 46 | if self.PROJECT_NAME != 'TestProjectConfigTab': | ||
| 47 | # Reset project name if it's not the default one | ||
| 48 | self.PROJECT_NAME = 'TestProjectConfigTab' | ||
| 49 | |||
| 50 | self.find("#create-project-button").click() | ||
| 51 | |||
| 52 | try: | ||
| 53 | self.wait_until_visible('#hint-error-project-name') | ||
| 54 | url = reverse('project', args=(TestProjectConfigTab.project_id, )) | ||
| 55 | self.get(url) | ||
| 56 | self.wait_until_visible('#config-nav', poll=3) | ||
| 57 | except TimeoutException: | ||
| 58 | self.wait_until_visible('#config-nav', poll=3) | ||
| 59 | |||
| 60 | def _random_string(self, length): | ||
| 61 | return ''.join( | ||
| 62 | random.choice(string.ascii_letters) for _ in range(length) | ||
| 63 | ) | ||
| 64 | |||
| 65 | def _navigate_to_project_page(self): | ||
| 66 | # Navigate to project page | ||
| 67 | if TestProjectConfigTab.project_id is None: | ||
| 68 | self._create_project(project_name=self._random_string(10)) | ||
| 69 | current_url = self.driver.current_url | ||
| 70 | TestProjectConfigTab.project_id = get_projectId_from_url(current_url) | ||
| 58 | else: | 71 | else: |
| 59 | if checkbox.is_selected(): | 72 | url = reverse('project', args=(TestProjectConfigTab.project_id,)) |
| 60 | checkbox.click() | 73 | self.get(url) |
| 61 | 74 | self.wait_until_visible('#config-nav') | |
| 62 | self.driver.find_element(By.ID, "create-project-button").click() | ||
| 63 | |||
| 64 | @classmethod | ||
| 65 | def _wait_until_build(cls, state): | ||
| 66 | while True: | ||
| 67 | try: | ||
| 68 | last_build_state = cls.driver.find_element( | ||
| 69 | By.XPATH, | ||
| 70 | '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]', | ||
| 71 | ) | ||
| 72 | build_state = last_build_state.get_attribute( | ||
| 73 | 'data-build-state') | ||
| 74 | state_text = state.lower().split() | ||
| 75 | if any(x in str(build_state).lower() for x in state_text): | ||
| 76 | break | ||
| 77 | except NoSuchElementException: | ||
| 78 | continue | ||
| 79 | sleep(1) | ||
| 80 | 75 | ||
| 81 | def _create_builds(self): | 76 | def _create_builds(self): |
| 82 | # check search box can be use to build recipes | 77 | # check search box can be use to build recipes |
| 83 | search_box = self.find('#build-input') | 78 | search_box = self.find('#build-input') |
| 84 | search_box.send_keys('core-image-minimal') | 79 | search_box.send_keys('core-image-minimal') |
| 85 | self.find('#build-button').click() | 80 | self.find('#build-button').click() |
| 86 | sleep(1) | ||
| 87 | self.wait_until_visible('#latest-builds') | 81 | self.wait_until_visible('#latest-builds') |
| 88 | # loop until reach the parsing state | 82 | # loop until reach the parsing state |
| 89 | self._wait_until_build('parsing starting cloning') | 83 | build_state = wait_until_build(self, 'parsing starting cloning') |
| 90 | lastest_builds = self.driver.find_elements( | 84 | lastest_builds = self.driver.find_elements( |
| 91 | By.XPATH, | 85 | By.XPATH, |
| 92 | '//div[@id="latest-builds"]/div', | 86 | '//div[@id="latest-builds"]/div', |
| @@ -100,8 +94,9 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 100 | '//span[@class="cancel-build-btn pull-right alert-link"]', | 94 | '//span[@class="cancel-build-btn pull-right alert-link"]', |
| 101 | ) | 95 | ) |
| 102 | cancel_button.click() | 96 | cancel_button.click() |
| 103 | sleep(1) | 97 | if 'starting' not in build_state: # change build state when cancelled in starting state |
| 104 | self._wait_until_build('cancelled') | 98 | wait_until_build_cancelled(self) |
| 99 | return build_state | ||
| 105 | 100 | ||
| 106 | def _get_tabs(self): | 101 | def _get_tabs(self): |
| 107 | # tabs links list | 102 | # tabs links list |
| @@ -114,64 +109,6 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 114 | config_nav = self.find('#config-nav') | 109 | config_nav = self.find('#config-nav') |
| 115 | return config_nav.find_elements(By.TAG_NAME, 'li')[index] | 110 | return config_nav.find_elements(By.TAG_NAME, 'li')[index] |
| 116 | 111 | ||
| 117 | def _get_create_builds(self, **kwargs): | ||
| 118 | """ Create a build and return the build object """ | ||
| 119 | # parameters for builds to associate with the projects | ||
| 120 | now = timezone.now() | ||
| 121 | release = '3' | ||
| 122 | project_name = 'projectmaster' | ||
| 123 | self._create_test_new_project( | ||
| 124 | project_name+"2", | ||
| 125 | release, | ||
| 126 | False, | ||
| 127 | ) | ||
| 128 | |||
| 129 | self.project1_build_success = { | ||
| 130 | 'project': Project.objects.get(id=1), | ||
| 131 | 'started_on': now, | ||
| 132 | 'completed_on': now, | ||
| 133 | 'outcome': Build.SUCCEEDED | ||
| 134 | } | ||
| 135 | |||
| 136 | self.project1_build_failure = { | ||
| 137 | 'project': Project.objects.get(id=1), | ||
| 138 | 'started_on': now, | ||
| 139 | 'completed_on': now, | ||
| 140 | 'outcome': Build.FAILED | ||
| 141 | } | ||
| 142 | build1 = Build.objects.create(**self.project1_build_success) | ||
| 143 | build2 = Build.objects.create(**self.project1_build_failure) | ||
| 144 | |||
| 145 | # add some targets to these builds so they have recipe links | ||
| 146 | # (and so we can find the row in the ToasterTable corresponding to | ||
| 147 | # a particular build) | ||
| 148 | Target.objects.create(build=build1, target='foo') | ||
| 149 | Target.objects.create(build=build2, target='bar') | ||
| 150 | |||
| 151 | if kwargs: | ||
| 152 | # Create kwargs.get('success') builds with success status with target | ||
| 153 | # and kwargs.get('failure') builds with failure status with target | ||
| 154 | for i in range(kwargs.get('success', 0)): | ||
| 155 | now = timezone.now() | ||
| 156 | self.project1_build_success['started_on'] = now | ||
| 157 | self.project1_build_success[ | ||
| 158 | 'completed_on'] = now - timezone.timedelta(days=i) | ||
| 159 | build = Build.objects.create(**self.project1_build_success) | ||
| 160 | Target.objects.create(build=build, | ||
| 161 | target=f'{i}_success_recipe', | ||
| 162 | task=f'{i}_success_task') | ||
| 163 | |||
| 164 | for i in range(kwargs.get('failure', 0)): | ||
| 165 | now = timezone.now() | ||
| 166 | self.project1_build_failure['started_on'] = now | ||
| 167 | self.project1_build_failure[ | ||
| 168 | 'completed_on'] = now - timezone.timedelta(days=i) | ||
| 169 | build = Build.objects.create(**self.project1_build_failure) | ||
| 170 | Target.objects.create(build=build, | ||
| 171 | target=f'{i}_fail_recipe', | ||
| 172 | task=f'{i}_fail_task') | ||
| 173 | return build1, build2 | ||
| 174 | |||
| 175 | def test_project_config_nav(self): | 112 | def test_project_config_nav(self): |
| 176 | """ Test project config tab navigation: | 113 | """ Test project config tab navigation: |
| 177 | - Check if the menu is displayed and contains the right elements: | 114 | - Check if the menu is displayed and contains the right elements: |
| @@ -188,13 +125,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 188 | - Actions | 125 | - Actions |
| 189 | - Delete project | 126 | - Delete project |
| 190 | """ | 127 | """ |
| 191 | # navigate to the project page | 128 | self._navigate_to_project_page() |
| 192 | url = reverse("project", args=(1,)) | ||
| 193 | self.get(url) | ||
| 194 | |||
| 195 | # check if the menu is displayed | ||
| 196 | self.wait_until_visible('#config-nav') | ||
| 197 | |||
| 198 | def _get_config_nav_item(index): | 129 | def _get_config_nav_item(index): |
| 199 | config_nav = self.find('#config-nav') | 130 | config_nav = self.find('#config-nav') |
| 200 | return config_nav.find_elements(By.TAG_NAME, 'li')[index] | 131 | return config_nav.find_elements(By.TAG_NAME, 'li')[index] |
| @@ -221,14 +152,14 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 221 | self.assertTrue("actions" in str(actions.text).lower()) | 152 | self.assertTrue("actions" in str(actions.text).lower()) |
| 222 | 153 | ||
| 223 | conf_nav_list = [ | 154 | conf_nav_list = [ |
| 224 | [0, 'Configuration', f"/toastergui/project/1"], # config | 155 | [0, 'Configuration', f"/toastergui/project/{TestProjectConfigTab.project_id}"], # config |
| 225 | [2, 'Custom images', f"/toastergui/project/1/customimages"], # custom images | 156 | [2, 'Custom images', f"/toastergui/project/{TestProjectConfigTab.project_id}/customimages"], # custom images |
| 226 | [3, 'Image recipes', f"/toastergui/project/1/images"], # image recipes | 157 | [3, 'Image recipes', f"/toastergui/project/{TestProjectConfigTab.project_id}/images"], # image recipes |
| 227 | [4, 'Software recipes', f"/toastergui/project/1/softwarerecipes"], # software recipes | 158 | [4, 'Software recipes', f"/toastergui/project/{TestProjectConfigTab.project_id}/softwarerecipes"], # software recipes |
| 228 | [5, 'Machines', f"/toastergui/project/1/machines"], # machines | 159 | [5, 'Machines', f"/toastergui/project/{TestProjectConfigTab.project_id}/machines"], # machines |
| 229 | [6, 'Layers', f"/toastergui/project/1/layers"], # layers | 160 | [6, 'Layers', f"/toastergui/project/{TestProjectConfigTab.project_id}/layers"], # layers |
| 230 | [7, 'Distro', f"/toastergui/project/1/distro"], # distro | 161 | [7, 'Distros', f"/toastergui/project/{TestProjectConfigTab.project_id}/distros"], # distro |
| 231 | [9, 'BitBake variables', f"/toastergui/project/1/configuration"], # bitbake variables | 162 | # [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTab.project_id}/configuration"], # bitbake variables |
| 232 | ] | 163 | ] |
| 233 | for index, item_name, url in conf_nav_list: | 164 | for index, item_name, url in conf_nav_list: |
| 234 | item = _get_config_nav_item(index) | 165 | item = _get_config_nav_item(index) |
| @@ -236,6 +167,96 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 236 | item.click() | 167 | item.click() |
| 237 | check_config_nav_item(index, item_name, url) | 168 | check_config_nav_item(index, item_name, url) |
| 238 | 169 | ||
| 170 | def test_image_recipe_editColumn(self): | ||
| 171 | """ Test the edit column feature in image recipe table on project page """ | ||
| 172 | def test_edit_column(check_box_id): | ||
| 173 | # Check that we can hide/show table column | ||
| 174 | check_box = self.find(f'#{check_box_id}') | ||
| 175 | th_class = str(check_box_id).replace('checkbox-', '') | ||
| 176 | if check_box.is_selected(): | ||
| 177 | # check if column is visible in table | ||
| 178 | self.assertTrue( | ||
| 179 | self.find( | ||
| 180 | f'#imagerecipestable thead th.{th_class}' | ||
| 181 | ).is_displayed(), | ||
| 182 | f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table" | ||
| 183 | ) | ||
| 184 | check_box.click() | ||
| 185 | # check if column is hidden in table | ||
| 186 | self.assertFalse( | ||
| 187 | self.find( | ||
| 188 | f'#imagerecipestable thead th.{th_class}' | ||
| 189 | ).is_displayed(), | ||
| 190 | f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table" | ||
| 191 | ) | ||
| 192 | else: | ||
| 193 | # check if column is hidden in table | ||
| 194 | self.assertFalse( | ||
| 195 | self.find( | ||
| 196 | f'#imagerecipestable thead th.{th_class}' | ||
| 197 | ).is_displayed(), | ||
| 198 | f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table" | ||
| 199 | ) | ||
| 200 | check_box.click() | ||
| 201 | # check if column is visible in table | ||
| 202 | self.assertTrue( | ||
| 203 | self.find( | ||
| 204 | f'#imagerecipestable thead th.{th_class}' | ||
| 205 | ).is_displayed(), | ||
| 206 | f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table" | ||
| 207 | ) | ||
| 208 | |||
| 209 | self._navigate_to_project_page() | ||
| 210 | # navigate to project image recipe page | ||
| 211 | recipe_image_page_link = self._get_config_nav_item(3) | ||
| 212 | recipe_image_page_link.click() | ||
| 213 | self.wait_until_present('#imagerecipestable tbody tr') | ||
| 214 | |||
| 215 | # Check edit column | ||
| 216 | edit_column = self.find('#edit-columns-button') | ||
| 217 | self.assertTrue(edit_column.is_displayed()) | ||
| 218 | edit_column.click() | ||
| 219 | # Check dropdown is visible | ||
| 220 | self.wait_until_visible('ul.dropdown-menu.editcol') | ||
| 221 | |||
| 222 | # Check that we can hide the edit column | ||
| 223 | test_edit_column('checkbox-get_description_or_summary') | ||
| 224 | test_edit_column('checkbox-layer_version__get_vcs_reference') | ||
| 225 | test_edit_column('checkbox-layer_version__layer__name') | ||
| 226 | test_edit_column('checkbox-license') | ||
| 227 | test_edit_column('checkbox-recipe-file') | ||
| 228 | test_edit_column('checkbox-section') | ||
| 229 | test_edit_column('checkbox-version') | ||
| 230 | |||
| 231 | def test_image_recipe_show_rows(self): | ||
| 232 | """ Test the show rows feature in image recipe table on project page """ | ||
| 233 | def test_show_rows(row_to_show, show_row_link): | ||
| 234 | # Check that we can show rows == row_to_show | ||
| 235 | show_row_link.select_by_value(str(row_to_show)) | ||
| 236 | self.wait_until_visible('#imagerecipestable tbody tr') | ||
| 237 | self.assertTrue( | ||
| 238 | len(self.find_all('#imagerecipestable tbody tr')) == row_to_show | ||
| 239 | ) | ||
| 240 | |||
| 241 | self._navigate_to_project_page() | ||
| 242 | # navigate to project image recipe page | ||
| 243 | recipe_image_page_link = self._get_config_nav_item(3) | ||
| 244 | recipe_image_page_link.click() | ||
| 245 | self.wait_until_present('#imagerecipestable tbody tr') | ||
| 246 | |||
| 247 | show_rows = self.driver.find_elements( | ||
| 248 | By.XPATH, | ||
| 249 | '//select[@class="form-control pagesize-imagerecipestable"]' | ||
| 250 | ) | ||
| 251 | # Check show rows | ||
| 252 | for show_row_link in show_rows: | ||
| 253 | show_row_link = Select(show_row_link) | ||
| 254 | test_show_rows(10, show_row_link) | ||
| 255 | test_show_rows(25, show_row_link) | ||
| 256 | test_show_rows(50, show_row_link) | ||
| 257 | test_show_rows(100, show_row_link) | ||
| 258 | test_show_rows(150, show_row_link) | ||
| 259 | |||
| 239 | def test_project_config_tab_right_section(self): | 260 | def test_project_config_tab_right_section(self): |
| 240 | """ Test project config tab right section contains five blocks: | 261 | """ Test project config tab right section contains five blocks: |
| 241 | - Machine: | 262 | - Machine: |
| @@ -257,35 +278,36 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 257 | - meta-poky | 278 | - meta-poky |
| 258 | - meta-yocto-bsp | 279 | - meta-yocto-bsp |
| 259 | """ | 280 | """ |
| 260 | # navigate to the project page | 281 | # Create a new project for this test |
| 261 | url = reverse("project", args=(1,)) | 282 | project_name = self._random_string(10) |
| 262 | self.get(url) | 283 | self._create_project(project_name=project_name) |
| 263 | 284 | current_url = self.driver.current_url | |
| 285 | TestProjectConfigTab.project_id = get_projectId_from_url(current_url) | ||
| 286 | url = current_url.split('?')[0] | ||
| 264 | # check if the menu is displayed | 287 | # check if the menu is displayed |
| 265 | self.wait_until_visible('#project-page') | 288 | self.wait_until_visible('#project-page') |
| 266 | block_l = self.driver.find_element( | 289 | block_l = self.driver.find_element( |
| 267 | By.XPATH, '//*[@id="project-page"]/div[2]') | 290 | By.XPATH, '//*[@id="project-page"]/div[2]') |
| 268 | machine = self.find('#machine-section') | ||
| 269 | distro = self.find('#distro-section') | ||
| 270 | most_built_recipes = self.driver.find_element( | 291 | most_built_recipes = self.driver.find_element( |
| 271 | By.XPATH, '//*[@id="project-page"]/div[1]/div[3]') | 292 | By.XPATH, '//*[@id="project-page"]/div[1]/div[3]') |
| 272 | project_release = self.driver.find_element( | 293 | project_release = self.driver.find_element( |
| 273 | By.XPATH, '//*[@id="project-page"]/div[1]/div[4]') | 294 | By.XPATH, '//*[@id="project-page"]/div[1]/div[4]') |
| 274 | layers = block_l.find_element(By.ID, 'layer-container') | 295 | layers = block_l.find_element(By.ID, 'layer-container') |
| 275 | 296 | ||
| 276 | def check_machine_distro(self, item_name, new_item_name, block): | 297 | def check_machine_distro(self, item_name, new_item_name, block_id): |
| 298 | block = self.find(f'#{block_id}') | ||
| 277 | title = block.find_element(By.TAG_NAME, 'h3') | 299 | title = block.find_element(By.TAG_NAME, 'h3') |
| 278 | self.assertTrue(item_name.capitalize() in title.text) | 300 | self.assertTrue(item_name.capitalize() in title.text) |
| 279 | edit_btn = block.find_element(By.ID, f'change-{item_name}-toggle') | 301 | edit_btn = self.find(f'#change-{item_name}-toggle') |
| 280 | edit_btn.click() | 302 | edit_btn.click() |
| 281 | sleep(1) | 303 | self.wait_until_visible(f'#{item_name}-change-input') |
| 282 | name_input = block.find_element(By.ID, f'{item_name}-change-input') | 304 | name_input = self.find(f'#{item_name}-change-input') |
| 283 | name_input.clear() | 305 | name_input.clear() |
| 284 | name_input.send_keys(new_item_name) | 306 | name_input.send_keys(new_item_name) |
| 285 | change_btn = block.find_element(By.ID, f'{item_name}-change-btn') | 307 | change_btn = self.find(f'#{item_name}-change-btn') |
| 286 | change_btn.click() | 308 | change_btn.click() |
| 287 | sleep(1) | 309 | self.wait_until_visible(f'#project-{item_name}-name') |
| 288 | project_name = block.find_element(By.ID, f'project-{item_name}-name') | 310 | project_name = self.find(f'#project-{item_name}-name') |
| 289 | self.assertTrue(new_item_name in project_name.text) | 311 | self.assertTrue(new_item_name in project_name.text) |
| 290 | # check change notificaiton is displayed | 312 | # check change notificaiton is displayed |
| 291 | change_notification = self.find('#change-notification') | 313 | change_notification = self.find('#change-notification') |
| @@ -293,10 +315,30 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 293 | f'You have changed the {item_name} to: {new_item_name}' in change_notification.text | 315 | f'You have changed the {item_name} to: {new_item_name}' in change_notification.text |
| 294 | ) | 316 | ) |
| 295 | 317 | ||
| 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) | ||
| 296 | # Machine | 338 | # Machine |
| 297 | check_machine_distro(self, 'machine', 'qemux86-64', machine) | 339 | check_machine_distro(self, 'machine', 'qemux86-64', 'machine-section') |
| 298 | # Distro | 340 | # Distro |
| 299 | check_machine_distro(self, 'distro', 'poky-altcfg', distro) | 341 | check_machine_distro(self, 'distro', 'poky-altcfg', 'distro-section') |
| 300 | 342 | ||
| 301 | # Project release | 343 | # Project release |
| 302 | title = project_release.find_element(By.TAG_NAME, 'h3') | 344 | title = project_release.find_element(By.TAG_NAME, 'h3') |
| @@ -304,7 +346,6 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 304 | self.assertTrue( | 346 | self.assertTrue( |
| 305 | "Yocto Project master" in self.find('#project-release-title').text | 347 | "Yocto Project master" in self.find('#project-release-title').text |
| 306 | ) | 348 | ) |
| 307 | |||
| 308 | # Layers | 349 | # Layers |
| 309 | title = layers.find_element(By.TAG_NAME, 'h3') | 350 | title = layers.find_element(By.TAG_NAME, 'h3') |
| 310 | self.assertTrue("Layers" in title.text) | 351 | self.assertTrue("Layers" in title.text) |
| @@ -314,7 +355,9 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 314 | # meta-yocto-bsp | 355 | # meta-yocto-bsp |
| 315 | layers_list = layers.find_element(By.ID, 'layers-in-project-list') | 356 | layers_list = layers.find_element(By.ID, 'layers-in-project-list') |
| 316 | layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') | 357 | layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') |
| 317 | self.assertTrue(len(layers_list_items) == 3) | 358 | # remove all layers except the first three layers |
| 359 | for i in range(3, len(layers_list_items)): | ||
| 360 | layers_list_items[i].find_element(By.TAG_NAME, 'span').click() | ||
| 318 | # check can add a layer if exists | 361 | # check can add a layer if exists |
| 319 | add_layer_input = layers.find_element(By.ID, 'layer-add-input') | 362 | add_layer_input = layers.find_element(By.ID, 'layer-add-input') |
| 320 | add_layer_input.send_keys('meta-oe') | 363 | add_layer_input.send_keys('meta-oe') |
| @@ -326,7 +369,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 326 | dropdown_item.click() | 369 | dropdown_item.click() |
| 327 | add_layer_btn = layers.find_element(By.ID, 'add-layer-btn') | 370 | add_layer_btn = layers.find_element(By.ID, 'add-layer-btn') |
| 328 | add_layer_btn.click() | 371 | add_layer_btn.click() |
| 329 | sleep(1) | 372 | self.wait_until_visible('#layers-in-project-list') |
| 330 | # check layer is added | 373 | # check layer is added |
| 331 | layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') | 374 | layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') |
| 332 | self.assertTrue(len(layers_list_items) == 4) | 375 | self.assertTrue(len(layers_list_items) == 4) |
| @@ -334,48 +377,33 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 334 | # Most built recipes | 377 | # Most built recipes |
| 335 | title = most_built_recipes.find_element(By.TAG_NAME, 'h3') | 378 | title = most_built_recipes.find_element(By.TAG_NAME, 'h3') |
| 336 | self.assertTrue("Most built recipes" in title.text) | 379 | self.assertTrue("Most built recipes" in title.text) |
| 337 | # Create a new builds 5 | 380 | # Create a new builds |
| 338 | self._create_builds() | 381 | build_state = self._create_builds() |
| 339 | 382 | ||
| 340 | # Refresh the page | 383 | # Refresh the page |
| 341 | self.get(url) | 384 | self.driver.get(url) |
| 342 | 385 | ||
| 343 | sleep(1) # wait for page to load | 386 | self.wait_until_visible('#project-page', poll=3) |
| 344 | self.wait_until_visible('#project-page') | ||
| 345 | # check can select a recipe and build it | 387 | # check can select a recipe and build it |
| 346 | most_built_recipes = self.driver.find_element( | 388 | most_built_recipes = self.driver.find_element( |
| 347 | By.XPATH, '//*[@id="project-page"]/div[1]/div[3]') | 389 | By.XPATH, '//*[@id="project-page"]/div[1]/div[3]') |
| 348 | recipe_list = most_built_recipes.find_element(By.ID, 'freq-build-list') | 390 | recipe_list = most_built_recipes.find_element(By.ID, 'freq-build-list') |
| 349 | recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li') | 391 | recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li') |
| 350 | self.assertTrue( | 392 | if 'starting' not in build_state: # Build will not appear in the list if canceled in starting state |
| 351 | len(recipe_list_items) > 0, | 393 | self.assertTrue( |
| 352 | msg="No recipes found in the most built recipes list", | 394 | len(recipe_list_items) > 0, |
| 353 | ) | 395 | msg="No recipes found in the most built recipes list", |
| 354 | checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input') | 396 | ) |
| 355 | checkbox.click() | 397 | rebuild_from_most_build_recipes(recipe_list_items) |
| 356 | build_btn = self.find('#freq-build-btn') | 398 | else: |
| 357 | build_btn.click() | 399 | self.assertTrue( |
| 358 | sleep(1) # wait for page to load | 400 | len(recipe_list_items) == 0, |
| 359 | self.wait_until_visible('#latest-builds') | 401 | msg="Recipes found in the most built recipes list", |
| 360 | self._wait_until_build('parsing starting cloning queueing') | 402 | ) |
| 361 | lastest_builds = self.driver.find_elements( | ||
| 362 | By.XPATH, | ||
| 363 | '//div[@id="latest-builds"]/div' | ||
| 364 | ) | ||
| 365 | last_build = lastest_builds[0] | ||
| 366 | cancel_button = last_build.find_element( | ||
| 367 | By.XPATH, | ||
| 368 | '//span[@class="cancel-build-btn pull-right alert-link"]', | ||
| 369 | ) | ||
| 370 | cancel_button.click() | ||
| 371 | self.assertTrue(len(lastest_builds) == 2) | ||
| 372 | 403 | ||
| 373 | def test_project_page_tab_importlayer(self): | 404 | def test_project_page_tab_importlayer(self): |
| 374 | """ Test project page tab import layer """ | 405 | """ Test project page tab import layer """ |
| 375 | # navigate to the project page | 406 | self._navigate_to_project_page() |
| 376 | url = reverse("project", args=(1,)) | ||
| 377 | self.get(url) | ||
| 378 | |||
| 379 | # navigate to "Import layers" tab | 407 | # navigate to "Import layers" tab |
| 380 | import_layers_tab = self._get_tabs()[2] | 408 | import_layers_tab = self._get_tabs()[2] |
| 381 | import_layers_tab.find_element(By.TAG_NAME, 'a').click() | 409 | import_layers_tab.find_element(By.TAG_NAME, 'a').click() |
| @@ -415,10 +443,10 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 415 | 443 | ||
| 416 | def test_project_page_custom_image_no_image(self): | 444 | def test_project_page_custom_image_no_image(self): |
| 417 | """ Test project page tab "New custom image" when no custom image """ | 445 | """ Test project page tab "New custom image" when no custom image """ |
| 418 | # navigate to the project page | 446 | project_name = self._random_string(10) |
| 419 | url = reverse("project", args=(1,)) | 447 | self._create_project(project_name=project_name) |
| 420 | self.get(url) | 448 | current_url = self.driver.current_url |
| 421 | 449 | TestProjectConfigTab.project_id = get_projectId_from_url(current_url) | |
| 422 | # navigate to "Custom image" tab | 450 | # navigate to "Custom image" tab |
| 423 | custom_image_section = self._get_config_nav_item(2) | 451 | custom_image_section = self._get_config_nav_item(2) |
| 424 | custom_image_section.click() | 452 | custom_image_section.click() |
| @@ -433,8 +461,10 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 433 | div_empty_msg = self.find('#empty-state-customimagestable') | 461 | div_empty_msg = self.find('#empty-state-customimagestable') |
| 434 | link_create_custom_image = div_empty_msg.find_element( | 462 | link_create_custom_image = div_empty_msg.find_element( |
| 435 | By.TAG_NAME, 'a') | 463 | By.TAG_NAME, 'a') |
| 464 | last_project_id = Project.objects.get(name=project_name).id | ||
| 465 | self.assertTrue(last_project_id is not None) | ||
| 436 | self.assertTrue( | 466 | self.assertTrue( |
| 437 | f"/toastergui/project/1/newcustomimage" in str( | 467 | f"/toastergui/project/{last_project_id}/newcustomimage" in str( |
| 438 | link_create_custom_image.get_attribute('href') | 468 | link_create_custom_image.get_attribute('href') |
| 439 | ) | 469 | ) |
| 440 | ) | 470 | ) |
| @@ -451,11 +481,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 451 | - Check image recipe build button works | 481 | - Check image recipe build button works |
| 452 | - Check image recipe table features(show/hide column, pagination) | 482 | - Check image recipe table features(show/hide column, pagination) |
| 453 | """ | 483 | """ |
| 454 | # navigate to the project page | 484 | self._navigate_to_project_page() |
| 455 | url = reverse("project", args=(1,)) | ||
| 456 | self.get(url) | ||
| 457 | self.wait_until_visible('#config-nav') | ||
| 458 | |||
| 459 | # navigate to "Images section" | 485 | # navigate to "Images section" |
| 460 | images_section = self._get_config_nav_item(3) | 486 | images_section = self._get_config_nav_item(3) |
| 461 | images_section.click() | 487 | images_section.click() |
| @@ -479,100 +505,17 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase): | |||
| 479 | '//td[@class="add-del-layers"]' | 505 | '//td[@class="add-del-layers"]' |
| 480 | ) | 506 | ) |
| 481 | build_btn.click() | 507 | build_btn.click() |
| 482 | self._wait_until_build('parsing starting cloning') | 508 | build_state = wait_until_build(self, 'parsing starting cloning queued') |
| 483 | lastest_builds = self.driver.find_elements( | 509 | lastest_builds = self.driver.find_elements( |
| 484 | By.XPATH, | 510 | By.XPATH, |
| 485 | '//div[@id="latest-builds"]/div' | 511 | '//div[@id="latest-builds"]/div' |
| 486 | ) | 512 | ) |
| 487 | self.assertTrue(len(lastest_builds) > 0) | 513 | self.assertTrue(len(lastest_builds) > 0) |
| 488 | 514 | last_build = lastest_builds[0] | |
| 489 | def test_image_recipe_editColumn(self): | 515 | cancel_button = last_build.find_element( |
| 490 | """ Test the edit column feature in image recipe table on project page """ | ||
| 491 | self._get_create_builds(success=10, failure=10) | ||
| 492 | |||
| 493 | def test_edit_column(check_box_id): | ||
| 494 | # Check that we can hide/show table column | ||
| 495 | check_box = self.find(f'#{check_box_id}') | ||
| 496 | th_class = str(check_box_id).replace('checkbox-', '') | ||
| 497 | if check_box.is_selected(): | ||
| 498 | # check if column is visible in table | ||
| 499 | self.assertTrue( | ||
| 500 | self.find( | ||
| 501 | f'#imagerecipestable thead th.{th_class}' | ||
| 502 | ).is_displayed(), | ||
| 503 | f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table" | ||
| 504 | ) | ||
| 505 | check_box.click() | ||
| 506 | # check if column is hidden in table | ||
| 507 | self.assertFalse( | ||
| 508 | self.find( | ||
| 509 | f'#imagerecipestable thead th.{th_class}' | ||
| 510 | ).is_displayed(), | ||
| 511 | f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table" | ||
| 512 | ) | ||
| 513 | else: | ||
| 514 | # check if column is hidden in table | ||
| 515 | self.assertFalse( | ||
| 516 | self.find( | ||
| 517 | f'#imagerecipestable thead th.{th_class}' | ||
| 518 | ).is_displayed(), | ||
| 519 | f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table" | ||
| 520 | ) | ||
| 521 | check_box.click() | ||
| 522 | # check if column is visible in table | ||
| 523 | self.assertTrue( | ||
| 524 | self.find( | ||
| 525 | f'#imagerecipestable thead th.{th_class}' | ||
| 526 | ).is_displayed(), | ||
| 527 | f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table" | ||
| 528 | ) | ||
| 529 | |||
| 530 | url = reverse('projectimagerecipes', args=(1,)) | ||
| 531 | self.get(url) | ||
| 532 | self.wait_until_present('#imagerecipestable tbody tr') | ||
| 533 | |||
| 534 | # Check edit column | ||
| 535 | edit_column = self.find('#edit-columns-button') | ||
| 536 | self.assertTrue(edit_column.is_displayed()) | ||
| 537 | edit_column.click() | ||
| 538 | # Check dropdown is visible | ||
| 539 | self.wait_until_visible('ul.dropdown-menu.editcol') | ||
| 540 | |||
| 541 | # Check that we can hide the edit column | ||
| 542 | test_edit_column('checkbox-get_description_or_summary') | ||
| 543 | test_edit_column('checkbox-layer_version__get_vcs_reference') | ||
| 544 | test_edit_column('checkbox-layer_version__layer__name') | ||
| 545 | test_edit_column('checkbox-license') | ||
| 546 | test_edit_column('checkbox-recipe-file') | ||
| 547 | test_edit_column('checkbox-section') | ||
| 548 | test_edit_column('checkbox-version') | ||
| 549 | |||
| 550 | def test_image_recipe_show_rows(self): | ||
| 551 | """ Test the show rows feature in image recipe table on project page """ | ||
| 552 | self._get_create_builds(success=100, failure=100) | ||
| 553 | |||
| 554 | def test_show_rows(row_to_show, show_row_link): | ||
| 555 | # Check that we can show rows == row_to_show | ||
| 556 | show_row_link.select_by_value(str(row_to_show)) | ||
| 557 | self.wait_until_present('#imagerecipestable tbody tr') | ||
| 558 | sleep(1) | ||
| 559 | self.assertTrue( | ||
| 560 | len(self.find_all('#imagerecipestable tbody tr')) == row_to_show | ||
| 561 | ) | ||
| 562 | |||
| 563 | url = reverse('projectimagerecipes', args=(2,)) | ||
| 564 | self.get(url) | ||
| 565 | self.wait_until_present('#imagerecipestable tbody tr') | ||
| 566 | |||
| 567 | show_rows = self.driver.find_elements( | ||
| 568 | By.XPATH, | 516 | By.XPATH, |
| 569 | '//select[@class="form-control pagesize-imagerecipestable"]' | 517 | '//span[@class="cancel-build-btn pull-right alert-link"]', |
| 570 | ) | 518 | ) |
| 571 | # Check show rows | 519 | cancel_button.click() |
| 572 | for show_row_link in show_rows: | 520 | if 'starting' not in build_state: # change build state when cancelled in starting state |
| 573 | show_row_link = Select(show_row_link) | 521 | wait_until_build_cancelled(self) |
| 574 | test_show_rows(10, show_row_link) | ||
| 575 | test_show_rows(25, show_row_link) | ||
| 576 | test_show_rows(50, show_row_link) | ||
| 577 | test_show_rows(100, show_row_link) | ||
| 578 | test_show_rows(150, show_row_link) | ||
