summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/tests/functional
diff options
context:
space:
mode:
authorAlassane Yattara <alassane.yattara@savoirfairelinux.com>2023-12-08 02:53:18 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-12-08 17:17:42 +0000
commitd5a6e3b546e7d6691150a5906e531e3b3d6919ee (patch)
tree10f87b155d065a3fbd5cd3e85e392129040c98fc /bitbake/lib/toaster/tests/functional
parent49128ef8bad44bcd863548b240940d2b5a38e32e (diff)
downloadpoky-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.py335
-rw-r--r--bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py495
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
9import string
10import random
11import pytest
12from django.urls import reverse
13from selenium.webdriver import Keys
14from selenium.webdriver.support.select import Select
15from selenium.common.exceptions import TimeoutException
16from tests.functional.functional_helpers import SeleniumFunctionalTestCase
17from selenium.webdriver.common.by import By
18
19from .utils import get_projectId_from_url
20
21
22@pytest.mark.django_db
23@pytest.mark.order("last")
24class 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
9from time import sleep 9import string
10import random
10import pytest 11import pytest
11from django.utils import timezone
12from django.urls import reverse 12from django.urls import reverse
13from selenium.webdriver import Keys 13from selenium.webdriver import Keys
14from selenium.webdriver.support.select import Select 14from selenium.webdriver.support.select import Select
15from selenium.common.exceptions import NoSuchElementException 15from selenium.common.exceptions import TimeoutException
16from orm.models import Build, Project, Target 16from orm.models import Project
17from tests.functional.functional_helpers import SeleniumFunctionalTestCase 17from tests.functional.functional_helpers import SeleniumFunctionalTestCase
18from selenium.webdriver.common.by import By 18from selenium.webdriver.common.by import By
19 19
20from .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")
22class TestProjectConfigTab(SeleniumFunctionalTestCase): 25class 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)