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 9import os 10import random 11import string 12from unittest import skip 13import pytest 14from django.urls import reverse 15from django.utils import timezone 16from selenium.webdriver.common.keys import Keys 17from selenium.webdriver.support.select import Select 18from selenium.common.exceptions import TimeoutException 19from tests.functional.functional_helpers import SeleniumFunctionalTestCase 20from orm.models import Build, Project, Target 21from selenium.webdriver.common.by import By 22 23from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled 24 25 26@pytest.mark.django_db 27@pytest.mark.order("last") 28class TestProjectPage(SeleniumFunctionalTestCase): 29 project_id = None 30 PROJECT_NAME = 'TestProjectPage' 31 32 def _create_project(self, project_name): 33 """ Create/Test new project using: 34 - Project Name: Any string 35 - Release: Any string 36 - Merge Toaster settings: True or False 37 """ 38 self.get(reverse('newproject')) 39 self.wait_until_visible('#new-project-name') 40 self.find("#new-project-name").send_keys(project_name) 41 select = Select(self.find("#projectversion")) 42 select.select_by_value('3') 43 44 # check merge toaster settings 45 checkbox = self.find('.checkbox-mergeattr') 46 if not checkbox.is_selected(): 47 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) 74 else: 75 url = reverse('project', args=(TestProjectPage.project_id,)) 76 self.get(url) 77 self.wait_until_visible('#config-nav') 78 79 def _get_create_builds(self, **kwargs): 80 """ Create a build and return the build object """ 81 # parameters for builds to associate with the projects 82 now = timezone.now() 83 self.project1_build_success = { 84 'project': Project.objects.get(id=TestProjectPage.project_id), 85 'started_on': now, 86 'completed_on': now, 87 'outcome': Build.SUCCEEDED 88 } 89 90 self.project1_build_failure = { 91 'project': Project.objects.get(id=TestProjectPage.project_id), 92 'started_on': now, 93 'completed_on': now, 94 'outcome': Build.FAILED 95 } 96 build1 = Build.objects.create(**self.project1_build_success) 97 build2 = Build.objects.create(**self.project1_build_failure) 98 99 # add some targets to these builds so they have recipe links 100 # (and so we can find the row in the ToasterTable corresponding to 101 # a particular build) 102 Target.objects.create(build=build1, target='foo') 103 Target.objects.create(build=build2, target='bar') 104 105 if kwargs: 106 # Create kwargs.get('success') builds with success status with target 107 # and kwargs.get('failure') builds with failure status with target 108 for i in range(kwargs.get('success', 0)): 109 now = timezone.now() 110 self.project1_build_success['started_on'] = now 111 self.project1_build_success[ 112 'completed_on'] = now - timezone.timedelta(days=i) 113 build = Build.objects.create(**self.project1_build_success) 114 Target.objects.create(build=build, 115 target=f'{i}_success_recipe', 116 task=f'{i}_success_task') 117 118 for i in range(kwargs.get('failure', 0)): 119 now = timezone.now() 120 self.project1_build_failure['started_on'] = now 121 self.project1_build_failure[ 122 'completed_on'] = now - timezone.timedelta(days=i) 123 build = Build.objects.create(**self.project1_build_failure) 124 Target.objects.create(build=build, 125 target=f'{i}_fail_recipe', 126 task=f'{i}_fail_task') 127 return build1, build2 128 129 def _mixin_test_table_edit_column( 130 self, 131 table_id, 132 edit_btn_id, 133 list_check_box_id: list 134 ): 135 # Check edit column 136 edit_column = self.find(f'#{edit_btn_id}') 137 self.assertTrue(edit_column.is_displayed()) 138 edit_column.click() 139 # Check dropdown is visible 140 self.wait_until_visible('ul.dropdown-menu.editcol') 141 for check_box_id in list_check_box_id: 142 # Check that we can hide/show table column 143 check_box = self.find(f'#{check_box_id}') 144 th_class = str(check_box_id).replace('checkbox-', '') 145 if check_box.is_selected(): 146 # check if column is visible in table 147 self.assertTrue( 148 self.find( 149 f'#{table_id} thead th.{th_class}' 150 ).is_displayed(), 151 f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table" 152 ) 153 check_box.click() 154 # check if column is hidden in table 155 self.assertFalse( 156 self.find( 157 f'#{table_id} thead th.{th_class}' 158 ).is_displayed(), 159 f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table" 160 ) 161 else: 162 # check if column is hidden in table 163 self.assertFalse( 164 self.find( 165 f'#{table_id} thead th.{th_class}' 166 ).is_displayed(), 167 f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table" 168 ) 169 check_box.click() 170 # check if column is visible in table 171 self.assertTrue( 172 self.find( 173 f'#{table_id} thead th.{th_class}' 174 ).is_displayed(), 175 f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table" 176 ) 177 178 def _get_config_nav_item(self, index): 179 config_nav = self.find('#config-nav') 180 return config_nav.find_elements(By.TAG_NAME, 'li')[index] 181 182 def _navigate_to_config_nav(self, nav_id, nav_index): 183 # navigate to the project page 184 self._navigate_to_project_page() 185 # click on "Software recipe" tab 186 soft_recipe = self._get_config_nav_item(nav_index) 187 soft_recipe.click() 188 self.wait_until_visible(f'#{nav_id}') 189 190 def _mixin_test_table_show_rows(self, table_selector, **kwargs): 191 """ Test the show rows feature in the builds table on the all builds page """ 192 def test_show_rows(row_to_show, show_row_link): 193 # Check that we can show rows == row_to_show 194 show_row_link.select_by_value(str(row_to_show)) 195 self.wait_until_visible(f'#{table_selector} tbody tr', poll=3) 196 # check at least some rows are visible 197 self.assertTrue( 198 len(self.find_all(f'#{table_selector} tbody tr')) > 0 199 ) 200 self.wait_until_present(f'#{table_selector} tbody tr') 201 show_rows = self.driver.find_elements( 202 By.XPATH, 203 f'//select[@class="form-control pagesize-{table_selector}"]' 204 ) 205 rows_to_show = [10, 25, 50, 100, 150] 206 to_skip = kwargs.get('to_skip', []) 207 # Check show rows 208 for show_row_link in show_rows: 209 show_row_link = Select(show_row_link) 210 for row_to_show in rows_to_show: 211 if row_to_show not in to_skip: 212 test_show_rows(row_to_show, show_row_link) 213 214 def _mixin_test_table_search_input(self, **kwargs): 215 input_selector, input_text, searchBtn_selector, table_selector, *_ = kwargs.values() 216 # Test search input 217 self.wait_until_visible(f'#{input_selector}') 218 recipe_input = self.find(f'#{input_selector}') 219 recipe_input.send_keys(input_text) 220 self.find(f'#{searchBtn_selector}').click() 221 self.wait_until_visible(f'#{table_selector} tbody tr') 222 rows = self.find_all(f'#{table_selector} tbody tr') 223 self.assertTrue(len(rows) > 0) 224 225 def test_create_project(self): 226 """ Create/Test new project using: 227 - Project Name: Any string 228 - Release: Any string 229 - Merge Toaster settings: True or False 230 """ 231 self._create_project(project_name=self.PROJECT_NAME) 232 233 def test_image_recipe_editColumn(self): 234 """ Test the edit column feature in image recipe table on project page """ 235 self._get_create_builds(success=10, failure=10) 236 237 url = reverse('projectimagerecipes', args=(TestProjectPage.project_id,)) 238 self.get(url) 239 self.wait_until_present('#imagerecipestable tbody tr') 240 241 column_list = [ 242 'get_description_or_summary', 'layer_version__get_vcs_reference', 243 'layer_version__layer__name', 'license', 'recipe-file', 'section', 244 'version' 245 ] 246 247 # Check that we can hide the edit column 248 self._mixin_test_table_edit_column( 249 'imagerecipestable', 250 'edit-columns-button', 251 [f'checkbox-{column}' for column in column_list] 252 ) 253 254 def test_page_header_on_project_page(self): 255 """ Check page header in project page: 256 - AT LEFT -> Logo of Yocto project, displayed, clickable 257 - "Toaster"+" Information icon", displayed, clickable 258 - "Server Icon" + "All builds", displayed, clickable 259 - "Directory Icon" + "All projects", displayed, clickable 260 - "Book Icon" + "Documentation", displayed, clickable 261 - AT RIGHT -> button "New project", displayed, clickable 262 """ 263 # navigate to the project page 264 self._navigate_to_project_page() 265 266 # check page header 267 # AT LEFT -> Logo of Yocto project 268 logo = self.driver.find_element( 269 By.XPATH, 270 "//div[@class='toaster-navbar-brand']", 271 ) 272 logo_img = logo.find_element(By.TAG_NAME, 'img') 273 self.assertTrue(logo_img.is_displayed(), 274 'Logo of Yocto project not found') 275 self.assertTrue( 276 '/static/img/logo.png' in str(logo_img.get_attribute('src')), 277 'Logo of Yocto project not found' 278 ) 279 # "Toaster"+" Information icon", clickable 280 toaster = self.driver.find_element( 281 By.XPATH, 282 "//div[@class='toaster-navbar-brand']//a[@class='brand']", 283 ) 284 self.assertTrue(toaster.is_displayed(), 'Toaster not found') 285 self.assertTrue(toaster.text == 'Toaster') 286 info_sign = self.find('.glyphicon-info-sign') 287 self.assertTrue(info_sign.is_displayed()) 288 289 # "Server Icon" + "All builds" 290 all_builds = self.find('#navbar-all-builds') 291 all_builds_link = all_builds.find_element(By.TAG_NAME, 'a') 292 self.assertTrue("All builds" in all_builds_link.text) 293 self.assertTrue( 294 '/toastergui/builds/' in str(all_builds_link.get_attribute('href')) 295 ) 296 server_icon = all_builds.find_element(By.TAG_NAME, 'i') 297 self.assertTrue( 298 server_icon.get_attribute('class') == 'glyphicon glyphicon-tasks' 299 ) 300 self.assertTrue(server_icon.is_displayed()) 301 302 # "Directory Icon" + "All projects" 303 all_projects = self.find('#navbar-all-projects') 304 all_projects_link = all_projects.find_element(By.TAG_NAME, 'a') 305 self.assertTrue("All projects" in all_projects_link.text) 306 self.assertTrue( 307 '/toastergui/projects/' in str(all_projects_link.get_attribute( 308 'href')) 309 ) 310 dir_icon = all_projects.find_element(By.TAG_NAME, 'i') 311 self.assertTrue( 312 dir_icon.get_attribute('class') == 'icon-folder-open' 313 ) 314 self.assertTrue(dir_icon.is_displayed()) 315 316 # "Book Icon" + "Documentation" 317 toaster_docs_link = self.find('#navbar-docs') 318 toaster_docs_link_link = toaster_docs_link.find_element(By.TAG_NAME, 319 'a') 320 self.assertTrue("Documentation" in toaster_docs_link_link.text) 321 self.assertTrue( 322 toaster_docs_link_link.get_attribute('href') == 'http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual' 323 ) 324 book_icon = toaster_docs_link.find_element(By.TAG_NAME, 'i') 325 self.assertTrue( 326 book_icon.get_attribute('class') == 'glyphicon glyphicon-book' 327 ) 328 self.assertTrue(book_icon.is_displayed()) 329 330 # AT RIGHT -> button "New project" 331 new_project_button = self.find('#new-project-button') 332 self.assertTrue(new_project_button.is_displayed()) 333 self.assertTrue(new_project_button.text == 'New project') 334 new_project_button.click() 335 self.assertTrue( 336 '/toastergui/newproject/' in str(self.driver.current_url) 337 ) 338 339 def test_edit_project_name(self): 340 """ Test edit project name: 341 - Click on "Edit" icon button 342 - Change project name 343 - Click on "Save" button 344 - Check project name is changed 345 """ 346 # navigate to the project page 347 self._navigate_to_project_page() 348 349 # click on "Edit" icon button 350 self.wait_until_visible('#project-name-container') 351 edit_button = self.find('#project-change-form-toggle') 352 edit_button.click() 353 project_name_input = self.find('#project-name-change-input') 354 self.assertTrue(project_name_input.is_displayed()) 355 project_name_input.clear() 356 project_name_input.send_keys('New Name') 357 self.find('#project-name-change-btn').click() 358 359 # check project name is changed 360 self.wait_until_visible('#project-name-container') 361 self.assertTrue( 362 'New Name' in str(self.find('#project-name-container').text) 363 ) 364 365 def test_project_page_tabs(self): 366 """ Test project tabs: 367 - "configuration" tab 368 - "Builds" tab 369 - "Import layers" tab 370 - "New custom image" tab 371 Check search box used to build recipes 372 """ 373 # navigate to the project page 374 self._navigate_to_project_page() 375 376 # check "configuration" tab 377 self.wait_until_visible('#topbar-configuration-tab') 378 config_tab = self.find('#topbar-configuration-tab') 379 self.assertTrue(config_tab.get_attribute('class') == 'active') 380 self.assertTrue('Configuration' in str(config_tab.text)) 381 self.assertTrue( 382 f"/toastergui/project/{TestProjectPage.project_id}" in str(self.driver.current_url) 383 ) 384 385 def get_tabs(): 386 # tabs links list 387 return self.driver.find_elements( 388 By.XPATH, 389 '//div[@id="project-topbar"]//li' 390 ) 391 392 def check_tab_link(tab_index, tab_name, url): 393 tab = get_tabs()[tab_index] 394 tab_link = tab.find_element(By.TAG_NAME, 'a') 395 self.assertTrue(url in tab_link.get_attribute('href')) 396 self.assertTrue(tab_name in tab_link.text) 397 self.assertTrue(tab.get_attribute('class') == 'active') 398 399 # check "Builds" tab 400 builds_tab = get_tabs()[1] 401 builds_tab.find_element(By.TAG_NAME, 'a').click() 402 check_tab_link( 403 1, 404 'Builds', 405 f"/toastergui/project/{TestProjectPage.project_id}/builds" 406 ) 407 408 # check "Import layers" tab 409 import_layers_tab = get_tabs()[2] 410 import_layers_tab.find_element(By.TAG_NAME, 'a').click() 411 check_tab_link( 412 2, 413 'Import layer', 414 f"/toastergui/project/{TestProjectPage.project_id}/importlayer" 415 ) 416 417 # check "New custom image" tab 418 new_custom_image_tab = get_tabs()[3] 419 new_custom_image_tab.find_element(By.TAG_NAME, 'a').click() 420 check_tab_link( 421 3, 422 'New custom image', 423 f"/toastergui/project/{TestProjectPage.project_id}/newcustomimage" 424 ) 425 426 # check search box can be use to build recipes 427 search_box = self.find('#build-input') 428 search_box.send_keys('core-image-minimal') 429 self.find('#build-button').click() 430 self.wait_until_visible('#latest-builds') 431 lastest_builds = self.driver.find_elements( 432 By.XPATH, 433 '//div[@id="latest-builds"]', 434 ) 435 last_build = lastest_builds[0] 436 self.assertTrue( 437 'core-image-minimal' in str(last_build.text) 438 ) 439 440 def test_softwareRecipe_page(self): 441 """ Test software recipe page 442 - Check title "Compatible software recipes" is displayed 443 - Check search input 444 - Check "build recipe" button works 445 - Check software recipe table feature(show/hide column, pagination) 446 """ 447 self._navigate_to_config_nav('softwarerecipestable', 4) 448 # check title "Compatible software recipes" is displayed 449 self.assertTrue("Compatible software recipes" in self.get_page_source()) 450 # Test search input 451 self._mixin_test_table_search_input( 452 input_selector='search-input-softwarerecipestable', 453 input_text='busybox', 454 searchBtn_selector='search-submit-softwarerecipestable', 455 table_selector='softwarerecipestable' 456 ) 457 # check "build recipe" button works 458 rows = self.find_all('#softwarerecipestable tbody tr') 459 image_to_build = rows[0] 460 build_btn = image_to_build.find_element( 461 By.XPATH, 462 '//td[@class="add-del-layers"]//a[1]' 463 ) 464 build_btn.click() 465 build_state = wait_until_build(self, 'queued cloning starting parsing failed') 466 lastest_builds = self.driver.find_elements( 467 By.XPATH, 468 '//div[@id="latest-builds"]/div' 469 ) 470 self.assertTrue(len(lastest_builds) > 0) 471 last_build = lastest_builds[0] 472 cancel_button = last_build.find_element( 473 By.XPATH, 474 '//span[@class="cancel-build-btn pull-right alert-link"]', 475 ) 476 cancel_button.click() 477 if 'starting' not in build_state: # change build state when cancelled in starting state 478 wait_until_build_cancelled(self) 479 480 # check software recipe table feature(show/hide column, pagination) 481 self._navigate_to_config_nav('softwarerecipestable', 4) 482 column_list = [ 483 'get_description_or_summary', 484 'layer_version__get_vcs_reference', 485 'layer_version__layer__name', 486 'license', 487 'recipe-file', 488 'section', 489 'version', 490 ] 491 self._mixin_test_table_edit_column( 492 'softwarerecipestable', 493 'edit-columns-button', 494 [f'checkbox-{column}' for column in column_list] 495 ) 496 self._navigate_to_config_nav('softwarerecipestable', 4) 497 # check show rows(pagination) 498 self._mixin_test_table_show_rows( 499 table_selector='softwarerecipestable', 500 to_skip=[150], 501 ) 502 503 def test_machines_page(self): 504 """ Test Machine page 505 - Check if title "Compatible machines" is displayed 506 - Check search input 507 - Check "Select machine" button works 508 - Check "Add layer" button works 509 - Check Machine table feature(show/hide column, pagination) 510 """ 511 self._navigate_to_config_nav('machinestable', 5) 512 # check title "Compatible software recipes" is displayed 513 self.assertTrue("Compatible machines" in self.get_page_source()) 514 # Test search input 515 self._mixin_test_table_search_input( 516 input_selector='search-input-machinestable', 517 input_text='qemux86-64', 518 searchBtn_selector='search-submit-machinestable', 519 table_selector='machinestable' 520 ) 521 # check "Select machine" button works 522 rows = self.find_all('#machinestable tbody tr') 523 machine_to_select = rows[0] 524 select_btn = machine_to_select.find_element( 525 By.XPATH, 526 '//td[@class="add-del-layers"]//a[1]' 527 ) 528 select_btn.send_keys(Keys.RETURN) 529 self.wait_until_visible('#config-nav') 530 project_machine_name = self.find('#project-machine-name') 531 self.assertTrue( 532 'qemux86-64' in project_machine_name.text 533 ) 534 # check "Add layer" button works 535 self._navigate_to_config_nav('machinestable', 5) 536 # Search for a machine whit layer not in project 537 self._mixin_test_table_search_input( 538 input_selector='search-input-machinestable', 539 input_text='qemux86-64-tpm2', 540 searchBtn_selector='search-submit-machinestable', 541 table_selector='machinestable' 542 ) 543 self.wait_until_visible('#machinestable tbody tr', poll=3) 544 rows = self.find_all('#machinestable tbody tr') 545 machine_to_add = rows[0] 546 add_btn = machine_to_add.find_element(By.XPATH, '//td[@class="add-del-layers"]') 547 add_btn.click() 548 self.wait_until_visible('#change-notification') 549 change_notification = self.find('#change-notification') 550 self.assertTrue( 551 f'You have added 1 layer to your project' in str(change_notification.text) 552 ) 553 # check Machine table feature(show/hide column, pagination) 554 self._navigate_to_config_nav('machinestable', 5) 555 column_list = [ 556 'description', 557 'layer_version__get_vcs_reference', 558 'layer_version__layer__name', 559 'machinefile', 560 ] 561 self._mixin_test_table_edit_column( 562 'machinestable', 563 'edit-columns-button', 564 [f'checkbox-{column}' for column in column_list] 565 ) 566 self._navigate_to_config_nav('machinestable', 5) 567 # check show rows(pagination) 568 self._mixin_test_table_show_rows( 569 table_selector='machinestable', 570 to_skip=[150], 571 ) 572 573 def test_layers_page(self): 574 """ Test layers page 575 - Check if title "Compatible layerss" is displayed 576 - Check search input 577 - Check "Add layer" button works 578 - Check "Remove layer" button works 579 - Check layers table feature(show/hide column, pagination) 580 """ 581 self._navigate_to_config_nav('layerstable', 6) 582 # check title "Compatible layers" is displayed 583 self.assertTrue("Compatible layers" in self.get_page_source()) 584 # Test search input 585 input_text='meta-tanowrt' 586 self._mixin_test_table_search_input( 587 input_selector='search-input-layerstable', 588 input_text=input_text, 589 searchBtn_selector='search-submit-layerstable', 590 table_selector='layerstable' 591 ) 592 # check "Add layer" button works 593 self.wait_until_visible('#layerstable tbody tr', poll=3) 594 rows = self.find_all('#layerstable tbody tr') 595 layer_to_add = rows[0] 596 add_btn = layer_to_add.find_element( 597 By.XPATH, 598 '//td[@class="add-del-layers"]' 599 ) 600 add_btn.click() 601 # check modal is displayed 602 self.wait_until_visible('#dependencies-modal', poll=3) 603 list_dependencies = self.find_all('#dependencies-list li') 604 # click on add-layers button 605 add_layers_btn = self.driver.find_element( 606 By.XPATH, 607 '//form[@id="dependencies-modal-form"]//button[@class="btn btn-primary"]' 608 ) 609 add_layers_btn.click() 610 self.wait_until_visible('#change-notification') 611 change_notification = self.find('#change-notification') 612 self.assertTrue( 613 f'You have added {len(list_dependencies)+1} layers to your project: {input_text} and its dependencies' in str(change_notification.text) 614 ) 615 # check "Remove layer" button works 616 self.wait_until_visible('#layerstable tbody tr', poll=3) 617 rows = self.find_all('#layerstable tbody tr') 618 layer_to_remove = rows[0] 619 remove_btn = layer_to_remove.find_element( 620 By.XPATH, 621 '//td[@class="add-del-layers"]' 622 ) 623 remove_btn.click() 624 self.wait_until_visible('#change-notification', poll=2) 625 change_notification = self.find('#change-notification') 626 self.assertTrue( 627 f'You have removed 1 layer from your project: {input_text}' in str(change_notification.text) 628 ) 629 # check layers table feature(show/hide column, pagination) 630 self._navigate_to_config_nav('layerstable', 6) 631 column_list = [ 632 'dependencies', 633 'revision', 634 'layer__vcs_url', 635 'git_subdir', 636 'layer__summary', 637 ] 638 self._mixin_test_table_edit_column( 639 'layerstable', 640 'edit-columns-button', 641 [f'checkbox-{column}' for column in column_list] 642 ) 643 self._navigate_to_config_nav('layerstable', 6) 644 # check show rows(pagination) 645 self._mixin_test_table_show_rows( 646 table_selector='layerstable', 647 to_skip=[150], 648 ) 649 650 def test_distro_page(self): 651 """ Test distros page 652 - Check if title "Compatible distros" is displayed 653 - Check search input 654 - Check "Add layer" button works 655 - Check distro table feature(show/hide column, pagination) 656 """ 657 self._navigate_to_config_nav('distrostable', 7) 658 # check title "Compatible distros" is displayed 659 self.assertTrue("Compatible Distros" in self.get_page_source()) 660 # Test search input 661 input_text='poky-altcfg' 662 self._mixin_test_table_search_input( 663 input_selector='search-input-distrostable', 664 input_text=input_text, 665 searchBtn_selector='search-submit-distrostable', 666 table_selector='distrostable' 667 ) 668 # check "Add distro" button works 669 rows = self.find_all('#distrostable tbody tr') 670 distro_to_add = rows[0] 671 add_btn = distro_to_add.find_element( 672 By.XPATH, 673 '//td[@class="add-del-layers"]//a[1]' 674 ) 675 add_btn.click() 676 self.wait_until_visible('#change-notification', poll=2) 677 change_notification = self.find('#change-notification') 678 self.assertTrue( 679 f'You have changed the distro to: {input_text}' in str(change_notification.text) 680 ) 681 # check distro table feature(show/hide column, pagination) 682 self._navigate_to_config_nav('distrostable', 7) 683 column_list = [ 684 'description', 685 'templatefile', 686 'layer_version__get_vcs_reference', 687 'layer_version__layer__name', 688 ] 689 self._mixin_test_table_edit_column( 690 'distrostable', 691 'edit-columns-button', 692 [f'checkbox-{column}' for column in column_list] 693 ) 694 self._navigate_to_config_nav('distrostable', 7) 695 # check show rows(pagination) 696 self._mixin_test_table_show_rows( 697 table_selector='distrostable', 698 to_skip=[150], 699 ) 700 701 def test_single_layer_page(self): 702 """ Test layer page 703 - Check if title is displayed 704 - Check add/remove layer button works 705 - Check tabs(layers, recipes, machines) are displayed 706 - Check left section is displayed 707 - Check layer name 708 - Check layer summary 709 - Check layer description 710 """ 711 url = reverse("layerdetails", args=(TestProjectPage.project_id, 8)) 712 self.get(url) 713 self.wait_until_visible('.page-header') 714 # check title is displayed 715 self.assertTrue(self.find('.page-header h1').is_displayed()) 716 717 # check add layer button works 718 remove_layer_btn = self.find('#add-remove-layer-btn') 719 remove_layer_btn.click() 720 self.wait_until_visible('#change-notification', poll=2) 721 change_notification = self.find('#change-notification') 722 self.assertTrue( 723 f'You have removed 1 layer from your project' in str(change_notification.text) 724 ) 725 # check add layer button works, 18 is the random layer id 726 add_layer_btn = self.find('#add-remove-layer-btn') 727 add_layer_btn.click() 728 self.wait_until_visible('#change-notification') 729 change_notification = self.find('#change-notification') 730 self.assertTrue( 731 f'You have added 1 layer to your project' in str(change_notification.text) 732 ) 733 # check tabs(layers, recipes, machines) are displayed 734 tabs = self.find_all('.nav-tabs li') 735 self.assertEqual(len(tabs), 3) 736 # Check first tab 737 tabs[0].click() 738 self.assertTrue( 739 'active' in str(self.find('#information').get_attribute('class')) 740 ) 741 # Check second tab 742 tabs[1].click() 743 self.assertTrue( 744 'active' in str(self.find('#recipes').get_attribute('class')) 745 ) 746 # Check third tab 747 tabs[2].click() 748 self.assertTrue( 749 'active' in str(self.find('#machines').get_attribute('class')) 750 ) 751 # Check left section is displayed 752 section = self.find('.well') 753 # Check layer name 754 self.assertTrue( 755 section.find_element(By.XPATH, '//h2[1]').is_displayed() 756 ) 757 # Check layer summary 758 self.assertTrue("Summary" in section.text) 759 # Check layer description 760 self.assertTrue("Description" in section.text) 761 762 def test_single_recipe_page(self): 763 """ Test recipe page 764 - Check if title is displayed 765 - Check add recipe layer displayed 766 - Check left section is displayed 767 - Check recipe: name, summary, description, Version, Section, 768 License, Approx. packages included, Approx. size, Recipe file 769 """ 770 url = reverse("recipedetails", args=(TestProjectPage.project_id, 53428)) 771 self.get(url) 772 self.wait_until_visible('.page-header') 773 # check title is displayed 774 self.assertTrue(self.find('.page-header h1').is_displayed()) 775 # check add recipe layer displayed 776 add_recipe_layer_btn = self.find('#add-layer-btn') 777 self.assertTrue(add_recipe_layer_btn.is_displayed()) 778 # check left section is displayed 779 section = self.find('.well') 780 # Check recipe name 781 self.assertTrue( 782 section.find_element(By.XPATH, '//h2[1]').is_displayed() 783 ) 784 # Check recipe sections details info are displayed 785 self.assertTrue("Summary" in section.text) 786 self.assertTrue("Description" in section.text) 787 self.assertTrue("Version" in section.text) 788 self.assertTrue("Section" in section.text) 789 self.assertTrue("License" in section.text) 790 self.assertTrue("Approx. packages included" in section.text) 791 self.assertTrue("Approx. package size" in section.text) 792 self.assertTrue("Recipe file" in section.text) 793