summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/tests/browser
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster/tests/browser')
-rw-r--r--bitbake/lib/toaster/tests/browser/selenium_helpers_base.py88
-rw-r--r--bitbake/lib/toaster/tests/browser/test_all_builds_page.py316
-rw-r--r--bitbake/lib/toaster/tests/browser/test_all_projects_page.py162
-rw-r--r--bitbake/lib/toaster/tests/browser/test_builddashboard_page.py15
-rw-r--r--bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py8
-rw-r--r--bitbake/lib/toaster/tests/browser/test_delete_project.py103
-rw-r--r--bitbake/lib/toaster/tests/browser/test_landing_page.py143
-rw-r--r--bitbake/lib/toaster/tests/browser/test_layerdetails_page.py31
-rw-r--r--bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py24
-rw-r--r--bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py14
-rw-r--r--bitbake/lib/toaster/tests/browser/test_new_project_page.py16
-rw-r--r--bitbake/lib/toaster/tests/browser/test_project_builds_page.py4
-rw-r--r--bitbake/lib/toaster/tests/browser/test_project_config_page.py33
-rw-r--r--bitbake/lib/toaster/tests/browser/test_sample.py10
-rw-r--r--bitbake/lib/toaster/tests/browser/test_toastertable_ui.py11
15 files changed, 867 insertions, 111 deletions
diff --git a/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py b/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
index 644d45fe58..6953541ab5 100644
--- a/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
+++ b/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
@@ -19,11 +19,15 @@ import os
19import time 19import time
20import unittest 20import unittest
21 21
22import pytest
22from selenium import webdriver 23from selenium import webdriver
24from selenium.webdriver.support import expected_conditions as EC
23from selenium.webdriver.support.ui import WebDriverWait 25from selenium.webdriver.support.ui import WebDriverWait
26from selenium.webdriver.common.by import By
24from selenium.webdriver.common.desired_capabilities import DesiredCapabilities 27from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
25from selenium.common.exceptions import NoSuchElementException, \ 28from selenium.common.exceptions import NoSuchElementException, \
26 StaleElementReferenceException, TimeoutException 29 StaleElementReferenceException, TimeoutException, \
30 SessionNotCreatedException, WebDriverException
27 31
28def create_selenium_driver(cls,browser='chrome'): 32def create_selenium_driver(cls,browser='chrome'):
29 # set default browser string based on env (if available) 33 # set default browser string based on env (if available)
@@ -32,9 +36,32 @@ def create_selenium_driver(cls,browser='chrome'):
32 browser = env_browser 36 browser = env_browser
33 37
34 if browser == 'chrome': 38 if browser == 'chrome':
35 return webdriver.Chrome( 39 options = webdriver.ChromeOptions()
36 service_args=["--verbose", "--log-path=selenium.log"] 40 options.add_argument('--headless')
37 ) 41 options.add_argument('--disable-infobars')
42 options.add_argument('--disable-dev-shm-usage')
43 options.add_argument('--no-sandbox')
44 options.add_argument('--remote-debugging-port=9222')
45 try:
46 return webdriver.Chrome(options=options)
47 except SessionNotCreatedException as e:
48 exit_message = "Halting tests prematurely to avoid cascading errors."
49 # check if chrome / chromedriver exists
50 chrome_path = os.popen("find ~/.cache/selenium/chrome/ -name 'chrome' -type f -print -quit").read().strip()
51 if not chrome_path:
52 pytest.exit(f"Failed to install/find chrome.\n{exit_message}")
53 chromedriver_path = os.popen("find ~/.cache/selenium/chromedriver/ -name 'chromedriver' -type f -print -quit").read().strip()
54 if not chromedriver_path:
55 pytest.exit(f"Failed to install/find chromedriver.\n{exit_message}")
56 # check if depends on each are fulfilled
57 depends_chrome = os.popen(f"ldd {chrome_path} | grep 'not found'").read().strip()
58 if depends_chrome:
59 pytest.exit(f"Missing chrome dependencies.\n{depends_chrome}\n{exit_message}")
60 depends_chromedriver = os.popen(f"ldd {chromedriver_path} | grep 'not found'").read().strip()
61 if depends_chromedriver:
62 pytest.exit(f"Missing chromedriver dependencies.\n{depends_chromedriver}\n{exit_message}")
63 # print original error otherwise
64 pytest.exit(f"Failed to start chromedriver.\n{e}\n{exit_message}")
38 elif browser == 'firefox': 65 elif browser == 'firefox':
39 return webdriver.Firefox() 66 return webdriver.Firefox()
40 elif browser == 'marionette': 67 elif browser == 'marionette':
@@ -63,10 +90,12 @@ class Wait(WebDriverWait):
63 Subclass of WebDriverWait with predetermined timeout and poll 90 Subclass of WebDriverWait with predetermined timeout and poll
64 frequency. Also deals with a wider variety of exceptions. 91 frequency. Also deals with a wider variety of exceptions.
65 """ 92 """
66 _TIMEOUT = 10 93 _TIMEOUT = 20
67 _POLL_FREQUENCY = 0.5 94 _POLL_FREQUENCY = 0.5
68 95
69 def __init__(self, driver): 96 def __init__(self, driver, timeout=_TIMEOUT, poll=_POLL_FREQUENCY):
97 self._TIMEOUT = timeout
98 self._POLL_FREQUENCY = poll
70 super(Wait, self).__init__(driver, self._TIMEOUT, self._POLL_FREQUENCY) 99 super(Wait, self).__init__(driver, self._TIMEOUT, self._POLL_FREQUENCY)
71 100
72 def until(self, method, message=''): 101 def until(self, method, message=''):
@@ -85,6 +114,9 @@ class Wait(WebDriverWait):
85 pass 114 pass
86 except StaleElementReferenceException: 115 except StaleElementReferenceException:
87 pass 116 pass
117 except WebDriverException:
118 # selenium.common.exceptions.WebDriverException: Message: unknown error: unhandled inspector error: {"code":-32000,"message":"Node with given id does not belong to the document"}
119 pass
88 120
89 time.sleep(self._poll) 121 time.sleep(self._poll)
90 if time.time() > end_time: 122 if time.time() > end_time:
@@ -138,6 +170,8 @@ class SeleniumTestCaseBase(unittest.TestCase):
138 """ Clean up webdriver driver """ 170 """ Clean up webdriver driver """
139 171
140 cls.driver.quit() 172 cls.driver.quit()
173 # Allow driver resources to be properly freed before proceeding with further tests
174 time.sleep(5)
141 super(SeleniumTestCaseBase, cls).tearDownClass() 175 super(SeleniumTestCaseBase, cls).tearDownClass()
142 176
143 def get(self, url): 177 def get(self, url):
@@ -151,13 +185,20 @@ class SeleniumTestCaseBase(unittest.TestCase):
151 abs_url = '%s%s' % (self.live_server_url, url) 185 abs_url = '%s%s' % (self.live_server_url, url)
152 self.driver.get(abs_url) 186 self.driver.get(abs_url)
153 187
188 try: # Ensure page is loaded before proceeding
189 self.wait_until_visible("#global-nav")
190 except NoSuchElementException:
191 self.driver.implicitly_wait(3)
192 except TimeoutException:
193 self.driver.implicitly_wait(3)
194
154 def find(self, selector): 195 def find(self, selector):
155 """ Find single element by CSS selector """ 196 """ Find single element by CSS selector """
156 return self.driver.find_element_by_css_selector(selector) 197 return self.driver.find_element(By.CSS_SELECTOR, selector)
157 198
158 def find_all(self, selector): 199 def find_all(self, selector):
159 """ Find all elements matching CSS selector """ 200 """ Find all elements matching CSS selector """
160 return self.driver.find_elements_by_css_selector(selector) 201 return self.driver.find_elements(By.CSS_SELECTOR, selector)
161 202
162 def element_exists(self, selector): 203 def element_exists(self, selector):
163 """ 204 """
@@ -170,20 +211,43 @@ class SeleniumTestCaseBase(unittest.TestCase):
170 """ Return the element which currently has focus on the page """ 211 """ Return the element which currently has focus on the page """
171 return self.driver.switch_to.active_element 212 return self.driver.switch_to.active_element
172 213
173 def wait_until_present(self, selector): 214 def wait_until_present(self, selector, timeout=Wait._TIMEOUT):
174 """ Wait until element matching CSS selector is on the page """ 215 """ Wait until element matching CSS selector is on the page """
175 is_present = lambda driver: self.find(selector) 216 is_present = lambda driver: self.find(selector)
176 msg = 'An element matching "%s" should be on the page' % selector 217 msg = 'An element matching "%s" should be on the page' % selector
177 element = Wait(self.driver).until(is_present, msg) 218 element = Wait(self.driver, timeout=timeout).until(is_present, msg)
178 return element 219 return element
179 220
180 def wait_until_visible(self, selector): 221 def wait_until_visible(self, selector, timeout=Wait._TIMEOUT):
181 """ Wait until element matching CSS selector is visible on the page """ 222 """ Wait until element matching CSS selector is visible on the page """
182 is_visible = lambda driver: self.find(selector).is_displayed() 223 is_visible = lambda driver: self.find(selector).is_displayed()
183 msg = 'An element matching "%s" should be visible' % selector 224 msg = 'An element matching "%s" should be visible' % selector
184 Wait(self.driver).until(is_visible, msg) 225 Wait(self.driver, timeout=timeout).until(is_visible, msg)
226 return self.find(selector)
227
228 def wait_until_not_visible(self, selector, timeout=Wait._TIMEOUT):
229 """ Wait until element matching CSS selector is not visible on the page """
230 is_visible = lambda driver: self.find(selector).is_displayed()
231 msg = 'An element matching "%s" should be visible' % selector
232 Wait(self.driver, timeout=timeout).until_not(is_visible, msg)
185 return self.find(selector) 233 return self.find(selector)
186 234
235 def wait_until_clickable(self, selector, timeout=Wait._TIMEOUT):
236 """ Wait until element matching CSS selector is visible on the page """
237 WebDriverWait(self.driver, timeout=timeout).until(lambda driver: self.driver.execute_script("return jQuery.active == 0"))
238 is_clickable = lambda driver: (self.find(selector).is_displayed() and self.find(selector).is_enabled())
239 msg = 'An element matching "%s" should be clickable' % selector
240 Wait(self.driver, timeout=timeout).until(is_clickable, msg)
241 return self.find(selector)
242
243 def wait_until_element_clickable(self, finder, timeout=Wait._TIMEOUT):
244 """ Wait until element is clickable """
245 WebDriverWait(self.driver, timeout=timeout).until(lambda driver: self.driver.execute_script("return jQuery.active == 0"))
246 is_clickable = lambda driver: (finder(driver).is_displayed() and finder(driver).is_enabled())
247 msg = 'A matching element never became be clickable'
248 Wait(self.driver, timeout=timeout).until(is_clickable, msg)
249 return finder(self.driver)
250
187 def wait_until_focused(self, selector): 251 def wait_until_focused(self, selector):
188 """ Wait until element matching CSS selector has focus """ 252 """ Wait until element matching CSS selector has focus """
189 is_focused = \ 253 is_focused = \
diff --git a/bitbake/lib/toaster/tests/browser/test_all_builds_page.py b/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
index 8423d3dab2..9ab81fb11b 100644
--- a/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
@@ -7,13 +7,18 @@
7# SPDX-License-Identifier: GPL-2.0-only 7# SPDX-License-Identifier: GPL-2.0-only
8# 8#
9 9
10import os
10import re 11import re
11 12
12from django.urls import reverse 13from django.urls import reverse
14from selenium.webdriver.support.select import Select
13from django.utils import timezone 15from django.utils import timezone
16from bldcontrol.models import BuildRequest
14from tests.browser.selenium_helpers import SeleniumTestCase 17from tests.browser.selenium_helpers import SeleniumTestCase
15 18
16from orm.models import BitbakeVersion, Release, Project, Build, Target 19from orm.models import BitbakeVersion, Layer, Layer_Version, Recipe, Release, Project, Build, Target, Task
20
21from selenium.webdriver.common.by import By
17 22
18 23
19class TestAllBuildsPage(SeleniumTestCase): 24class TestAllBuildsPage(SeleniumTestCase):
@@ -23,7 +28,8 @@ class TestAllBuildsPage(SeleniumTestCase):
23 CLI_BUILDS_PROJECT_NAME = 'command line builds' 28 CLI_BUILDS_PROJECT_NAME = 'command line builds'
24 29
25 def setUp(self): 30 def setUp(self):
26 bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/', 31 builldir = os.environ.get('BUILDDIR', './')
32 bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
27 branch='master', dirpath='') 33 branch='master', dirpath='')
28 release = Release.objects.create(name='release1', 34 release = Release.objects.create(name='release1',
29 bitbake_version=bbv) 35 bitbake_version=bbv)
@@ -69,7 +75,7 @@ class TestAllBuildsPage(SeleniumTestCase):
69 '[data-role="data-recent-build-buildtime-field"]' % build.id 75 '[data-role="data-recent-build-buildtime-field"]' % build.id
70 76
71 # because this loads via Ajax, wait for it to be visible 77 # because this loads via Ajax, wait for it to be visible
72 self.wait_until_present(selector) 78 self.wait_until_visible(selector)
73 79
74 build_time_spans = self.find_all(selector) 80 build_time_spans = self.find_all(selector)
75 81
@@ -79,7 +85,7 @@ class TestAllBuildsPage(SeleniumTestCase):
79 85
80 def _get_row_for_build(self, build): 86 def _get_row_for_build(self, build):
81 """ Get the table row for the build from the all builds table """ 87 """ Get the table row for the build from the all builds table """
82 self.wait_until_present('#allbuildstable') 88 self.wait_until_visible('#allbuildstable')
83 89
84 rows = self.find_all('#allbuildstable tr') 90 rows = self.find_all('#allbuildstable tr')
85 91
@@ -91,7 +97,7 @@ class TestAllBuildsPage(SeleniumTestCase):
91 found_row = None 97 found_row = None
92 for row in rows: 98 for row in rows:
93 99
94 outcome_links = row.find_elements_by_css_selector(selector) 100 outcome_links = row.find_elements(By.CSS_SELECTOR, selector)
95 if len(outcome_links) == 1: 101 if len(outcome_links) == 1:
96 found_row = row 102 found_row = row
97 break 103 break
@@ -100,6 +106,66 @@ class TestAllBuildsPage(SeleniumTestCase):
100 106
101 return found_row 107 return found_row
102 108
109 def _get_create_builds(self, **kwargs):
110 """ Create a build and return the build object """
111 build1 = Build.objects.create(**self.project1_build_success)
112 build2 = Build.objects.create(**self.project1_build_failure)
113
114 # add some targets to these builds so they have recipe links
115 # (and so we can find the row in the ToasterTable corresponding to
116 # a particular build)
117 Target.objects.create(build=build1, target='foo')
118 Target.objects.create(build=build2, target='bar')
119
120 if kwargs:
121 # Create kwargs.get('success') builds with success status with target
122 # and kwargs.get('failure') builds with failure status with target
123 for i in range(kwargs.get('success', 0)):
124 now = timezone.now()
125 self.project1_build_success['started_on'] = now
126 self.project1_build_success[
127 'completed_on'] = now - timezone.timedelta(days=i)
128 build = Build.objects.create(**self.project1_build_success)
129 Target.objects.create(build=build,
130 target=f'{i}_success_recipe',
131 task=f'{i}_success_task')
132
133 self._set_buildRequest_and_task_on_build(build)
134 for i in range(kwargs.get('failure', 0)):
135 now = timezone.now()
136 self.project1_build_failure['started_on'] = now
137 self.project1_build_failure[
138 'completed_on'] = now - timezone.timedelta(days=i)
139 build = Build.objects.create(**self.project1_build_failure)
140 Target.objects.create(build=build,
141 target=f'{i}_fail_recipe',
142 task=f'{i}_fail_task')
143 self._set_buildRequest_and_task_on_build(build)
144 return build1, build2
145
146 def _create_recipe(self):
147 """ Add a recipe to the database and return it """
148 layer = Layer.objects.create()
149 layer_version = Layer_Version.objects.create(layer=layer)
150 return Recipe.objects.create(name='recipe_foo', layer_version=layer_version)
151
152 def _set_buildRequest_and_task_on_build(self, build):
153 """ Set buildRequest and task on build """
154 build.recipes_parsed = 1
155 build.save()
156 buildRequest = BuildRequest.objects.create(
157 build=build,
158 project=self.project1,
159 state=BuildRequest.REQ_COMPLETED)
160 build.build_request = buildRequest
161 recipe = self._create_recipe()
162 task = Task.objects.create(build=build,
163 recipe=recipe,
164 task_name='task',
165 outcome=Task.OUTCOME_SUCCESS)
166 task.save()
167 build.save()
168
103 def test_show_tasks_with_suffix(self): 169 def test_show_tasks_with_suffix(self):
104 """ Task should be shown as suffix on build name """ 170 """ Task should be shown as suffix on build name """
105 build = Build.objects.create(**self.project1_build_success) 171 build = Build.objects.create(**self.project1_build_success)
@@ -109,7 +175,7 @@ class TestAllBuildsPage(SeleniumTestCase):
109 175
110 url = reverse('all-builds') 176 url = reverse('all-builds')
111 self.get(url) 177 self.get(url)
112 self.wait_until_present('td[class="target"]') 178 self.wait_until_visible('td[class="target"]')
113 179
114 cell = self.find('td[class="target"]') 180 cell = self.find('td[class="target"]')
115 content = cell.get_attribute('innerHTML') 181 content = cell.get_attribute('innerHTML')
@@ -126,23 +192,26 @@ class TestAllBuildsPage(SeleniumTestCase):
126 but should be shown for other builds 192 but should be shown for other builds
127 """ 193 """
128 build1 = Build.objects.create(**self.project1_build_success) 194 build1 = Build.objects.create(**self.project1_build_success)
129 default_build = Build.objects.create(**self.default_project_build_success) 195 default_build = Build.objects.create(
196 **self.default_project_build_success)
130 197
131 url = reverse('all-builds') 198 url = reverse('all-builds')
132 self.get(url) 199 self.get(url)
133 200
134 # shouldn't see a rebuild button for command-line builds
135 selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % default_build.id
136 run_again_button = self.find_all(selector)
137 self.assertEqual(len(run_again_button), 0,
138 'should not see a rebuild button for cli builds')
139
140 # should see a rebuild button for non-command-line builds 201 # should see a rebuild button for non-command-line builds
202 self.wait_until_visible('#allbuildstable tbody tr')
203 self.wait_until_visible('.rebuild-btn')
141 selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id 204 selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id
142 run_again_button = self.find_all(selector) 205 run_again_button = self.find_all(selector)
143 self.assertEqual(len(run_again_button), 1, 206 self.assertEqual(len(run_again_button), 1,
144 'should see a rebuild button for non-cli builds') 207 'should see a rebuild button for non-cli builds')
145 208
209 # shouldn't see a rebuild button for command-line builds
210 selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % default_build.id
211 run_again_button = self.find_all(selector)
212 self.assertEqual(len(run_again_button), 0,
213 'should not see a rebuild button for cli builds')
214
146 def test_tooltips_on_project_name(self): 215 def test_tooltips_on_project_name(self):
147 """ 216 """
148 Test tooltips shown next to project name in the main table 217 Test tooltips shown next to project name in the main table
@@ -156,6 +225,7 @@ class TestAllBuildsPage(SeleniumTestCase):
156 225
157 url = reverse('all-builds') 226 url = reverse('all-builds')
158 self.get(url) 227 self.get(url)
228 self.wait_until_visible('#allbuildstable')
159 229
160 # get the project name cells from the table 230 # get the project name cells from the table
161 cells = self.find_all('#allbuildstable td[class="project"]') 231 cells = self.find_all('#allbuildstable td[class="project"]')
@@ -164,7 +234,7 @@ class TestAllBuildsPage(SeleniumTestCase):
164 234
165 for cell in cells: 235 for cell in cells:
166 content = cell.get_attribute('innerHTML') 236 content = cell.get_attribute('innerHTML')
167 help_icons = cell.find_elements_by_css_selector(selector) 237 help_icons = cell.find_elements(By.CSS_SELECTOR, selector)
168 238
169 if re.search(self.PROJECT_NAME, content): 239 if re.search(self.PROJECT_NAME, content):
170 # no help icon next to non-cli project name 240 # no help icon next to non-cli project name
@@ -184,38 +254,224 @@ class TestAllBuildsPage(SeleniumTestCase):
184 recent builds area; failed builds should not have links on the time column, 254 recent builds area; failed builds should not have links on the time column,
185 or in the recent builds area 255 or in the recent builds area
186 """ 256 """
187 build1 = Build.objects.create(**self.project1_build_success) 257 build1, build2 = self._get_create_builds()
188 build2 = Build.objects.create(**self.project1_build_failure)
189
190 # add some targets to these builds so they have recipe links
191 # (and so we can find the row in the ToasterTable corresponding to
192 # a particular build)
193 Target.objects.create(build=build1, target='foo')
194 Target.objects.create(build=build2, target='bar')
195 258
196 url = reverse('all-builds') 259 url = reverse('all-builds')
197 self.get(url) 260 self.get(url)
261 self.wait_until_visible('#allbuildstable')
198 262
199 # test recent builds area for successful build 263 # test recent builds area for successful build
200 element = self._get_build_time_element(build1) 264 element = self._get_build_time_element(build1)
201 links = element.find_elements_by_css_selector('a') 265 links = element.find_elements(By.CSS_SELECTOR, 'a')
202 msg = 'should be a link on the build time for a successful recent build' 266 msg = 'should be a link on the build time for a successful recent build'
203 self.assertEquals(len(links), 1, msg) 267 self.assertEqual(len(links), 1, msg)
204 268
205 # test recent builds area for failed build 269 # test recent builds area for failed build
206 element = self._get_build_time_element(build2) 270 element = self._get_build_time_element(build2)
207 links = element.find_elements_by_css_selector('a') 271 links = element.find_elements(By.CSS_SELECTOR, 'a')
208 msg = 'should not be a link on the build time for a failed recent build' 272 msg = 'should not be a link on the build time for a failed recent build'
209 self.assertEquals(len(links), 0, msg) 273 self.assertEqual(len(links), 0, msg)
210 274
211 # test the time column for successful build 275 # test the time column for successful build
212 build1_row = self._get_row_for_build(build1) 276 build1_row = self._get_row_for_build(build1)
213 links = build1_row.find_elements_by_css_selector('td.time a') 277 links = build1_row.find_elements(By.CSS_SELECTOR, 'td.time a')
214 msg = 'should be a link on the build time for a successful build' 278 msg = 'should be a link on the build time for a successful build'
215 self.assertEquals(len(links), 1, msg) 279 self.assertEqual(len(links), 1, msg)
216 280
217 # test the time column for failed build 281 # test the time column for failed build
218 build2_row = self._get_row_for_build(build2) 282 build2_row = self._get_row_for_build(build2)
219 links = build2_row.find_elements_by_css_selector('td.time a') 283 links = build2_row.find_elements(By.CSS_SELECTOR, 'td.time a')
220 msg = 'should not be a link on the build time for a failed build' 284 msg = 'should not be a link on the build time for a failed build'
221 self.assertEquals(len(links), 0, msg) 285 self.assertEqual(len(links), 0, msg)
286
287 def test_builds_table_search_box(self):
288 """ Test the search box in the builds table on the all builds page """
289 self._get_create_builds()
290
291 url = reverse('all-builds')
292 self.get(url)
293
294 # Check search box is present and works
295 self.wait_until_visible('#allbuildstable tbody tr')
296 search_box = self.find('#search-input-allbuildstable')
297 self.assertTrue(search_box.is_displayed())
298
299 # Check that we can search for a build by recipe name
300 search_box.send_keys('foo')
301 search_btn = self.find('#search-submit-allbuildstable')
302 search_btn.click()
303 self.wait_until_visible('#allbuildstable tbody tr')
304 rows = self.find_all('#allbuildstable tbody tr')
305 self.assertTrue(len(rows) >= 1)
306
307 def test_filtering_on_failure_tasks_column(self):
308 """ Test the filtering on failure tasks column in the builds table on the all builds page """
309 def _check_if_filter_failed_tasks_column_is_visible():
310 # check if failed tasks filter column is visible, if not click on it
311 # Check edit column
312 edit_column = self.find('#edit-columns-button')
313 self.assertTrue(edit_column.is_displayed())
314 edit_column.click()
315 # Check dropdown is visible
316 self.wait_until_visible('ul.dropdown-menu.editcol')
317 filter_fails_task_checkbox = self.find('#checkbox-failed_tasks')
318 if not filter_fails_task_checkbox.is_selected():
319 filter_fails_task_checkbox.click()
320 edit_column.click()
321
322 self._get_create_builds(success=10, failure=10)
323
324 url = reverse('all-builds')
325 self.get(url)
326
327 # Check filtering on failure tasks column
328 self.wait_until_visible('#allbuildstable tbody tr')
329 _check_if_filter_failed_tasks_column_is_visible()
330 failed_tasks_filter = self.find('#failed_tasks_filter')
331 failed_tasks_filter.click()
332 # Check popup is visible
333 self.wait_until_visible('#filter-modal-allbuildstable')
334 self.assertTrue(
335 self.find('#filter-modal-allbuildstable').is_displayed())
336 # Check that we can filter by failure tasks
337 build_without_failure_tasks = self.find(
338 '#failed_tasks_filter\\:without_failed_tasks')
339 build_without_failure_tasks.click()
340 # click on apply button
341 self.find('#filter-modal-allbuildstable .btn-primary').click()
342 self.wait_until_visible('#allbuildstable tbody tr')
343 # Check if filter is applied, by checking if failed_tasks_filter has btn-primary class
344 self.assertTrue(self.find('#failed_tasks_filter').get_attribute(
345 'class').find('btn-primary') != -1)
346
347 def test_filtering_on_completedOn_column(self):
348 """ Test the filtering on completed_on column in the builds table on the all builds page """
349 self._get_create_builds(success=10, failure=10)
350
351 url = reverse('all-builds')
352 self.get(url)
353
354 # Check filtering on failure tasks column
355 self.wait_until_visible('#allbuildstable tbody tr')
356 completed_on_filter = self.find('#completed_on_filter')
357 completed_on_filter.click()
358 # Check popup is visible
359 self.wait_until_visible('#filter-modal-allbuildstable')
360 self.assertTrue(
361 self.find('#filter-modal-allbuildstable').is_displayed())
362 # Check that we can filter by failure tasks
363 build_without_failure_tasks = self.find(
364 '#completed_on_filter\\:date_range')
365 build_without_failure_tasks.click()
366 # click on apply button
367 self.find('#filter-modal-allbuildstable .btn-primary').click()
368 self.wait_until_visible('#allbuildstable tbody tr')
369 # Check if filter is applied, by checking if completed_on_filter has btn-primary class
370 self.assertTrue(self.find('#completed_on_filter').get_attribute(
371 'class').find('btn-primary') != -1)
372
373 # Filter by date range
374 self.find('#completed_on_filter').click()
375 self.wait_until_visible('#filter-modal-allbuildstable')
376 date_ranges = self.driver.find_elements(
377 By.XPATH, '//input[@class="form-control hasDatepicker"]')
378 today = timezone.now()
379 yestersday = today - timezone.timedelta(days=1)
380 date_ranges[0].send_keys(yestersday.strftime('%Y-%m-%d'))
381 date_ranges[1].send_keys(today.strftime('%Y-%m-%d'))
382 self.find('#filter-modal-allbuildstable .btn-primary').click()
383 self.wait_until_visible('#allbuildstable tbody tr')
384 self.assertTrue(self.find('#completed_on_filter').get_attribute(
385 'class').find('btn-primary') != -1)
386 # Check if filter is applied, number of builds displayed should be 6
387 self.assertTrue(len(self.find_all('#allbuildstable tbody tr')) >= 4)
388
389 def test_builds_table_editColumn(self):
390 """ Test the edit column feature in the builds table on the all builds page """
391 self._get_create_builds(success=10, failure=10)
392
393 def test_edit_column(check_box_id):
394 # Check that we can hide/show table column
395 check_box = self.find(f'#{check_box_id}')
396 th_class = str(check_box_id).replace('checkbox-', '')
397 if check_box.is_selected():
398 # check if column is visible in table
399 self.assertTrue(
400 self.find(
401 f'#allbuildstable thead th.{th_class}'
402 ).is_displayed(),
403 f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
404 )
405 check_box.click()
406 # check if column is hidden in table
407 self.assertFalse(
408 self.find(
409 f'#allbuildstable thead th.{th_class}'
410 ).is_displayed(),
411 f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
412 )
413 else:
414 # check if column is hidden in table
415 self.assertFalse(
416 self.find(
417 f'#allbuildstable thead th.{th_class}'
418 ).is_displayed(),
419 f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
420 )
421 check_box.click()
422 # check if column is visible in table
423 self.assertTrue(
424 self.find(
425 f'#allbuildstable thead th.{th_class}'
426 ).is_displayed(),
427 f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
428 )
429 url = reverse('all-builds')
430 self.get(url)
431 self.wait_until_visible('#allbuildstable tbody tr')
432
433 # Check edit column
434 edit_column = self.find('#edit-columns-button')
435 self.assertTrue(edit_column.is_displayed())
436 edit_column.click()
437 # Check dropdown is visible
438 self.wait_until_visible('ul.dropdown-menu.editcol')
439
440 # Check that we can hide the edit column
441 test_edit_column('checkbox-errors_no')
442 test_edit_column('checkbox-failed_tasks')
443 test_edit_column('checkbox-image_files')
444 test_edit_column('checkbox-project')
445 test_edit_column('checkbox-started_on')
446 test_edit_column('checkbox-time')
447 test_edit_column('checkbox-warnings_no')
448
449 def test_builds_table_show_rows(self):
450 """ Test the show rows feature in the builds table on the all builds page """
451 self._get_create_builds(success=100, failure=100)
452
453 def test_show_rows(row_to_show, show_row_link):
454 # Check that we can show rows == row_to_show
455 show_row_link.select_by_value(str(row_to_show))
456 self.wait_until_visible('#allbuildstable tbody tr')
457 # check at least some rows are visible
458 self.assertTrue(
459 len(self.find_all('#allbuildstable tbody tr')) > 0
460 )
461
462 url = reverse('all-builds')
463 self.get(url)
464 self.wait_until_visible('#allbuildstable tbody tr')
465
466 show_rows = self.driver.find_elements(
467 By.XPATH,
468 '//select[@class="form-control pagesize-allbuildstable"]'
469 )
470 # Check show rows
471 for show_row_link in show_rows:
472 show_row_link = Select(show_row_link)
473 test_show_rows(10, show_row_link)
474 test_show_rows(25, show_row_link)
475 test_show_rows(50, show_row_link)
476 test_show_rows(100, show_row_link)
477 test_show_rows(150, show_row_link)
diff --git a/bitbake/lib/toaster/tests/browser/test_all_projects_page.py b/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
index 15b03400f9..05e12892be 100644
--- a/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
@@ -7,15 +7,20 @@
7# SPDX-License-Identifier: GPL-2.0-only 7# SPDX-License-Identifier: GPL-2.0-only
8# 8#
9 9
10import os
10import re 11import re
11 12
12from django.urls import reverse 13from django.urls import reverse
13from django.utils import timezone 14from django.utils import timezone
15from selenium.webdriver.support.select import Select
14from tests.browser.selenium_helpers import SeleniumTestCase 16from tests.browser.selenium_helpers import SeleniumTestCase
15 17
16from orm.models import BitbakeVersion, Release, Project, Build 18from orm.models import BitbakeVersion, Release, Project, Build
17from orm.models import ProjectVariable 19from orm.models import ProjectVariable
18 20
21from selenium.webdriver.common.by import By
22
23
19class TestAllProjectsPage(SeleniumTestCase): 24class TestAllProjectsPage(SeleniumTestCase):
20 """ Browser tests for projects page /projects/ """ 25 """ Browser tests for projects page /projects/ """
21 26
@@ -25,7 +30,8 @@ class TestAllProjectsPage(SeleniumTestCase):
25 30
26 def setUp(self): 31 def setUp(self):
27 """ Add default project manually """ 32 """ Add default project manually """
28 project = Project.objects.create_project(self.CLI_BUILDS_PROJECT_NAME, None) 33 project = Project.objects.create_project(
34 self.CLI_BUILDS_PROJECT_NAME, None)
29 self.default_project = project 35 self.default_project = project
30 self.default_project.is_default = True 36 self.default_project.is_default = True
31 self.default_project.save() 37 self.default_project.save()
@@ -35,6 +41,17 @@ class TestAllProjectsPage(SeleniumTestCase):
35 41
36 self.release = None 42 self.release = None
37 43
44 def _create_projects(self, nb_project=10):
45 projects = []
46 for i in range(1, nb_project + 1):
47 projects.append(
48 Project(
49 name='test project {}'.format(i),
50 release=self.release,
51 )
52 )
53 Project.objects.bulk_create(projects)
54
38 def _add_build_to_default_project(self): 55 def _add_build_to_default_project(self):
39 """ Add a build to the default project (not used in all tests) """ 56 """ Add a build to the default project (not used in all tests) """
40 now = timezone.now() 57 now = timezone.now()
@@ -45,12 +62,14 @@ class TestAllProjectsPage(SeleniumTestCase):
45 62
46 def _add_non_default_project(self): 63 def _add_non_default_project(self):
47 """ Add another project """ 64 """ Add another project """
48 bbv = BitbakeVersion.objects.create(name='test bbv', giturl='/tmp/', 65 builldir = os.environ.get('BUILDDIR', './')
66 bbv = BitbakeVersion.objects.create(name='test bbv', giturl=f'{builldir}/',
49 branch='master', dirpath='') 67 branch='master', dirpath='')
50 self.release = Release.objects.create(name='test release', 68 self.release = Release.objects.create(name='test release',
51 branch_name='master', 69 branch_name='master',
52 bitbake_version=bbv) 70 bitbake_version=bbv)
53 self.project = Project.objects.create_project(self.PROJECT_NAME, self.release) 71 self.project = Project.objects.create_project(
72 self.PROJECT_NAME, self.release)
54 self.project.is_default = False 73 self.project.is_default = False
55 self.project.save() 74 self.project.save()
56 75
@@ -62,7 +81,7 @@ class TestAllProjectsPage(SeleniumTestCase):
62 81
63 def _get_row_for_project(self, project_name): 82 def _get_row_for_project(self, project_name):
64 """ Get the HTML row for a project, or None if not found """ 83 """ Get the HTML row for a project, or None if not found """
65 self.wait_until_present('#projectstable tbody tr') 84 self.wait_until_visible('#projectstable tbody tr')
66 rows = self.find_all('#projectstable tbody tr') 85 rows = self.find_all('#projectstable tbody tr')
67 86
68 # find the row with a project name matching the one supplied 87 # find the row with a project name matching the one supplied
@@ -93,7 +112,8 @@ class TestAllProjectsPage(SeleniumTestCase):
93 url = reverse('all-projects') 112 url = reverse('all-projects')
94 self.get(url) 113 self.get(url)
95 114
96 default_project_row = self._get_row_for_project(self.default_project.name) 115 default_project_row = self._get_row_for_project(
116 self.default_project.name)
97 117
98 self.assertNotEqual(default_project_row, None, 118 self.assertNotEqual(default_project_row, None,
99 'default project "cli builds" should be in page') 119 'default project "cli builds" should be in page')
@@ -113,11 +133,12 @@ class TestAllProjectsPage(SeleniumTestCase):
113 self.wait_until_visible("#projectstable tr") 133 self.wait_until_visible("#projectstable tr")
114 134
115 # find the row for the default project 135 # find the row for the default project
116 default_project_row = self._get_row_for_project(self.default_project.name) 136 default_project_row = self._get_row_for_project(
137 self.default_project.name)
117 138
118 # check the release text for the default project 139 # check the release text for the default project
119 selector = 'span[data-project-field="release"] span.text-muted' 140 selector = 'span[data-project-field="release"] span.text-muted'
120 element = default_project_row.find_element_by_css_selector(selector) 141 element = default_project_row.find_element(By.CSS_SELECTOR, selector)
121 text = element.text.strip() 142 text = element.text.strip()
122 self.assertEqual(text, 'Not applicable', 143 self.assertEqual(text, 'Not applicable',
123 'release should be "not applicable" for default project') 144 'release should be "not applicable" for default project')
@@ -127,7 +148,7 @@ class TestAllProjectsPage(SeleniumTestCase):
127 148
128 # check the link in the release cell for the other project 149 # check the link in the release cell for the other project
129 selector = 'span[data-project-field="release"]' 150 selector = 'span[data-project-field="release"]'
130 element = other_project_row.find_element_by_css_selector(selector) 151 element = other_project_row.find_element(By.CSS_SELECTOR, selector)
131 text = element.text.strip() 152 text = element.text.strip()
132 self.assertEqual(text, self.release.name, 153 self.assertEqual(text, self.release.name,
133 'release name should be shown for non-default project') 154 'release name should be shown for non-default project')
@@ -148,11 +169,12 @@ class TestAllProjectsPage(SeleniumTestCase):
148 self.wait_until_visible("#projectstable tr") 169 self.wait_until_visible("#projectstable tr")
149 170
150 # find the row for the default project 171 # find the row for the default project
151 default_project_row = self._get_row_for_project(self.default_project.name) 172 default_project_row = self._get_row_for_project(
173 self.default_project.name)
152 174
153 # check the machine cell for the default project 175 # check the machine cell for the default project
154 selector = 'span[data-project-field="machine"] span.text-muted' 176 selector = 'span[data-project-field="machine"] span.text-muted'
155 element = default_project_row.find_element_by_css_selector(selector) 177 element = default_project_row.find_element(By.CSS_SELECTOR, selector)
156 text = element.text.strip() 178 text = element.text.strip()
157 self.assertEqual(text, 'Not applicable', 179 self.assertEqual(text, 'Not applicable',
158 'machine should be not applicable for default project') 180 'machine should be not applicable for default project')
@@ -162,7 +184,7 @@ class TestAllProjectsPage(SeleniumTestCase):
162 184
163 # check the link in the machine cell for the other project 185 # check the link in the machine cell for the other project
164 selector = 'span[data-project-field="machine"]' 186 selector = 'span[data-project-field="machine"]'
165 element = other_project_row.find_element_by_css_selector(selector) 187 element = other_project_row.find_element(By.CSS_SELECTOR, selector)
166 text = element.text.strip() 188 text = element.text.strip()
167 self.assertEqual(text, self.MACHINE_NAME, 189 self.assertEqual(text, self.MACHINE_NAME,
168 'machine name should be shown for non-default project') 190 'machine name should be shown for non-default project')
@@ -183,13 +205,15 @@ class TestAllProjectsPage(SeleniumTestCase):
183 self.get(reverse('all-projects')) 205 self.get(reverse('all-projects'))
184 206
185 # find the row for the default project 207 # find the row for the default project
186 default_project_row = self._get_row_for_project(self.default_project.name) 208 default_project_row = self._get_row_for_project(
209 self.default_project.name)
187 210
188 # check the link on the name field 211 # check the link on the name field
189 selector = 'span[data-project-field="name"] a' 212 selector = 'span[data-project-field="name"] a'
190 element = default_project_row.find_element_by_css_selector(selector) 213 element = default_project_row.find_element(By.CSS_SELECTOR, selector)
191 link_url = element.get_attribute('href').strip() 214 link_url = element.get_attribute('href').strip()
192 expected_url = reverse('projectbuilds', args=(self.default_project.id,)) 215 expected_url = reverse(
216 'projectbuilds', args=(self.default_project.id,))
193 msg = 'link on default project name should point to builds but was %s' % link_url 217 msg = 'link on default project name should point to builds but was %s' % link_url
194 self.assertTrue(link_url.endswith(expected_url), msg) 218 self.assertTrue(link_url.endswith(expected_url), msg)
195 219
@@ -198,8 +222,116 @@ class TestAllProjectsPage(SeleniumTestCase):
198 222
199 # check the link for the other project 223 # check the link for the other project
200 selector = 'span[data-project-field="name"] a' 224 selector = 'span[data-project-field="name"] a'
201 element = other_project_row.find_element_by_css_selector(selector) 225 element = other_project_row.find_element(By.CSS_SELECTOR, selector)
202 link_url = element.get_attribute('href').strip() 226 link_url = element.get_attribute('href').strip()
203 expected_url = reverse('project', args=(self.project.id,)) 227 expected_url = reverse('project', args=(self.project.id,))
204 msg = 'link on project name should point to configuration but was %s' % link_url 228 msg = 'link on project name should point to configuration but was %s' % link_url
205 self.assertTrue(link_url.endswith(expected_url), msg) 229 self.assertTrue(link_url.endswith(expected_url), msg)
230
231 def test_allProject_table_search_box(self):
232 """ Test the search box in the all project table on the all projects page """
233 self._create_projects()
234
235 url = reverse('all-projects')
236 self.get(url)
237
238 # Chseck search box is present and works
239 self.wait_until_visible('#projectstable tbody tr')
240 search_box = self.find('#search-input-projectstable')
241 self.assertTrue(search_box.is_displayed())
242
243 # Check that we can search for a project by project name
244 search_box.send_keys('test project 10')
245 search_btn = self.find('#search-submit-projectstable')
246 search_btn.click()
247 self.wait_until_visible('#projectstable tbody tr')
248 rows = self.find_all('#projectstable tbody tr')
249 self.assertTrue(len(rows) == 1)
250
251 def test_allProject_table_editColumn(self):
252 """ Test the edit column feature in the projects table on the all projects page """
253 self._create_projects()
254
255 def test_edit_column(check_box_id):
256 # Check that we can hide/show table column
257 check_box = self.find(f'#{check_box_id}')
258 th_class = str(check_box_id).replace('checkbox-', '')
259 if check_box.is_selected():
260 # check if column is visible in table
261 self.assertTrue(
262 self.find(
263 f'#projectstable thead th.{th_class}'
264 ).is_displayed(),
265 f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
266 )
267 check_box.click()
268 # check if column is hidden in table
269 self.assertFalse(
270 self.find(
271 f'#projectstable thead th.{th_class}'
272 ).is_displayed(),
273 f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
274 )
275 else:
276 # check if column is hidden in table
277 self.assertFalse(
278 self.find(
279 f'#projectstable thead th.{th_class}'
280 ).is_displayed(),
281 f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
282 )
283 check_box.click()
284 # check if column is visible in table
285 self.assertTrue(
286 self.find(
287 f'#projectstable thead th.{th_class}'
288 ).is_displayed(),
289 f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
290 )
291 url = reverse('all-projects')
292 self.get(url)
293 self.wait_until_visible('#projectstable tbody tr')
294
295 # Check edit column
296 edit_column = self.find('#edit-columns-button')
297 self.assertTrue(edit_column.is_displayed())
298 edit_column.click()
299 # Check dropdown is visible
300 self.wait_until_visible('ul.dropdown-menu.editcol')
301
302 # Check that we can hide the edit column
303 test_edit_column('checkbox-errors')
304 test_edit_column('checkbox-image_files')
305 test_edit_column('checkbox-last_build_outcome')
306 test_edit_column('checkbox-recipe_name')
307 test_edit_column('checkbox-warnings')
308
309 def test_allProject_table_show_rows(self):
310 """ Test the show rows feature in the projects table on the all projects page """
311 self._create_projects(nb_project=200)
312
313 def test_show_rows(row_to_show, show_row_link):
314 # Check that we can show rows == row_to_show
315 show_row_link.select_by_value(str(row_to_show))
316 self.wait_until_visible('#projectstable tbody tr')
317 # check at least some rows are visible
318 self.assertTrue(
319 len(self.find_all('#projectstable tbody tr')) > 0
320 )
321
322 url = reverse('all-projects')
323 self.get(url)
324 self.wait_until_visible('#projectstable tbody tr')
325
326 show_rows = self.driver.find_elements(
327 By.XPATH,
328 '//select[@class="form-control pagesize-projectstable"]'
329 )
330 # Check show rows
331 for show_row_link in show_rows:
332 show_row_link = Select(show_row_link)
333 test_show_rows(10, show_row_link)
334 test_show_rows(25, show_row_link)
335 test_show_rows(50, show_row_link)
336 test_show_rows(100, show_row_link)
337 test_show_rows(150, show_row_link)
diff --git a/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py b/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
index efcd89b346..82367108e2 100644
--- a/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
@@ -7,6 +7,7 @@
7# SPDX-License-Identifier: GPL-2.0-only 7# SPDX-License-Identifier: GPL-2.0-only
8# 8#
9 9
10import os
10from django.urls import reverse 11from django.urls import reverse
11from django.utils import timezone 12from django.utils import timezone
12 13
@@ -15,11 +16,14 @@ from tests.browser.selenium_helpers import SeleniumTestCase
15from orm.models import Project, Release, BitbakeVersion, Build, LogMessage 16from orm.models import Project, Release, BitbakeVersion, Build, LogMessage
16from orm.models import Layer, Layer_Version, Recipe, CustomImageRecipe, Variable 17from orm.models import Layer, Layer_Version, Recipe, CustomImageRecipe, Variable
17 18
19from selenium.webdriver.common.by import By
20
18class TestBuildDashboardPage(SeleniumTestCase): 21class TestBuildDashboardPage(SeleniumTestCase):
19 """ Tests for the build dashboard /build/X """ 22 """ Tests for the build dashboard /build/X """
20 23
21 def setUp(self): 24 def setUp(self):
22 bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/', 25 builldir = os.environ.get('BUILDDIR', './')
26 bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
23 branch='master', dirpath="") 27 branch='master', dirpath="")
24 release = Release.objects.create(name='release1', 28 release = Release.objects.create(name='release1',
25 bitbake_version=bbv) 29 bitbake_version=bbv)
@@ -158,6 +162,7 @@ class TestBuildDashboardPage(SeleniumTestCase):
158 """ 162 """
159 url = reverse('builddashboard', args=(build.id,)) 163 url = reverse('builddashboard', args=(build.id,))
160 self.get(url) 164 self.get(url)
165 self.wait_until_visible('#global-nav')
161 166
162 def _get_build_dashboard_errors(self, build): 167 def _get_build_dashboard_errors(self, build):
163 """ 168 """
@@ -183,7 +188,7 @@ class TestBuildDashboardPage(SeleniumTestCase):
183 188
184 found = False 189 found = False
185 for element in message_elements: 190 for element in message_elements:
186 log_message_text = element.find_element_by_tag_name('pre').text.strip() 191 log_message_text = element.find_element(By.TAG_NAME, 'pre').text.strip()
187 text_matches = (log_message_text == expected_text) 192 text_matches = (log_message_text == expected_text)
188 193
189 log_message_pk = element.get_attribute('data-log-message-id') 194 log_message_pk = element.get_attribute('data-log-message-id')
@@ -213,7 +218,7 @@ class TestBuildDashboardPage(SeleniumTestCase):
213 the WebElement modal match the list of text values in expected 218 the WebElement modal match the list of text values in expected
214 """ 219 """
215 # labels containing the radio buttons we're testing for 220 # labels containing the radio buttons we're testing for
216 labels = modal.find_elements_by_css_selector(".radio") 221 labels = modal.find_elements(By.CSS_SELECTOR,".radio")
217 222
218 labels_text = [lab.text for lab in labels] 223 labels_text = [lab.text for lab in labels]
219 self.assertEqual(len(labels_text), len(expected)) 224 self.assertEqual(len(labels_text), len(expected))
@@ -248,7 +253,7 @@ class TestBuildDashboardPage(SeleniumTestCase):
248 selector = '[data-role="edit-custom-image-trigger"]' 253 selector = '[data-role="edit-custom-image-trigger"]'
249 self.click(selector) 254 self.click(selector)
250 255
251 modal = self.driver.find_element_by_id('edit-custom-image-modal') 256 modal = self.driver.find_element(By.ID, 'edit-custom-image-modal')
252 self.wait_until_visible("#edit-custom-image-modal") 257 self.wait_until_visible("#edit-custom-image-modal")
253 258
254 # recipes we expect to see in the edit custom image modal 259 # recipes we expect to see in the edit custom image modal
@@ -270,7 +275,7 @@ class TestBuildDashboardPage(SeleniumTestCase):
270 selector = '[data-role="new-custom-image-trigger"]' 275 selector = '[data-role="new-custom-image-trigger"]'
271 self.click(selector) 276 self.click(selector)
272 277
273 modal = self.driver.find_element_by_id('new-custom-image-modal') 278 modal = self.driver.find_element(By.ID,'new-custom-image-modal')
274 self.wait_until_visible("#new-custom-image-modal") 279 self.wait_until_visible("#new-custom-image-modal")
275 280
276 # recipes we expect to see in the new custom image modal 281 # recipes we expect to see in the new custom image modal
diff --git a/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py b/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
index c6226d60eb..675825bd40 100644
--- a/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
+++ b/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
@@ -7,6 +7,7 @@
7# SPDX-License-Identifier: GPL-2.0-only 7# SPDX-License-Identifier: GPL-2.0-only
8# 8#
9 9
10import os
10from django.urls import reverse 11from django.urls import reverse
11from django.utils import timezone 12from django.utils import timezone
12 13
@@ -20,7 +21,8 @@ class TestBuildDashboardPageArtifacts(SeleniumTestCase):
20 """ Tests for artifacts on the build dashboard /build/X """ 21 """ Tests for artifacts on the build dashboard /build/X """
21 22
22 def setUp(self): 23 def setUp(self):
23 bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/', 24 builldir = os.environ.get('BUILDDIR', './')
25 bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
24 branch='master', dirpath="") 26 branch='master', dirpath="")
25 release = Release.objects.create(name='release1', 27 release = Release.objects.create(name='release1',
26 bitbake_version=bbv) 28 bitbake_version=bbv)
@@ -197,12 +199,12 @@ class TestBuildDashboardPageArtifacts(SeleniumTestCase):
197 # check package count and size, link on target name 199 # check package count and size, link on target name
198 selector = '[data-value="target-package-count"]' 200 selector = '[data-value="target-package-count"]'
199 element = self.find(selector) 201 element = self.find(selector)
200 self.assertEquals(element.text, '1', 202 self.assertEqual(element.text, '1',
201 'package count should be shown for image builds') 203 'package count should be shown for image builds')
202 204
203 selector = '[data-value="target-package-size"]' 205 selector = '[data-value="target-package-size"]'
204 element = self.find(selector) 206 element = self.find(selector)
205 self.assertEquals(element.text, '1.0 KB', 207 self.assertEqual(element.text, '1.0 KB',
206 'package size should be shown for image builds') 208 'package size should be shown for image builds')
207 209
208 selector = '[data-link="target-packages"]' 210 selector = '[data-link="target-packages"]'
diff --git a/bitbake/lib/toaster/tests/browser/test_delete_project.py b/bitbake/lib/toaster/tests/browser/test_delete_project.py
new file mode 100644
index 0000000000..1941777ccc
--- /dev/null
+++ b/bitbake/lib/toaster/tests/browser/test_delete_project.py
@@ -0,0 +1,103 @@
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# BitBake Toaster UI tests implementation
4#
5# Copyright (C) 2023 Savoir-faire Linux Inc
6#
7# SPDX-License-Identifier: GPL-2.0-only
8
9import pytest
10from django.urls import reverse
11from selenium.webdriver.support.ui import Select
12from tests.browser.selenium_helpers import SeleniumTestCase
13from orm.models import BitbakeVersion, Project, Release
14from selenium.webdriver.common.by import By
15
16class TestDeleteProject(SeleniumTestCase):
17
18 def setUp(self):
19 bitbake, _ = BitbakeVersion.objects.get_or_create(
20 name="master",
21 giturl="git://master",
22 branch="master",
23 dirpath="master")
24
25 self.release, _ = Release.objects.get_or_create(
26 name="master",
27 description="Yocto Project master",
28 branch_name="master",
29 helptext="latest",
30 bitbake_version=bitbake)
31
32 Release.objects.get_or_create(
33 name="foo",
34 description="Yocto Project foo",
35 branch_name="foo",
36 helptext="latest",
37 bitbake_version=bitbake)
38
39 @pytest.mark.django_db
40 def test_delete_project(self):
41 """ Test delete a project
42 - Check delete modal is visible
43 - Check delete modal has right text
44 - Confirm delete
45 - Check project is deleted
46 """
47 project_name = "project_to_delete"
48 url = reverse('newproject')
49 self.get(url)
50 self.enter_text('#new-project-name', project_name)
51 select = Select(self.find('#projectversion'))
52 select.select_by_value(str(self.release.pk))
53 self.click("#create-project-button")
54 # We should get redirected to the new project's page with the
55 # notification at the top
56 element = self.wait_until_visible('#project-created-notification')
57 self.assertTrue(project_name in element.text,
58 "New project name not in new project notification")
59 self.assertTrue(Project.objects.filter(name=project_name).count(),
60 "New project not found in database")
61
62 # Delete project
63 delete_project_link = self.driver.find_element(
64 By.XPATH, '//a[@href="#delete-project-modal"]')
65 delete_project_link.click()
66
67 # Check delete modal is visible
68 self.wait_until_visible('#delete-project-modal')
69
70 # Check delete modal has right text
71 modal_header_text = self.find('#delete-project-modal .modal-header').text
72 self.assertTrue(
73 "Are you sure you want to delete this project?" in modal_header_text,
74 "Delete project modal header text is wrong")
75
76 modal_body_text = self.find('#delete-project-modal .modal-body').text
77 self.assertTrue(
78 "Cancel its builds currently in progress" in modal_body_text,
79 "Modal body doesn't contain: Cancel its builds currently in progress")
80 self.assertTrue(
81 "Remove its configuration information" in modal_body_text,
82 "Modal body doesn't contain: Remove its configuration information")
83 self.assertTrue(
84 "Remove its imported layers" in modal_body_text,
85 "Modal body doesn't contain: Remove its imported layers")
86 self.assertTrue(
87 "Remove its custom images" in modal_body_text,
88 "Modal body doesn't contain: Remove its custom images")
89 self.assertTrue(
90 "Remove all its build information" in modal_body_text,
91 "Modal body doesn't contain: Remove all its build information")
92
93 # Confirm delete
94 delete_btn = self.find('#delete-project-confirmed')
95 delete_btn.click()
96
97 # Check project is deleted
98 self.wait_until_visible('#change-notification')
99 delete_notification = self.find('#change-notification-msg')
100 self.assertTrue("You have deleted 1 project:" in delete_notification.text)
101 self.assertTrue(project_name in delete_notification.text)
102 self.assertFalse(Project.objects.filter(name=project_name).exists(),
103 "Project not deleted from database")
diff --git a/bitbake/lib/toaster/tests/browser/test_landing_page.py b/bitbake/lib/toaster/tests/browser/test_landing_page.py
index 8bb64b9f3e..210359d561 100644
--- a/bitbake/lib/toaster/tests/browser/test_landing_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_landing_page.py
@@ -10,8 +10,10 @@
10from django.urls import reverse 10from django.urls import reverse
11from django.utils import timezone 11from django.utils import timezone
12from tests.browser.selenium_helpers import SeleniumTestCase 12from tests.browser.selenium_helpers import SeleniumTestCase
13from selenium.webdriver.common.by import By
14
15from orm.models import Layer, Layer_Version, Project, Build
13 16
14from orm.models import Project, Build
15 17
16class TestLandingPage(SeleniumTestCase): 18class TestLandingPage(SeleniumTestCase):
17 """ Tests for redirects on the landing page """ 19 """ Tests for redirects on the landing page """
@@ -29,12 +31,147 @@ class TestLandingPage(SeleniumTestCase):
29 self.project.is_default = True 31 self.project.is_default = True
30 self.project.save() 32 self.project.save()
31 33
34 def test_icon_info_visible_and_clickable(self):
35 """ Test that the information icon is visible and clickable """
36 self.get(reverse('landing'))
37 self.wait_until_visible('#toaster-version-info-sign')
38 info_sign = self.find('#toaster-version-info-sign')
39
40 # check that the info sign is visible
41 self.assertTrue(info_sign.is_displayed())
42
43 # check that the info sign is clickable
44 # and info modal is appearing when clicking on the info sign
45 info_sign.click() # click on the info sign make attribute 'aria-describedby' visible
46 info_model_id = info_sign.get_attribute('aria-describedby')
47 self.wait_until_visible(f'#{info_model_id}')
48 info_modal = self.find(f'#{info_model_id}')
49 self.assertTrue(info_modal.is_displayed())
50 self.assertTrue("Toaster version information" in info_modal.text)
51
52 def test_documentation_link_displayed(self):
53 """ Test that the documentation link is displayed """
54 self.get(reverse('landing'))
55 self.wait_until_visible('#navbar-docs')
56 documentation_link = self.find('#navbar-docs > a')
57
58 # check that the documentation link is visible
59 self.assertTrue(documentation_link.is_displayed())
60
61 # check browser open new tab toaster manual when clicking on the documentation link
62 self.assertEqual(documentation_link.get_attribute('target'), '_blank')
63 self.assertEqual(
64 documentation_link.get_attribute('href'),
65 'http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual')
66 self.assertTrue("Documentation" in documentation_link.text)
67
68 def test_openembedded_jumbotron_link_visible_and_clickable(self):
69 """ Test OpenEmbedded link jumbotron is visible and clickable: """
70 self.get(reverse('landing'))
71 self.wait_until_visible('.jumbotron')
72 jumbotron = self.find('.jumbotron')
73
74 # check OpenEmbedded
75 openembedded = jumbotron.find_element(By.LINK_TEXT, 'OpenEmbedded')
76 self.assertTrue(openembedded.is_displayed())
77 openembedded.click()
78 self.assertTrue("openembedded.org" in self.driver.current_url)
79
80 def test_bitbake_jumbotron_link_visible_and_clickable(self):
81 """ Test BitBake link jumbotron is visible and clickable: """
82 self.get(reverse('landing'))
83 self.wait_until_visible('.jumbotron')
84 jumbotron = self.find('.jumbotron')
85
86 # check BitBake
87 bitbake = jumbotron.find_element(By.LINK_TEXT, 'BitBake')
88 self.assertTrue(bitbake.is_displayed())
89 bitbake.click()
90 self.assertTrue(
91 "docs.yoctoproject.org/bitbake.html" in self.driver.current_url)
92
93 def test_yoctoproject_jumbotron_link_visible_and_clickable(self):
94 """ Test Yocto Project link jumbotron is visible and clickable: """
95 self.get(reverse('landing'))
96 self.wait_until_visible('.jumbotron')
97 jumbotron = self.find('.jumbotron')
98
99 # check Yocto Project
100 yoctoproject = jumbotron.find_element(By.LINK_TEXT, 'Yocto Project')
101 self.assertTrue(yoctoproject.is_displayed())
102 yoctoproject.click()
103 self.assertTrue("yoctoproject.org" in self.driver.current_url)
104
105 def test_link_setup_using_toaster_visible_and_clickable(self):
106 """ Test big magenta button setting up and using toaster link in jumbotron
107 if visible and clickable
108 """
109 self.get(reverse('landing'))
110 self.wait_until_visible('.jumbotron')
111 jumbotron = self.find('.jumbotron')
112
113 # check Big magenta button
114 big_magenta_button = jumbotron.find_element(By.LINK_TEXT,
115 'Toaster is ready to capture your command line builds'
116 )
117 self.assertTrue(big_magenta_button.is_displayed())
118 big_magenta_button.click()
119 self.assertTrue(
120 "docs.yoctoproject.org/toaster-manual/setup-and-use.html#setting-up-and-using-toaster" in self.driver.current_url)
121
122 def test_link_create_new_project_in_jumbotron_visible_and_clickable(self):
123 """ Test big blue button create new project jumbotron if visible and clickable """
124 # Create a layer and a layer version to make visible the big blue button
125 layer = Layer.objects.create(name='bar')
126 Layer_Version.objects.create(layer=layer)
127
128 self.get(reverse('landing'))
129 self.wait_until_visible('.jumbotron')
130 jumbotron = self.find('.jumbotron')
131
132 # check Big Blue button
133 big_blue_button = jumbotron.find_element(By.LINK_TEXT,
134 'Create your first Toaster project to run manage builds'
135 )
136 self.assertTrue(big_blue_button.is_displayed())
137 big_blue_button.click()
138 self.assertTrue("toastergui/newproject/" in self.driver.current_url)
139
140 def test_toaster_manual_link_visible_and_clickable(self):
141 """ Test Read the Toaster manual link jumbotron is visible and clickable: """
142 self.get(reverse('landing'))
143 self.wait_until_visible('.jumbotron')
144 jumbotron = self.find('.jumbotron')
145
146 # check Read the Toaster manual
147 toaster_manual = jumbotron.find_element(
148 By.LINK_TEXT, 'Read the Toaster manual')
149 self.assertTrue(toaster_manual.is_displayed())
150 toaster_manual.click()
151 self.assertTrue(
152 "https://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual" in self.driver.current_url)
153
154 def test_contrib_to_toaster_link_visible_and_clickable(self):
155 """ Test Contribute to Toaster link jumbotron is visible and clickable: """
156 self.get(reverse('landing'))
157 self.wait_until_visible('.jumbotron')
158 jumbotron = self.find('.jumbotron')
159
160 # check Contribute to Toaster
161 contribute_to_toaster = jumbotron.find_element(
162 By.LINK_TEXT, 'Contribute to Toaster')
163 self.assertTrue(contribute_to_toaster.is_displayed())
164 contribute_to_toaster.click()
165 self.assertTrue(
166 "wiki.yoctoproject.org/wiki/contribute_to_toaster" in str(self.driver.current_url).lower())
167
32 def test_only_default_project(self): 168 def test_only_default_project(self):
33 """ 169 """
34 No projects except default 170 No projects except default
35 => should see the landing page 171 => should see the landing page
36 """ 172 """
37 self.get(reverse('landing')) 173 self.get(reverse('landing'))
174 self.wait_until_visible('.jumbotron')
38 self.assertTrue(self.LANDING_PAGE_TITLE in self.get_page_source()) 175 self.assertTrue(self.LANDING_PAGE_TITLE in self.get_page_source())
39 176
40 def test_default_project_has_build(self): 177 def test_default_project_has_build(self):
@@ -67,6 +204,7 @@ class TestLandingPage(SeleniumTestCase):
67 user_project.save() 204 user_project.save()
68 205
69 self.get(reverse('landing')) 206 self.get(reverse('landing'))
207 self.wait_until_visible('#projectstable')
70 208
71 elements = self.find_all('#projectstable') 209 elements = self.find_all('#projectstable')
72 self.assertEqual(len(elements), 1, 'should redirect to projects') 210 self.assertEqual(len(elements), 1, 'should redirect to projects')
@@ -87,10 +225,9 @@ class TestLandingPage(SeleniumTestCase):
87 225
88 self.get(reverse('landing')) 226 self.get(reverse('landing'))
89 227
228 self.wait_until_visible("#latest-builds")
90 elements = self.find_all('#allbuildstable') 229 elements = self.find_all('#allbuildstable')
91 self.assertEqual(len(elements), 1, 'should redirect to builds') 230 self.assertEqual(len(elements), 1, 'should redirect to builds')
92 content = self.get_page_source() 231 content = self.get_page_source()
93 self.assertTrue(self.PROJECT_NAME in content, 232 self.assertTrue(self.PROJECT_NAME in content,
94 'should show builds for project %s' % self.PROJECT_NAME) 233 'should show builds for project %s' % self.PROJECT_NAME)
95 self.assertFalse(self.CLI_BUILDS_PROJECT_NAME in content,
96 'should not show builds for cli project')
diff --git a/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
index 71bdd2aafd..6abfdef699 100644
--- a/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
@@ -8,6 +8,7 @@
8# 8#
9 9
10from django.urls import reverse 10from django.urls import reverse
11from selenium.common.exceptions import ElementClickInterceptedException, TimeoutException
11from tests.browser.selenium_helpers import SeleniumTestCase 12from tests.browser.selenium_helpers import SeleniumTestCase
12 13
13from orm.models import Layer, Layer_Version, Project, LayerSource, Release 14from orm.models import Layer, Layer_Version, Project, LayerSource, Release
@@ -63,11 +64,12 @@ class TestLayerDetailsPage(SeleniumTestCase):
63 args=(self.project.pk, 64 args=(self.project.pk,
64 self.imported_layer_version.pk)) 65 self.imported_layer_version.pk))
65 66
66 def test_edit_layerdetails(self): 67 def test_edit_layerdetails_page(self):
67 """ Edit all the editable fields for the layer refresh the page and 68 """ Edit all the editable fields for the layer refresh the page and
68 check that the new values exist""" 69 check that the new values exist"""
69 70
70 self.get(self.url) 71 self.get(self.url)
72 self.wait_until_visible("#add-remove-layer-btn")
71 73
72 self.click("#add-remove-layer-btn") 74 self.click("#add-remove-layer-btn")
73 self.click("#edit-layer-source") 75 self.click("#edit-layer-source")
@@ -97,13 +99,21 @@ class TestLayerDetailsPage(SeleniumTestCase):
97 "Expecting any of \"%s\"but got \"%s\"" % 99 "Expecting any of \"%s\"but got \"%s\"" %
98 (self.initial_values, value)) 100 (self.initial_values, value))
99 101
102 # Make sure the input visible beofre sending keys
103 self.wait_until_clickable("#layer-git input[type=text]")
100 inputs.send_keys("-edited") 104 inputs.send_keys("-edited")
101 105
102 # Save the new values 106 # Save the new values
103 for save_btn in self.find_all(".change-btn"): 107 for save_btn in self.find_all(".change-btn"):
104 save_btn.click() 108 save_btn.click()
105 109
106 self.click("#save-changes-for-switch") 110 self.wait_until_visible("#save-changes-for-switch")
111 # Ensure scrolled into view
112 self.driver.execute_script('window.scrollTo({behavior: "instant", top: 0, left: 0})')
113 btn_save_chg_for_switch = self.wait_until_clickable(
114 "#save-changes-for-switch")
115 btn_save_chg_for_switch.click()
116
107 self.wait_until_visible("#edit-layer-source") 117 self.wait_until_visible("#edit-layer-source")
108 118
109 # Refresh the page to see if the new values are returned 119 # Refresh the page to see if the new values are returned
@@ -132,7 +142,11 @@ class TestLayerDetailsPage(SeleniumTestCase):
132 new_dir = "/home/test/my-meta-dir" 142 new_dir = "/home/test/my-meta-dir"
133 dir_input.send_keys(new_dir) 143 dir_input.send_keys(new_dir)
134 144
135 self.click("#save-changes-for-switch") 145 self.wait_until_visible("#save-changes-for-switch")
146 btn_save_chg_for_switch = self.wait_until_clickable(
147 "#save-changes-for-switch")
148 btn_save_chg_for_switch.click()
149
136 self.wait_until_visible("#edit-layer-source") 150 self.wait_until_visible("#edit-layer-source")
137 151
138 # Refresh the page to see if the new values are returned 152 # Refresh the page to see if the new values are returned
@@ -142,6 +156,7 @@ class TestLayerDetailsPage(SeleniumTestCase):
142 "Expected %s in the dir value for layer directory" % 156 "Expected %s in the dir value for layer directory" %
143 new_dir) 157 new_dir)
144 158
159
145 def test_delete_layer(self): 160 def test_delete_layer(self):
146 """ Delete the layer """ 161 """ Delete the layer """
147 162
@@ -178,6 +193,7 @@ class TestLayerDetailsPage(SeleniumTestCase):
178 self.get(self.url) 193 self.get(self.url)
179 194
180 # Add the layer 195 # Add the layer
196 self.wait_until_clickable("#add-remove-layer-btn")
181 self.click("#add-remove-layer-btn") 197 self.click("#add-remove-layer-btn")
182 198
183 notification = self.wait_until_visible("#change-notification-msg") 199 notification = self.wait_until_visible("#change-notification-msg")
@@ -185,12 +201,17 @@ class TestLayerDetailsPage(SeleniumTestCase):
185 expected_text = "You have added 1 layer to your project: %s" % \ 201 expected_text = "You have added 1 layer to your project: %s" % \
186 self.imported_layer_version.layer.name 202 self.imported_layer_version.layer.name
187 203
188 self.assertTrue(expected_text in notification.text, 204 self.assertIn(expected_text, notification.text,
189 "Expected notification text %s not found was " 205 "Expected notification text %s not found was "
190 " \"%s\" instead" % 206 " \"%s\" instead" %
191 (expected_text, notification.text)) 207 (expected_text, notification.text))
192 208
209 hide_button = self.find('#hide-alert')
210 hide_button.click()
211 self.wait_until_not_visible('#change-notification')
212
193 # Remove the layer 213 # Remove the layer
214 self.wait_until_clickable("#add-remove-layer-btn")
194 self.click("#add-remove-layer-btn") 215 self.click("#add-remove-layer-btn")
195 216
196 notification = self.wait_until_visible("#change-notification-msg") 217 notification = self.wait_until_visible("#change-notification-msg")
@@ -198,7 +219,7 @@ class TestLayerDetailsPage(SeleniumTestCase):
198 expected_text = "You have removed 1 layer from your project: %s" % \ 219 expected_text = "You have removed 1 layer from your project: %s" % \
199 self.imported_layer_version.layer.name 220 self.imported_layer_version.layer.name
200 221
201 self.assertTrue(expected_text in notification.text, 222 self.assertIn(expected_text, notification.text,
202 "Expected notification text %s not found was " 223 "Expected notification text %s not found was "
203 " \"%s\" instead" % 224 " \"%s\" instead" %
204 (expected_text, notification.text)) 225 (expected_text, notification.text))
diff --git a/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py b/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
index 7844aaa395..d7a4c34532 100644
--- a/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
+++ b/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
@@ -6,7 +6,6 @@
6# 6#
7# Copyright (C) 2013-2016 Intel Corporation 7# Copyright (C) 2013-2016 Intel Corporation
8# 8#
9
10from django.urls import reverse 9from django.urls import reverse
11from django.utils import timezone 10from django.utils import timezone
12from tests.browser.selenium_helpers import SeleniumTestCase 11from tests.browser.selenium_helpers import SeleniumTestCase
@@ -14,6 +13,8 @@ from tests.browser.selenium_helpers_base import Wait
14from orm.models import Project, Build, Task, Recipe, Layer, Layer_Version 13from orm.models import Project, Build, Task, Recipe, Layer, Layer_Version
15from bldcontrol.models import BuildRequest 14from bldcontrol.models import BuildRequest
16 15
16from selenium.webdriver.common.by import By
17
17class TestMostRecentBuildsStates(SeleniumTestCase): 18class TestMostRecentBuildsStates(SeleniumTestCase):
18 """ Test states update correctly in most recent builds area """ 19 """ Test states update correctly in most recent builds area """
19 20
@@ -45,13 +46,14 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
45 # build queued; check shown as queued 46 # build queued; check shown as queued
46 selector = base_selector + '[data-build-state="Queued"]' 47 selector = base_selector + '[data-build-state="Queued"]'
47 element = self.wait_until_visible(selector) 48 element = self.wait_until_visible(selector)
48 self.assertRegexpMatches(element.get_attribute('innerHTML'), 49 self.assertRegex(element.get_attribute('innerHTML'),
49 'Build queued', 'build should show queued status') 50 'Build queued', 'build should show queued status')
50 51
51 # waiting for recipes to be parsed 52 # waiting for recipes to be parsed
52 build.outcome = Build.IN_PROGRESS 53 build.outcome = Build.IN_PROGRESS
53 build.recipes_to_parse = recipes_to_parse 54 build.recipes_to_parse = recipes_to_parse
54 build.recipes_parsed = 0 55 build.recipes_parsed = 0
56 build.save()
55 57
56 build_request.state = BuildRequest.REQ_INPROGRESS 58 build_request.state = BuildRequest.REQ_INPROGRESS
57 build_request.save() 59 build_request.save()
@@ -62,7 +64,7 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
62 element = self.wait_until_visible(selector) 64 element = self.wait_until_visible(selector)
63 65
64 bar_selector = '#recipes-parsed-percentage-bar-%s' % build.id 66 bar_selector = '#recipes-parsed-percentage-bar-%s' % build.id
65 bar_element = element.find_element_by_css_selector(bar_selector) 67 bar_element = element.find_element(By.CSS_SELECTOR, bar_selector)
66 self.assertEqual(bar_element.value_of_css_property('width'), '0px', 68 self.assertEqual(bar_element.value_of_css_property('width'), '0px',
67 'recipe parse progress should be at 0') 69 'recipe parse progress should be at 0')
68 70
@@ -73,7 +75,7 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
73 self.get(url) 75 self.get(url)
74 76
75 element = self.wait_until_visible(selector) 77 element = self.wait_until_visible(selector)
76 bar_element = element.find_element_by_css_selector(bar_selector) 78 bar_element = element.find_element(By.CSS_SELECTOR, bar_selector)
77 recipe_bar_updated = lambda driver: \ 79 recipe_bar_updated = lambda driver: \
78 bar_element.get_attribute('style') == 'width: 50%;' 80 bar_element.get_attribute('style') == 'width: 50%;'
79 msg = 'recipe parse progress bar should update to 50%' 81 msg = 'recipe parse progress bar should update to 50%'
@@ -94,11 +96,11 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
94 96
95 selector = base_selector + '[data-build-state="Starting"]' 97 selector = base_selector + '[data-build-state="Starting"]'
96 element = self.wait_until_visible(selector) 98 element = self.wait_until_visible(selector)
97 self.assertRegexpMatches(element.get_attribute('innerHTML'), 99 self.assertRegex(element.get_attribute('innerHTML'),
98 'Tasks starting', 'build should show "tasks starting" status') 100 'Tasks starting', 'build should show "tasks starting" status')
99 101
100 # first task finished; check tasks progress bar 102 # first task finished; check tasks progress bar
101 task1.order = 1 103 task1.outcome = Task.OUTCOME_SUCCESS
102 task1.save() 104 task1.save()
103 105
104 self.get(url) 106 self.get(url)
@@ -107,7 +109,7 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
107 element = self.wait_until_visible(selector) 109 element = self.wait_until_visible(selector)
108 110
109 bar_selector = '#build-pc-done-bar-%s' % build.id 111 bar_selector = '#build-pc-done-bar-%s' % build.id
110 bar_element = element.find_element_by_css_selector(bar_selector) 112 bar_element = element.find_element(By.CSS_SELECTOR, bar_selector)
111 113
112 task_bar_updated = lambda driver: \ 114 task_bar_updated = lambda driver: \
113 bar_element.get_attribute('style') == 'width: 50%;' 115 bar_element.get_attribute('style') == 'width: 50%;'
@@ -115,13 +117,13 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
115 element = Wait(self.driver).until(task_bar_updated, msg) 117 element = Wait(self.driver).until(task_bar_updated, msg)
116 118
117 # last task finished; check tasks progress bar updates 119 # last task finished; check tasks progress bar updates
118 task2.order = 2 120 task2.outcome = Task.OUTCOME_SUCCESS
119 task2.save() 121 task2.save()
120 122
121 self.get(url) 123 self.get(url)
122 124
123 element = self.wait_until_visible(selector) 125 element = self.wait_until_visible(selector)
124 bar_element = element.find_element_by_css_selector(bar_selector) 126 bar_element = element.find_element(By.CSS_SELECTOR, bar_selector)
125 task_bar_updated = lambda driver: \ 127 task_bar_updated = lambda driver: \
126 bar_element.get_attribute('style') == 'width: 100%;' 128 bar_element.get_attribute('style') == 'width: 100%;'
127 msg = 'tasks progress bar should update to 100%' 129 msg = 'tasks progress bar should update to 100%'
@@ -183,7 +185,7 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
183 selector = '[data-latest-build-result="%s"] ' \ 185 selector = '[data-latest-build-result="%s"] ' \
184 '[data-build-state="Cancelling"]' % build.id 186 '[data-build-state="Cancelling"]' % build.id
185 element = self.wait_until_visible(selector) 187 element = self.wait_until_visible(selector)
186 self.assertRegexpMatches(element.get_attribute('innerHTML'), 188 self.assertRegex(element.get_attribute('innerHTML'),
187 'Cancelling the build', 'build should show "cancelling" status') 189 'Cancelling the build', 'build should show "cancelling" status')
188 190
189 # check cancelled state 191 # check cancelled state
@@ -195,5 +197,5 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
195 selector = '[data-latest-build-result="%s"] ' \ 197 selector = '[data-latest-build-result="%s"] ' \
196 '[data-build-state="Cancelled"]' % build.id 198 '[data-build-state="Cancelled"]' % build.id
197 element = self.wait_until_visible(selector) 199 element = self.wait_until_visible(selector)
198 self.assertRegexpMatches(element.get_attribute('innerHTML'), 200 self.assertRegex(element.get_attribute('innerHTML'),
199 'Build cancelled', 'build should show "cancelled" status') 201 'Build cancelled', 'build should show "cancelled" status')
diff --git a/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py b/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
index 9906ae42a9..bf0304dbec 100644
--- a/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
@@ -6,6 +6,7 @@
6# 6#
7# SPDX-License-Identifier: GPL-2.0-only 7# SPDX-License-Identifier: GPL-2.0-only
8# 8#
9from bldcontrol.models import BuildEnvironment
9 10
10from django.urls import reverse 11from django.urls import reverse
11from tests.browser.selenium_helpers import SeleniumTestCase 12from tests.browser.selenium_helpers import SeleniumTestCase
@@ -18,6 +19,9 @@ class TestNewCustomImagePage(SeleniumTestCase):
18 CUSTOM_IMAGE_NAME = 'roopa-doopa' 19 CUSTOM_IMAGE_NAME = 'roopa-doopa'
19 20
20 def setUp(self): 21 def setUp(self):
22 BuildEnvironment.objects.get_or_create(
23 betype=BuildEnvironment.TYPE_LOCAL,
24 )
21 release = Release.objects.create( 25 release = Release.objects.create(
22 name='baz', 26 name='baz',
23 bitbake_version=BitbakeVersion.objects.create(name='v1') 27 bitbake_version=BitbakeVersion.objects.create(name='v1')
@@ -41,11 +45,16 @@ class TestNewCustomImagePage(SeleniumTestCase):
41 ) 45 )
42 46
43 # add a fake image recipe to the layer that can be customised 47 # add a fake image recipe to the layer that can be customised
48 builldir = os.environ.get('BUILDDIR', './')
44 self.recipe = Recipe.objects.create( 49 self.recipe = Recipe.objects.create(
45 name='core-image-minimal', 50 name='core-image-minimal',
46 layer_version=layer_version, 51 layer_version=layer_version,
52 file_path=f'{builldir}/core-image-minimal.bb',
47 is_image=True 53 is_image=True
48 ) 54 )
55 # create a tmp file for the recipe
56 with open(self.recipe.file_path, 'w') as f:
57 f.write('foo')
49 58
50 # another project with a custom image already in it 59 # another project with a custom image already in it
51 project2 = Project.objects.create(name='whoop', release=release) 60 project2 = Project.objects.create(name='whoop', release=release)
@@ -81,6 +90,7 @@ class TestNewCustomImagePage(SeleniumTestCase):
81 """ 90 """
82 url = reverse('newcustomimage', args=(self.project.id,)) 91 url = reverse('newcustomimage', args=(self.project.id,))
83 self.get(url) 92 self.get(url)
93 self.wait_until_visible('#global-nav')
84 94
85 self.click('button[data-recipe="%s"]' % self.recipe.id) 95 self.click('button[data-recipe="%s"]' % self.recipe.id)
86 96
@@ -128,7 +138,7 @@ class TestNewCustomImagePage(SeleniumTestCase):
128 """ 138 """
129 self._create_custom_image(self.recipe.name) 139 self._create_custom_image(self.recipe.name)
130 element = self.wait_until_visible('#invalid-name-help') 140 element = self.wait_until_visible('#invalid-name-help')
131 self.assertRegexpMatches(element.text.strip(), 141 self.assertRegex(element.text.strip(),
132 'image with this name already exists') 142 'image with this name already exists')
133 143
134 def test_new_duplicates_project_image(self): 144 def test_new_duplicates_project_image(self):
@@ -146,4 +156,4 @@ class TestNewCustomImagePage(SeleniumTestCase):
146 self._create_custom_image(custom_image_name) 156 self._create_custom_image(custom_image_name)
147 element = self.wait_until_visible('#invalid-name-help') 157 element = self.wait_until_visible('#invalid-name-help')
148 expected = 'An image with this name already exists in this project' 158 expected = 'An image with this name already exists in this project'
149 self.assertRegexpMatches(element.text.strip(), expected) 159 self.assertRegex(element.text.strip(), expected)
diff --git a/bitbake/lib/toaster/tests/browser/test_new_project_page.py b/bitbake/lib/toaster/tests/browser/test_new_project_page.py
index e20a1f686e..e50f236c32 100644
--- a/bitbake/lib/toaster/tests/browser/test_new_project_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_new_project_page.py
@@ -6,11 +6,11 @@
6# 6#
7# SPDX-License-Identifier: GPL-2.0-only 7# SPDX-License-Identifier: GPL-2.0-only
8# 8#
9
10from django.urls import reverse 9from django.urls import reverse
11from tests.browser.selenium_helpers import SeleniumTestCase 10from tests.browser.selenium_helpers import SeleniumTestCase
12from selenium.webdriver.support.ui import Select 11from selenium.webdriver.support.ui import Select
13from selenium.common.exceptions import InvalidElementStateException 12from selenium.common.exceptions import InvalidElementStateException
13from selenium.webdriver.common.by import By
14 14
15from orm.models import Project, Release, BitbakeVersion 15from orm.models import Project, Release, BitbakeVersion
16 16
@@ -47,7 +47,7 @@ class TestNewProjectPage(SeleniumTestCase):
47 47
48 url = reverse('newproject') 48 url = reverse('newproject')
49 self.get(url) 49 self.get(url)
50 50 self.wait_until_visible('#new-project-name')
51 self.enter_text('#new-project-name', project_name) 51 self.enter_text('#new-project-name', project_name)
52 52
53 select = Select(self.find('#projectversion')) 53 select = Select(self.find('#projectversion'))
@@ -57,7 +57,8 @@ class TestNewProjectPage(SeleniumTestCase):
57 57
58 # We should get redirected to the new project's page with the 58 # We should get redirected to the new project's page with the
59 # notification at the top 59 # notification at the top
60 element = self.wait_until_visible('#project-created-notification') 60 element = self.wait_until_visible(
61 '#project-created-notification')
61 62
62 self.assertTrue(project_name in element.text, 63 self.assertTrue(project_name in element.text,
63 "New project name not in new project notification") 64 "New project name not in new project notification")
@@ -78,15 +79,20 @@ class TestNewProjectPage(SeleniumTestCase):
78 79
79 url = reverse('newproject') 80 url = reverse('newproject')
80 self.get(url) 81 self.get(url)
82 self.wait_until_visible('#new-project-name')
81 83
82 self.enter_text('#new-project-name', project_name) 84 self.enter_text('#new-project-name', project_name)
83 85
84 select = Select(self.find('#projectversion')) 86 select = Select(self.find('#projectversion'))
85 select.select_by_value(str(self.release.pk)) 87 select.select_by_value(str(self.release.pk))
86 88
87 element = self.wait_until_visible('#hint-error-project-name') 89 radio = self.driver.find_element(By.ID, 'type-new')
90 radio.click()
91
92 self.wait_until_visible('#hint-error-project-name')
93 element = self.find('#hint-error-project-name')
88 94
89 self.assertTrue(("Project names must be unique" in element.text), 95 self.assertIn("Project names must be unique", element.text,
90 "Did not find unique project name error message") 96 "Did not find unique project name error message")
91 97
92 # Try and click it anyway, if it submits we'll have a new project in 98 # Try and click it anyway, if it submits we'll have a new project in
diff --git a/bitbake/lib/toaster/tests/browser/test_project_builds_page.py b/bitbake/lib/toaster/tests/browser/test_project_builds_page.py
index 51717e72d4..0dba33b9c8 100644
--- a/bitbake/lib/toaster/tests/browser/test_project_builds_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_project_builds_page.py
@@ -7,6 +7,7 @@
7# SPDX-License-Identifier: GPL-2.0-only 7# SPDX-License-Identifier: GPL-2.0-only
8# 8#
9 9
10import os
10import re 11import re
11 12
12from django.urls import reverse 13from django.urls import reverse
@@ -22,7 +23,8 @@ class TestProjectBuildsPage(SeleniumTestCase):
22 CLI_BUILDS_PROJECT_NAME = 'command line builds' 23 CLI_BUILDS_PROJECT_NAME = 'command line builds'
23 24
24 def setUp(self): 25 def setUp(self):
25 bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/', 26 builldir = os.environ.get('BUILDDIR', './')
27 bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
26 branch='master', dirpath='') 28 branch='master', dirpath='')
27 release = Release.objects.create(name='release1', 29 release = Release.objects.create(name='release1',
28 bitbake_version=bbv) 30 bitbake_version=bbv)
diff --git a/bitbake/lib/toaster/tests/browser/test_project_config_page.py b/bitbake/lib/toaster/tests/browser/test_project_config_page.py
index 944bcb2631..b9de541efa 100644
--- a/bitbake/lib/toaster/tests/browser/test_project_config_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_project_config_page.py
@@ -7,10 +7,12 @@
7# SPDX-License-Identifier: GPL-2.0-only 7# SPDX-License-Identifier: GPL-2.0-only
8# 8#
9 9
10import os
10from django.urls import reverse 11from django.urls import reverse
11from tests.browser.selenium_helpers import SeleniumTestCase 12from tests.browser.selenium_helpers import SeleniumTestCase
12 13
13from orm.models import BitbakeVersion, Release, Project, ProjectVariable 14from orm.models import BitbakeVersion, Release, Project, ProjectVariable
15from selenium.webdriver.common.by import By
14 16
15class TestProjectConfigsPage(SeleniumTestCase): 17class TestProjectConfigsPage(SeleniumTestCase):
16 """ Test data at /project/X/builds is displayed correctly """ 18 """ Test data at /project/X/builds is displayed correctly """
@@ -21,7 +23,8 @@ class TestProjectConfigsPage(SeleniumTestCase):
21 'any of these characters' 23 'any of these characters'
22 24
23 def setUp(self): 25 def setUp(self):
24 bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/', 26 builldir = os.environ.get('BUILDDIR', './')
27 bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
25 branch='master', dirpath='') 28 branch='master', dirpath='')
26 release = Release.objects.create(name='release1', 29 release = Release.objects.create(name='release1',
27 bitbake_version=bbv) 30 bitbake_version=bbv)
@@ -66,7 +69,7 @@ class TestProjectConfigsPage(SeleniumTestCase):
66 69
67 self.enter_text('#new-imagefs_types', imagefs_type) 70 self.enter_text('#new-imagefs_types', imagefs_type)
68 71
69 checkboxes = self.driver.find_elements_by_xpath("//input[@class='fs-checkbox-fstypes']") 72 checkboxes = self.driver.find_elements(By.XPATH, "//input[@class='fs-checkbox-fstypes']")
70 73
71 for checkbox in checkboxes: 74 for checkbox in checkboxes:
72 if checkbox.get_attribute("value") == "btrfs": 75 if checkbox.get_attribute("value") == "btrfs":
@@ -95,7 +98,7 @@ class TestProjectConfigsPage(SeleniumTestCase):
95 for checkbox in checkboxes: 98 for checkbox in checkboxes:
96 if checkbox.get_attribute("value") == "cpio": 99 if checkbox.get_attribute("value") == "cpio":
97 checkbox.click() 100 checkbox.click()
98 element = self.driver.find_element_by_id('new-imagefs_types') 101 element = self.driver.find_element(By.ID, 'new-imagefs_types')
99 102
100 self.wait_until_visible('#new-imagefs_types') 103 self.wait_until_visible('#new-imagefs_types')
101 104
@@ -129,7 +132,7 @@ class TestProjectConfigsPage(SeleniumTestCase):
129 self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg) 132 self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg)
130 133
131 # downloads dir path has a space 134 # downloads dir path has a space
132 self.driver.find_element_by_id('new-dl_dir').clear() 135 self.driver.find_element(By.ID, 'new-dl_dir').clear()
133 self.enter_text('#new-dl_dir', '/foo/bar a') 136 self.enter_text('#new-dl_dir', '/foo/bar a')
134 137
135 element = self.wait_until_visible('#hintError-dl_dir') 138 element = self.wait_until_visible('#hintError-dl_dir')
@@ -137,7 +140,7 @@ class TestProjectConfigsPage(SeleniumTestCase):
137 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) 140 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
138 141
139 # downloads dir path starts with ${...} but has a space 142 # downloads dir path starts with ${...} but has a space
140 self.driver.find_element_by_id('new-dl_dir').clear() 143 self.driver.find_element(By.ID,'new-dl_dir').clear()
141 self.enter_text('#new-dl_dir', '${TOPDIR}/down foo') 144 self.enter_text('#new-dl_dir', '${TOPDIR}/down foo')
142 145
143 element = self.wait_until_visible('#hintError-dl_dir') 146 element = self.wait_until_visible('#hintError-dl_dir')
@@ -145,18 +148,18 @@ class TestProjectConfigsPage(SeleniumTestCase):
145 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) 148 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
146 149
147 # downloads dir path starts with / 150 # downloads dir path starts with /
148 self.driver.find_element_by_id('new-dl_dir').clear() 151 self.driver.find_element(By.ID,'new-dl_dir').clear()
149 self.enter_text('#new-dl_dir', '/bar/foo') 152 self.enter_text('#new-dl_dir', '/bar/foo')
150 153
151 hidden_element = self.driver.find_element_by_id('hintError-dl_dir') 154 hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir')
152 self.assertEqual(hidden_element.is_displayed(), False, 155 self.assertEqual(hidden_element.is_displayed(), False,
153 'downloads directory path valid but treated as invalid') 156 'downloads directory path valid but treated as invalid')
154 157
155 # downloads dir path starts with ${...} 158 # downloads dir path starts with ${...}
156 self.driver.find_element_by_id('new-dl_dir').clear() 159 self.driver.find_element(By.ID,'new-dl_dir').clear()
157 self.enter_text('#new-dl_dir', '${TOPDIR}/down') 160 self.enter_text('#new-dl_dir', '${TOPDIR}/down')
158 161
159 hidden_element = self.driver.find_element_by_id('hintError-dl_dir') 162 hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir')
160 self.assertEqual(hidden_element.is_displayed(), False, 163 self.assertEqual(hidden_element.is_displayed(), False,
161 'downloads directory path valid but treated as invalid') 164 'downloads directory path valid but treated as invalid')
162 165
@@ -184,7 +187,7 @@ class TestProjectConfigsPage(SeleniumTestCase):
184 self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg) 187 self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg)
185 188
186 # path has a space 189 # path has a space
187 self.driver.find_element_by_id('new-sstate_dir').clear() 190 self.driver.find_element(By.ID, 'new-sstate_dir').clear()
188 self.enter_text('#new-sstate_dir', '/foo/bar a') 191 self.enter_text('#new-sstate_dir', '/foo/bar a')
189 192
190 element = self.wait_until_visible('#hintError-sstate_dir') 193 element = self.wait_until_visible('#hintError-sstate_dir')
@@ -192,7 +195,7 @@ class TestProjectConfigsPage(SeleniumTestCase):
192 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) 195 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
193 196
194 # path starts with ${...} but has a space 197 # path starts with ${...} but has a space
195 self.driver.find_element_by_id('new-sstate_dir').clear() 198 self.driver.find_element(By.ID,'new-sstate_dir').clear()
196 self.enter_text('#new-sstate_dir', '${TOPDIR}/down foo') 199 self.enter_text('#new-sstate_dir', '${TOPDIR}/down foo')
197 200
198 element = self.wait_until_visible('#hintError-sstate_dir') 201 element = self.wait_until_visible('#hintError-sstate_dir')
@@ -200,18 +203,18 @@ class TestProjectConfigsPage(SeleniumTestCase):
200 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) 203 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
201 204
202 # path starts with / 205 # path starts with /
203 self.driver.find_element_by_id('new-sstate_dir').clear() 206 self.driver.find_element(By.ID,'new-sstate_dir').clear()
204 self.enter_text('#new-sstate_dir', '/bar/foo') 207 self.enter_text('#new-sstate_dir', '/bar/foo')
205 208
206 hidden_element = self.driver.find_element_by_id('hintError-sstate_dir') 209 hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir')
207 self.assertEqual(hidden_element.is_displayed(), False, 210 self.assertEqual(hidden_element.is_displayed(), False,
208 'sstate directory path valid but treated as invalid') 211 'sstate directory path valid but treated as invalid')
209 212
210 # paths starts with ${...} 213 # paths starts with ${...}
211 self.driver.find_element_by_id('new-sstate_dir').clear() 214 self.driver.find_element(By.ID, 'new-sstate_dir').clear()
212 self.enter_text('#new-sstate_dir', '${TOPDIR}/down') 215 self.enter_text('#new-sstate_dir', '${TOPDIR}/down')
213 216
214 hidden_element = self.driver.find_element_by_id('hintError-sstate_dir') 217 hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir')
215 self.assertEqual(hidden_element.is_displayed(), False, 218 self.assertEqual(hidden_element.is_displayed(), False,
216 'sstate directory path valid but treated as invalid') 219 'sstate directory path valid but treated as invalid')
217 220
diff --git a/bitbake/lib/toaster/tests/browser/test_sample.py b/bitbake/lib/toaster/tests/browser/test_sample.py
index b0067c21cd..f04f1d9a16 100644
--- a/bitbake/lib/toaster/tests/browser/test_sample.py
+++ b/bitbake/lib/toaster/tests/browser/test_sample.py
@@ -27,3 +27,13 @@ class TestSample(SeleniumTestCase):
27 self.get(url) 27 self.get(url)
28 brand_link = self.find('.toaster-navbar-brand a.brand') 28 brand_link = self.find('.toaster-navbar-brand a.brand')
29 self.assertEqual(brand_link.text.strip(), 'Toaster') 29 self.assertEqual(brand_link.text.strip(), 'Toaster')
30
31 def test_no_builds_message(self):
32 """ Test that a message is shown when there are no builds """
33 url = reverse('all-builds')
34 self.get(url)
35 self.wait_until_visible('#empty-state-allbuildstable') # wait for the empty state div to appear
36 div_msg = self.find('#empty-state-allbuildstable .alert-info')
37
38 msg = 'Sorry - no data found'
39 self.assertEqual(div_msg.text, msg)
diff --git a/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py b/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py
index e82d5ec654..691aca1ef0 100644
--- a/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py
+++ b/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py
@@ -8,11 +8,13 @@
8# 8#
9 9
10from datetime import datetime 10from datetime import datetime
11import os
11 12
12from django.urls import reverse 13from django.urls import reverse
13from django.utils import timezone 14from django.utils import timezone
14from tests.browser.selenium_helpers import SeleniumTestCase 15from tests.browser.selenium_helpers import SeleniumTestCase
15from orm.models import BitbakeVersion, Release, Project, Build 16from orm.models import BitbakeVersion, Release, Project, Build
17from selenium.webdriver.common.by import By
16 18
17class TestToasterTableUI(SeleniumTestCase): 19class TestToasterTableUI(SeleniumTestCase):
18 """ 20 """
@@ -33,7 +35,7 @@ class TestToasterTableUI(SeleniumTestCase):
33 table: WebElement for a ToasterTable 35 table: WebElement for a ToasterTable
34 """ 36 """
35 selector = 'thead a.sorted' 37 selector = 'thead a.sorted'
36 heading = table.find_element_by_css_selector(selector) 38 heading = table.find_element(By.CSS_SELECTOR, selector)
37 return heading.get_attribute('innerHTML').strip() 39 return heading.get_attribute('innerHTML').strip()
38 40
39 def _get_datetime_from_cell(self, row, selector): 41 def _get_datetime_from_cell(self, row, selector):
@@ -45,7 +47,7 @@ class TestToasterTableUI(SeleniumTestCase):
45 selector: CSS selector to use to find the cell containing the date time 47 selector: CSS selector to use to find the cell containing the date time
46 string 48 string
47 """ 49 """
48 cell = row.find_element_by_css_selector(selector) 50 cell = row.find_element(By.CSS_SELECTOR, selector)
49 cell_text = cell.get_attribute('innerHTML').strip() 51 cell_text = cell.get_attribute('innerHTML').strip()
50 return datetime.strptime(cell_text, '%d/%m/%y %H:%M') 52 return datetime.strptime(cell_text, '%d/%m/%y %H:%M')
51 53
@@ -58,7 +60,8 @@ class TestToasterTableUI(SeleniumTestCase):
58 later = now + timezone.timedelta(hours=1) 60 later = now + timezone.timedelta(hours=1)
59 even_later = later + timezone.timedelta(hours=1) 61 even_later = later + timezone.timedelta(hours=1)
60 62
61 bbv = BitbakeVersion.objects.create(name='test bbv', giturl='/tmp/', 63 builldir = os.environ.get('BUILDDIR', './')
64 bbv = BitbakeVersion.objects.create(name='test bbv', giturl=f'{builldir}/',
62 branch='master', dirpath='') 65 branch='master', dirpath='')
63 release = Release.objects.create(name='test release', 66 release = Release.objects.create(name='test release',
64 branch_name='master', 67 branch_name='master',
@@ -105,7 +108,7 @@ class TestToasterTableUI(SeleniumTestCase):
105 self.click('#checkbox-started_on') 108 self.click('#checkbox-started_on')
106 109
107 # sort by started_on column 110 # sort by started_on column
108 links = table.find_elements_by_css_selector('th.started_on a') 111 links = table.find_elements(By.CSS_SELECTOR, 'th.started_on a')
109 for link in links: 112 for link in links:
110 if link.get_attribute('innerHTML').strip() == 'Started on': 113 if link.get_attribute('innerHTML').strip() == 'Started on':
111 link.click() 114 link.click()