summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/tests/functional/test_project_page.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster/tests/functional/test_project_page.py')
-rw-r--r--bitbake/lib/toaster/tests/functional/test_project_page.py792
1 files changed, 792 insertions, 0 deletions
diff --git a/bitbake/lib/toaster/tests/functional/test_project_page.py b/bitbake/lib/toaster/tests/functional/test_project_page.py
new file mode 100644
index 0000000000..adbe3587e4
--- /dev/null
+++ b/bitbake/lib/toaster/tests/functional/test_project_page.py
@@ -0,0 +1,792 @@
+#! /usr/bin/env python3 #
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+import os
+import random
+import string
+from unittest import skip
+import pytest
+from django.urls import reverse
+from django.utils import timezone
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.support.select import Select
+from selenium.common.exceptions import TimeoutException
+from tests.functional.functional_helpers import SeleniumFunctionalTestCase
+from orm.models import Build, Project, Target
+from selenium.webdriver.common.by import By
+
+from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled
+
+
+@pytest.mark.django_db
+@pytest.mark.order("last")
+class TestProjectPage(SeleniumFunctionalTestCase):
+ project_id = None
+ PROJECT_NAME = 'TestProjectPage'
+
+ def _create_project(self, project_name):
+ """ Create/Test new project using:
+ - Project Name: Any string
+ - Release: Any string
+ - Merge Toaster settings: True or False
+ """
+ self.get(reverse('newproject'))
+ self.wait_until_visible('#new-project-name')
+ self.find("#new-project-name").send_keys(project_name)
+ select = Select(self.find("#projectversion"))
+ select.select_by_value('3')
+
+ # check merge toaster settings
+ checkbox = self.find('.checkbox-mergeattr')
+ if not checkbox.is_selected():
+ checkbox.click()
+
+ if self.PROJECT_NAME != 'TestProjectPage':
+ # Reset project name if it's not the default one
+ self.PROJECT_NAME = 'TestProjectPage'
+
+ self.find("#create-project-button").click()
+
+ try:
+ self.wait_until_visible('#hint-error-project-name')
+ url = reverse('project', args=(TestProjectPage.project_id, ))
+ self.get(url)
+ self.wait_until_visible('#config-nav', poll=3)
+ except TimeoutException:
+ self.wait_until_visible('#config-nav', poll=3)
+
+ def _random_string(self, length):
+ return ''.join(
+ random.choice(string.ascii_letters) for _ in range(length)
+ )
+
+ def _navigate_to_project_page(self):
+ # Navigate to project page
+ if TestProjectPage.project_id is None:
+ self._create_project(project_name=self._random_string(10))
+ current_url = self.driver.current_url
+ TestProjectPage.project_id = get_projectId_from_url(current_url)
+ else:
+ url = reverse('project', args=(TestProjectPage.project_id,))
+ self.get(url)
+ self.wait_until_visible('#config-nav')
+
+ def _get_create_builds(self, **kwargs):
+ """ Create a build and return the build object """
+ # parameters for builds to associate with the projects
+ now = timezone.now()
+ self.project1_build_success = {
+ 'project': Project.objects.get(id=TestProjectPage.project_id),
+ 'started_on': now,
+ 'completed_on': now,
+ 'outcome': Build.SUCCEEDED
+ }
+
+ self.project1_build_failure = {
+ 'project': Project.objects.get(id=TestProjectPage.project_id),
+ 'started_on': now,
+ 'completed_on': now,
+ 'outcome': Build.FAILED
+ }
+ build1 = Build.objects.create(**self.project1_build_success)
+ build2 = Build.objects.create(**self.project1_build_failure)
+
+ # add some targets to these builds so they have recipe links
+ # (and so we can find the row in the ToasterTable corresponding to
+ # a particular build)
+ Target.objects.create(build=build1, target='foo')
+ Target.objects.create(build=build2, target='bar')
+
+ if kwargs:
+ # Create kwargs.get('success') builds with success status with target
+ # and kwargs.get('failure') builds with failure status with target
+ for i in range(kwargs.get('success', 0)):
+ now = timezone.now()
+ self.project1_build_success['started_on'] = now
+ self.project1_build_success[
+ 'completed_on'] = now - timezone.timedelta(days=i)
+ build = Build.objects.create(**self.project1_build_success)
+ Target.objects.create(build=build,
+ target=f'{i}_success_recipe',
+ task=f'{i}_success_task')
+
+ for i in range(kwargs.get('failure', 0)):
+ now = timezone.now()
+ self.project1_build_failure['started_on'] = now
+ self.project1_build_failure[
+ 'completed_on'] = now - timezone.timedelta(days=i)
+ build = Build.objects.create(**self.project1_build_failure)
+ Target.objects.create(build=build,
+ target=f'{i}_fail_recipe',
+ task=f'{i}_fail_task')
+ return build1, build2
+
+ def _mixin_test_table_edit_column(
+ self,
+ table_id,
+ edit_btn_id,
+ list_check_box_id: list
+ ):
+ # Check edit column
+ edit_column = self.find(f'#{edit_btn_id}')
+ self.assertTrue(edit_column.is_displayed())
+ edit_column.click()
+ # Check dropdown is visible
+ self.wait_until_visible('ul.dropdown-menu.editcol')
+ for check_box_id in list_check_box_id:
+ # Check that we can hide/show table column
+ check_box = self.find(f'#{check_box_id}')
+ th_class = str(check_box_id).replace('checkbox-', '')
+ if check_box.is_selected():
+ # check if column is visible in table
+ self.assertTrue(
+ self.find(
+ f'#{table_id} thead th.{th_class}'
+ ).is_displayed(),
+ f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
+ )
+ check_box.click()
+ # check if column is hidden in table
+ self.assertFalse(
+ self.find(
+ f'#{table_id} thead th.{th_class}'
+ ).is_displayed(),
+ f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
+ )
+ else:
+ # check if column is hidden in table
+ self.assertFalse(
+ self.find(
+ f'#{table_id} thead th.{th_class}'
+ ).is_displayed(),
+ f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
+ )
+ check_box.click()
+ # check if column is visible in table
+ self.assertTrue(
+ self.find(
+ f'#{table_id} thead th.{th_class}'
+ ).is_displayed(),
+ f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
+ )
+
+ def _get_config_nav_item(self, index):
+ config_nav = self.find('#config-nav')
+ return config_nav.find_elements(By.TAG_NAME, 'li')[index]
+
+ def _navigate_to_config_nav(self, nav_id, nav_index):
+ # navigate to the project page
+ self._navigate_to_project_page()
+ # click on "Software recipe" tab
+ soft_recipe = self._get_config_nav_item(nav_index)
+ soft_recipe.click()
+ self.wait_until_visible(f'#{nav_id}')
+
+ def _mixin_test_table_show_rows(self, table_selector, **kwargs):
+ """ Test the show rows feature in the builds table on the all builds page """
+ def test_show_rows(row_to_show, show_row_link):
+ # Check that we can show rows == row_to_show
+ show_row_link.select_by_value(str(row_to_show))
+ self.wait_until_visible(f'#{table_selector} tbody tr', poll=3)
+ # check at least some rows are visible
+ self.assertTrue(
+ len(self.find_all(f'#{table_selector} tbody tr')) > 0
+ )
+ self.wait_until_present(f'#{table_selector} tbody tr')
+ show_rows = self.driver.find_elements(
+ By.XPATH,
+ f'//select[@class="form-control pagesize-{table_selector}"]'
+ )
+ rows_to_show = [10, 25, 50, 100, 150]
+ to_skip = kwargs.get('to_skip', [])
+ # Check show rows
+ for show_row_link in show_rows:
+ show_row_link = Select(show_row_link)
+ for row_to_show in rows_to_show:
+ if row_to_show not in to_skip:
+ test_show_rows(row_to_show, show_row_link)
+
+ def _mixin_test_table_search_input(self, **kwargs):
+ input_selector, input_text, searchBtn_selector, table_selector, *_ = kwargs.values()
+ # Test search input
+ self.wait_until_visible(f'#{input_selector}')
+ recipe_input = self.find(f'#{input_selector}')
+ recipe_input.send_keys(input_text)
+ self.find(f'#{searchBtn_selector}').click()
+ self.wait_until_visible(f'#{table_selector} tbody tr')
+ rows = self.find_all(f'#{table_selector} tbody tr')
+ self.assertTrue(len(rows) > 0)
+
+ def test_create_project(self):
+ """ Create/Test new project using:
+ - Project Name: Any string
+ - Release: Any string
+ - Merge Toaster settings: True or False
+ """
+ self._create_project(project_name=self.PROJECT_NAME)
+
+ def test_image_recipe_editColumn(self):
+ """ Test the edit column feature in image recipe table on project page """
+ self._get_create_builds(success=10, failure=10)
+
+ url = reverse('projectimagerecipes', args=(TestProjectPage.project_id,))
+ self.get(url)
+ self.wait_until_present('#imagerecipestable tbody tr')
+
+ column_list = [
+ 'get_description_or_summary', 'layer_version__get_vcs_reference',
+ 'layer_version__layer__name', 'license', 'recipe-file', 'section',
+ 'version'
+ ]
+
+ # Check that we can hide the edit column
+ self._mixin_test_table_edit_column(
+ 'imagerecipestable',
+ 'edit-columns-button',
+ [f'checkbox-{column}' for column in column_list]
+ )
+
+ def test_page_header_on_project_page(self):
+ """ Check page header in project page:
+ - AT LEFT -> Logo of Yocto project, displayed, clickable
+ - "Toaster"+" Information icon", displayed, clickable
+ - "Server Icon" + "All builds", displayed, clickable
+ - "Directory Icon" + "All projects", displayed, clickable
+ - "Book Icon" + "Documentation", displayed, clickable
+ - AT RIGHT -> button "New project", displayed, clickable
+ """
+ # navigate to the project page
+ self._navigate_to_project_page()
+
+ # check page header
+ # AT LEFT -> Logo of Yocto project
+ logo = self.driver.find_element(
+ By.XPATH,
+ "//div[@class='toaster-navbar-brand']",
+ )
+ logo_img = logo.find_element(By.TAG_NAME, 'img')
+ self.assertTrue(logo_img.is_displayed(),
+ 'Logo of Yocto project not found')
+ self.assertTrue(
+ '/static/img/logo.png' in str(logo_img.get_attribute('src')),
+ 'Logo of Yocto project not found'
+ )
+ # "Toaster"+" Information icon", clickable
+ toaster = self.driver.find_element(
+ By.XPATH,
+ "//div[@class='toaster-navbar-brand']//a[@class='brand']",
+ )
+ self.assertTrue(toaster.is_displayed(), 'Toaster not found')
+ self.assertTrue(toaster.text == 'Toaster')
+ info_sign = self.find('.glyphicon-info-sign')
+ self.assertTrue(info_sign.is_displayed())
+
+ # "Server Icon" + "All builds"
+ all_builds = self.find('#navbar-all-builds')
+ all_builds_link = all_builds.find_element(By.TAG_NAME, 'a')
+ self.assertTrue("All builds" in all_builds_link.text)
+ self.assertTrue(
+ '/toastergui/builds/' in str(all_builds_link.get_attribute('href'))
+ )
+ server_icon = all_builds.find_element(By.TAG_NAME, 'i')
+ self.assertTrue(
+ server_icon.get_attribute('class') == 'glyphicon glyphicon-tasks'
+ )
+ self.assertTrue(server_icon.is_displayed())
+
+ # "Directory Icon" + "All projects"
+ all_projects = self.find('#navbar-all-projects')
+ all_projects_link = all_projects.find_element(By.TAG_NAME, 'a')
+ self.assertTrue("All projects" in all_projects_link.text)
+ self.assertTrue(
+ '/toastergui/projects/' in str(all_projects_link.get_attribute(
+ 'href'))
+ )
+ dir_icon = all_projects.find_element(By.TAG_NAME, 'i')
+ self.assertTrue(
+ dir_icon.get_attribute('class') == 'icon-folder-open'
+ )
+ self.assertTrue(dir_icon.is_displayed())
+
+ # "Book Icon" + "Documentation"
+ toaster_docs_link = self.find('#navbar-docs')
+ toaster_docs_link_link = toaster_docs_link.find_element(By.TAG_NAME,
+ 'a')
+ self.assertTrue("Documentation" in toaster_docs_link_link.text)
+ self.assertTrue(
+ toaster_docs_link_link.get_attribute('href') == 'http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual'
+ )
+ book_icon = toaster_docs_link.find_element(By.TAG_NAME, 'i')
+ self.assertTrue(
+ book_icon.get_attribute('class') == 'glyphicon glyphicon-book'
+ )
+ self.assertTrue(book_icon.is_displayed())
+
+ # AT RIGHT -> button "New project"
+ new_project_button = self.find('#new-project-button')
+ self.assertTrue(new_project_button.is_displayed())
+ self.assertTrue(new_project_button.text == 'New project')
+ new_project_button.click()
+ self.assertTrue(
+ '/toastergui/newproject/' in str(self.driver.current_url)
+ )
+
+ def test_edit_project_name(self):
+ """ Test edit project name:
+ - Click on "Edit" icon button
+ - Change project name
+ - Click on "Save" button
+ - Check project name is changed
+ """
+ # navigate to the project page
+ self._navigate_to_project_page()
+
+ # click on "Edit" icon button
+ self.wait_until_visible('#project-name-container')
+ edit_button = self.find('#project-change-form-toggle')
+ edit_button.click()
+ project_name_input = self.find('#project-name-change-input')
+ self.assertTrue(project_name_input.is_displayed())
+ project_name_input.clear()
+ project_name_input.send_keys('New Name')
+ self.find('#project-name-change-btn').click()
+
+ # check project name is changed
+ self.wait_until_visible('#project-name-container')
+ self.assertTrue(
+ 'New Name' in str(self.find('#project-name-container').text)
+ )
+
+ def test_project_page_tabs(self):
+ """ Test project tabs:
+ - "configuration" tab
+ - "Builds" tab
+ - "Import layers" tab
+ - "New custom image" tab
+ Check search box used to build recipes
+ """
+ # navigate to the project page
+ self._navigate_to_project_page()
+
+ # check "configuration" tab
+ self.wait_until_visible('#topbar-configuration-tab')
+ config_tab = self.find('#topbar-configuration-tab')
+ self.assertTrue(config_tab.get_attribute('class') == 'active')
+ self.assertTrue('Configuration' in str(config_tab.text))
+ self.assertTrue(
+ f"/toastergui/project/{TestProjectPage.project_id}" in str(self.driver.current_url)
+ )
+
+ def get_tabs():
+ # tabs links list
+ return self.driver.find_elements(
+ By.XPATH,
+ '//div[@id="project-topbar"]//li'
+ )
+
+ def check_tab_link(tab_index, tab_name, url):
+ tab = get_tabs()[tab_index]
+ tab_link = tab.find_element(By.TAG_NAME, 'a')
+ self.assertTrue(url in tab_link.get_attribute('href'))
+ self.assertTrue(tab_name in tab_link.text)
+ self.assertTrue(tab.get_attribute('class') == 'active')
+
+ # check "Builds" tab
+ builds_tab = get_tabs()[1]
+ builds_tab.find_element(By.TAG_NAME, 'a').click()
+ check_tab_link(
+ 1,
+ 'Builds',
+ f"/toastergui/project/{TestProjectPage.project_id}/builds"
+ )
+
+ # check "Import layers" tab
+ import_layers_tab = get_tabs()[2]
+ import_layers_tab.find_element(By.TAG_NAME, 'a').click()
+ check_tab_link(
+ 2,
+ 'Import layer',
+ f"/toastergui/project/{TestProjectPage.project_id}/importlayer"
+ )
+
+ # check "New custom image" tab
+ new_custom_image_tab = get_tabs()[3]
+ new_custom_image_tab.find_element(By.TAG_NAME, 'a').click()
+ check_tab_link(
+ 3,
+ 'New custom image',
+ f"/toastergui/project/{TestProjectPage.project_id}/newcustomimage"
+ )
+
+ # check search box can be use to build recipes
+ search_box = self.find('#build-input')
+ search_box.send_keys('core-image-minimal')
+ self.find('#build-button').click()
+ self.wait_until_visible('#latest-builds')
+ lastest_builds = self.driver.find_elements(
+ By.XPATH,
+ '//div[@id="latest-builds"]',
+ )
+ last_build = lastest_builds[0]
+ self.assertTrue(
+ 'core-image-minimal' in str(last_build.text)
+ )
+
+ def test_softwareRecipe_page(self):
+ """ Test software recipe page
+ - Check title "Compatible software recipes" is displayed
+ - Check search input
+ - Check "build recipe" button works
+ - Check software recipe table feature(show/hide column, pagination)
+ """
+ self._navigate_to_config_nav('softwarerecipestable', 4)
+ # check title "Compatible software recipes" is displayed
+ self.assertTrue("Compatible software recipes" in self.get_page_source())
+ # Test search input
+ self._mixin_test_table_search_input(
+ input_selector='search-input-softwarerecipestable',
+ input_text='busybox',
+ searchBtn_selector='search-submit-softwarerecipestable',
+ table_selector='softwarerecipestable'
+ )
+ # check "build recipe" button works
+ rows = self.find_all('#softwarerecipestable tbody tr')
+ image_to_build = rows[0]
+ build_btn = image_to_build.find_element(
+ By.XPATH,
+ '//td[@class="add-del-layers"]//a[1]'
+ )
+ build_btn.click()
+ build_state = wait_until_build(self, 'queued cloning starting parsing failed')
+ lastest_builds = self.driver.find_elements(
+ By.XPATH,
+ '//div[@id="latest-builds"]/div'
+ )
+ self.assertTrue(len(lastest_builds) > 0)
+ last_build = lastest_builds[0]
+ cancel_button = last_build.find_element(
+ By.XPATH,
+ '//span[@class="cancel-build-btn pull-right alert-link"]',
+ )
+ cancel_button.click()
+ if 'starting' not in build_state: # change build state when cancelled in starting state
+ wait_until_build_cancelled(self)
+
+ # check software recipe table feature(show/hide column, pagination)
+ self._navigate_to_config_nav('softwarerecipestable', 4)
+ column_list = [
+ 'get_description_or_summary',
+ 'layer_version__get_vcs_reference',
+ 'layer_version__layer__name',
+ 'license',
+ 'recipe-file',
+ 'section',
+ 'version',
+ ]
+ self._mixin_test_table_edit_column(
+ 'softwarerecipestable',
+ 'edit-columns-button',
+ [f'checkbox-{column}' for column in column_list]
+ )
+ self._navigate_to_config_nav('softwarerecipestable', 4)
+ # check show rows(pagination)
+ self._mixin_test_table_show_rows(
+ table_selector='softwarerecipestable',
+ to_skip=[150],
+ )
+
+ def test_machines_page(self):
+ """ Test Machine page
+ - Check if title "Compatible machines" is displayed
+ - Check search input
+ - Check "Select machine" button works
+ - Check "Add layer" button works
+ - Check Machine table feature(show/hide column, pagination)
+ """
+ self._navigate_to_config_nav('machinestable', 5)
+ # check title "Compatible software recipes" is displayed
+ self.assertTrue("Compatible machines" in self.get_page_source())
+ # Test search input
+ self._mixin_test_table_search_input(
+ input_selector='search-input-machinestable',
+ input_text='qemux86-64',
+ searchBtn_selector='search-submit-machinestable',
+ table_selector='machinestable'
+ )
+ # check "Select machine" button works
+ rows = self.find_all('#machinestable tbody tr')
+ machine_to_select = rows[0]
+ select_btn = machine_to_select.find_element(
+ By.XPATH,
+ '//td[@class="add-del-layers"]//a[1]'
+ )
+ select_btn.send_keys(Keys.RETURN)
+ self.wait_until_visible('#config-nav')
+ project_machine_name = self.find('#project-machine-name')
+ self.assertTrue(
+ 'qemux86-64' in project_machine_name.text
+ )
+ # check "Add layer" button works
+ self._navigate_to_config_nav('machinestable', 5)
+ # Search for a machine whit layer not in project
+ self._mixin_test_table_search_input(
+ input_selector='search-input-machinestable',
+ input_text='qemux86-64-tpm2',
+ searchBtn_selector='search-submit-machinestable',
+ table_selector='machinestable'
+ )
+ self.wait_until_visible('#machinestable tbody tr', poll=3)
+ rows = self.find_all('#machinestable tbody tr')
+ machine_to_add = rows[0]
+ add_btn = machine_to_add.find_element(By.XPATH, '//td[@class="add-del-layers"]')
+ add_btn.click()
+ self.wait_until_visible('#change-notification')
+ change_notification = self.find('#change-notification')
+ self.assertTrue(
+ f'You have added 1 layer to your project' in str(change_notification.text)
+ )
+ # check Machine table feature(show/hide column, pagination)
+ self._navigate_to_config_nav('machinestable', 5)
+ column_list = [
+ 'description',
+ 'layer_version__get_vcs_reference',
+ 'layer_version__layer__name',
+ 'machinefile',
+ ]
+ self._mixin_test_table_edit_column(
+ 'machinestable',
+ 'edit-columns-button',
+ [f'checkbox-{column}' for column in column_list]
+ )
+ self._navigate_to_config_nav('machinestable', 5)
+ # check show rows(pagination)
+ self._mixin_test_table_show_rows(
+ table_selector='machinestable',
+ to_skip=[150],
+ )
+
+ def test_layers_page(self):
+ """ Test layers page
+ - Check if title "Compatible layerss" is displayed
+ - Check search input
+ - Check "Add layer" button works
+ - Check "Remove layer" button works
+ - Check layers table feature(show/hide column, pagination)
+ """
+ self._navigate_to_config_nav('layerstable', 6)
+ # check title "Compatible layers" is displayed
+ self.assertTrue("Compatible layers" in self.get_page_source())
+ # Test search input
+ input_text='meta-tanowrt'
+ self._mixin_test_table_search_input(
+ input_selector='search-input-layerstable',
+ input_text=input_text,
+ searchBtn_selector='search-submit-layerstable',
+ table_selector='layerstable'
+ )
+ # check "Add layer" button works
+ self.wait_until_visible('#layerstable tbody tr', poll=3)
+ rows = self.find_all('#layerstable tbody tr')
+ layer_to_add = rows[0]
+ add_btn = layer_to_add.find_element(
+ By.XPATH,
+ '//td[@class="add-del-layers"]'
+ )
+ add_btn.click()
+ # check modal is displayed
+ self.wait_until_visible('#dependencies-modal', poll=3)
+ list_dependencies = self.find_all('#dependencies-list li')
+ # click on add-layers button
+ add_layers_btn = self.driver.find_element(
+ By.XPATH,
+ '//form[@id="dependencies-modal-form"]//button[@class="btn btn-primary"]'
+ )
+ add_layers_btn.click()
+ self.wait_until_visible('#change-notification')
+ change_notification = self.find('#change-notification')
+ self.assertTrue(
+ f'You have added {len(list_dependencies)+1} layers to your project: {input_text} and its dependencies' in str(change_notification.text)
+ )
+ # check "Remove layer" button works
+ self.wait_until_visible('#layerstable tbody tr', poll=3)
+ rows = self.find_all('#layerstable tbody tr')
+ layer_to_remove = rows[0]
+ remove_btn = layer_to_remove.find_element(
+ By.XPATH,
+ '//td[@class="add-del-layers"]'
+ )
+ remove_btn.click()
+ self.wait_until_visible('#change-notification', poll=2)
+ change_notification = self.find('#change-notification')
+ self.assertTrue(
+ f'You have removed 1 layer from your project: {input_text}' in str(change_notification.text)
+ )
+ # check layers table feature(show/hide column, pagination)
+ self._navigate_to_config_nav('layerstable', 6)
+ column_list = [
+ 'dependencies',
+ 'revision',
+ 'layer__vcs_url',
+ 'git_subdir',
+ 'layer__summary',
+ ]
+ self._mixin_test_table_edit_column(
+ 'layerstable',
+ 'edit-columns-button',
+ [f'checkbox-{column}' for column in column_list]
+ )
+ self._navigate_to_config_nav('layerstable', 6)
+ # check show rows(pagination)
+ self._mixin_test_table_show_rows(
+ table_selector='layerstable',
+ to_skip=[150],
+ )
+
+ def test_distro_page(self):
+ """ Test distros page
+ - Check if title "Compatible distros" is displayed
+ - Check search input
+ - Check "Add layer" button works
+ - Check distro table feature(show/hide column, pagination)
+ """
+ self._navigate_to_config_nav('distrostable', 7)
+ # check title "Compatible distros" is displayed
+ self.assertTrue("Compatible Distros" in self.get_page_source())
+ # Test search input
+ input_text='poky-altcfg'
+ self._mixin_test_table_search_input(
+ input_selector='search-input-distrostable',
+ input_text=input_text,
+ searchBtn_selector='search-submit-distrostable',
+ table_selector='distrostable'
+ )
+ # check "Add distro" button works
+ rows = self.find_all('#distrostable tbody tr')
+ distro_to_add = rows[0]
+ add_btn = distro_to_add.find_element(
+ By.XPATH,
+ '//td[@class="add-del-layers"]//a[1]'
+ )
+ add_btn.click()
+ self.wait_until_visible('#change-notification', poll=2)
+ change_notification = self.find('#change-notification')
+ self.assertTrue(
+ f'You have changed the distro to: {input_text}' in str(change_notification.text)
+ )
+ # check distro table feature(show/hide column, pagination)
+ self._navigate_to_config_nav('distrostable', 7)
+ column_list = [
+ 'description',
+ 'templatefile',
+ 'layer_version__get_vcs_reference',
+ 'layer_version__layer__name',
+ ]
+ self._mixin_test_table_edit_column(
+ 'distrostable',
+ 'edit-columns-button',
+ [f'checkbox-{column}' for column in column_list]
+ )
+ self._navigate_to_config_nav('distrostable', 7)
+ # check show rows(pagination)
+ self._mixin_test_table_show_rows(
+ table_selector='distrostable',
+ to_skip=[150],
+ )
+
+ def test_single_layer_page(self):
+ """ Test layer page
+ - Check if title is displayed
+ - Check add/remove layer button works
+ - Check tabs(layers, recipes, machines) are displayed
+ - Check left section is displayed
+ - Check layer name
+ - Check layer summary
+ - Check layer description
+ """
+ url = reverse("layerdetails", args=(TestProjectPage.project_id, 8))
+ self.get(url)
+ self.wait_until_visible('.page-header')
+ # check title is displayed
+ self.assertTrue(self.find('.page-header h1').is_displayed())
+
+ # check add layer button works
+ remove_layer_btn = self.find('#add-remove-layer-btn')
+ remove_layer_btn.click()
+ self.wait_until_visible('#change-notification', poll=2)
+ change_notification = self.find('#change-notification')
+ self.assertTrue(
+ f'You have removed 1 layer from your project' in str(change_notification.text)
+ )
+ # check add layer button works, 18 is the random layer id
+ add_layer_btn = self.find('#add-remove-layer-btn')
+ add_layer_btn.click()
+ self.wait_until_visible('#change-notification')
+ change_notification = self.find('#change-notification')
+ self.assertTrue(
+ f'You have added 1 layer to your project' in str(change_notification.text)
+ )
+ # check tabs(layers, recipes, machines) are displayed
+ tabs = self.find_all('.nav-tabs li')
+ self.assertEqual(len(tabs), 3)
+ # Check first tab
+ tabs[0].click()
+ self.assertTrue(
+ 'active' in str(self.find('#information').get_attribute('class'))
+ )
+ # Check second tab
+ tabs[1].click()
+ self.assertTrue(
+ 'active' in str(self.find('#recipes').get_attribute('class'))
+ )
+ # Check third tab
+ tabs[2].click()
+ self.assertTrue(
+ 'active' in str(self.find('#machines').get_attribute('class'))
+ )
+ # Check left section is displayed
+ section = self.find('.well')
+ # Check layer name
+ self.assertTrue(
+ section.find_element(By.XPATH, '//h2[1]').is_displayed()
+ )
+ # Check layer summary
+ self.assertTrue("Summary" in section.text)
+ # Check layer description
+ self.assertTrue("Description" in section.text)
+
+ def test_single_recipe_page(self):
+ """ Test recipe page
+ - Check if title is displayed
+ - Check add recipe layer displayed
+ - Check left section is displayed
+ - Check recipe: name, summary, description, Version, Section,
+ License, Approx. packages included, Approx. size, Recipe file
+ """
+ url = reverse("recipedetails", args=(TestProjectPage.project_id, 53428))
+ self.get(url)
+ self.wait_until_visible('.page-header')
+ # check title is displayed
+ self.assertTrue(self.find('.page-header h1').is_displayed())
+ # check add recipe layer displayed
+ add_recipe_layer_btn = self.find('#add-layer-btn')
+ self.assertTrue(add_recipe_layer_btn.is_displayed())
+ # check left section is displayed
+ section = self.find('.well')
+ # Check recipe name
+ self.assertTrue(
+ section.find_element(By.XPATH, '//h2[1]').is_displayed()
+ )
+ # Check recipe sections details info are displayed
+ self.assertTrue("Summary" in section.text)
+ self.assertTrue("Description" in section.text)
+ self.assertTrue("Version" in section.text)
+ self.assertTrue("Section" in section.text)
+ self.assertTrue("License" in section.text)
+ self.assertTrue("Approx. packages included" in section.text)
+ self.assertTrue("Approx. package size" in section.text)
+ self.assertTrue("Recipe file" in section.text)