diff options
Diffstat (limited to 'bitbake/lib/toaster/tests/functional/test_project_page.py')
-rw-r--r-- | bitbake/lib/toaster/tests/functional/test_project_page.py | 792 |
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 @@ | |||
1 | #! /usr/bin/env python3 # | ||
2 | # BitBake Toaster UI tests implementation | ||
3 | # | ||
4 | # Copyright (C) 2023 Savoir-faire Linux | ||
5 | # | ||
6 | # SPDX-License-Identifier: GPL-2.0-only | ||
7 | # | ||
8 | |||
9 | import os | ||
10 | import random | ||
11 | import string | ||
12 | from unittest import skip | ||
13 | import pytest | ||
14 | from django.urls import reverse | ||
15 | from django.utils import timezone | ||
16 | from selenium.webdriver.common.keys import Keys | ||
17 | from selenium.webdriver.support.select import Select | ||
18 | from selenium.common.exceptions import TimeoutException | ||
19 | from tests.functional.functional_helpers import SeleniumFunctionalTestCase | ||
20 | from orm.models import Build, Project, Target | ||
21 | from selenium.webdriver.common.by import By | ||
22 | |||
23 | from .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") | ||
28 | class 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) | ||