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.py76
-rw-r--r--bitbake/lib/toaster/tests/browser/test_all_builds_page.py315
-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.py131
-rw-r--r--bitbake/lib/toaster/tests/browser/test_layerdetails_page.py39
-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, 854 insertions, 107 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..393be75496 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
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':
@@ -66,7 +93,9 @@ class Wait(WebDriverWait):
66 _TIMEOUT = 10 93 _TIMEOUT = 10
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=''):
@@ -138,6 +167,8 @@ class SeleniumTestCaseBase(unittest.TestCase):
138 """ Clean up webdriver driver """ 167 """ Clean up webdriver driver """
139 168
140 cls.driver.quit() 169 cls.driver.quit()
170 # Allow driver resources to be properly freed before proceeding with further tests
171 time.sleep(5)
141 super(SeleniumTestCaseBase, cls).tearDownClass() 172 super(SeleniumTestCaseBase, cls).tearDownClass()
142 173
143 def get(self, url): 174 def get(self, url):
@@ -151,13 +182,20 @@ class SeleniumTestCaseBase(unittest.TestCase):
151 abs_url = '%s%s' % (self.live_server_url, url) 182 abs_url = '%s%s' % (self.live_server_url, url)
152 self.driver.get(abs_url) 183 self.driver.get(abs_url)
153 184
185 try: # Ensure page is loaded before proceeding
186 self.wait_until_visible("#global-nav", poll=3)
187 except NoSuchElementException:
188 self.driver.implicitly_wait(3)
189 except TimeoutException:
190 self.driver.implicitly_wait(3)
191
154 def find(self, selector): 192 def find(self, selector):
155 """ Find single element by CSS selector """ 193 """ Find single element by CSS selector """
156 return self.driver.find_element_by_css_selector(selector) 194 return self.driver.find_element(By.CSS_SELECTOR, selector)
157 195
158 def find_all(self, selector): 196 def find_all(self, selector):
159 """ Find all elements matching CSS selector """ 197 """ Find all elements matching CSS selector """
160 return self.driver.find_elements_by_css_selector(selector) 198 return self.driver.find_elements(By.CSS_SELECTOR, selector)
161 199
162 def element_exists(self, selector): 200 def element_exists(self, selector):
163 """ 201 """
@@ -170,18 +208,34 @@ class SeleniumTestCaseBase(unittest.TestCase):
170 """ Return the element which currently has focus on the page """ 208 """ Return the element which currently has focus on the page """
171 return self.driver.switch_to.active_element 209 return self.driver.switch_to.active_element
172 210
173 def wait_until_present(self, selector): 211 def wait_until_present(self, selector, poll=0.5):
174 """ Wait until element matching CSS selector is on the page """ 212 """ Wait until element matching CSS selector is on the page """
175 is_present = lambda driver: self.find(selector) 213 is_present = lambda driver: self.find(selector)
176 msg = 'An element matching "%s" should be on the page' % selector 214 msg = 'An element matching "%s" should be on the page' % selector
177 element = Wait(self.driver).until(is_present, msg) 215 element = Wait(self.driver, poll=poll).until(is_present, msg)
216 if poll > 2:
217 time.sleep(poll) # element need more delay to be present
178 return element 218 return element
179 219
180 def wait_until_visible(self, selector): 220 def wait_until_visible(self, selector, poll=1):
181 """ Wait until element matching CSS selector is visible on the page """ 221 """ Wait until element matching CSS selector is visible on the page """
182 is_visible = lambda driver: self.find(selector).is_displayed() 222 is_visible = lambda driver: self.find(selector).is_displayed()
183 msg = 'An element matching "%s" should be visible' % selector 223 msg = 'An element matching "%s" should be visible' % selector
184 Wait(self.driver).until(is_visible, msg) 224 Wait(self.driver, poll=poll).until(is_visible, msg)
225 time.sleep(poll) # wait for visibility to settle
226 return self.find(selector)
227
228 def wait_until_clickable(self, selector, poll=1):
229 """ Wait until element matching CSS selector is visible on the page """
230 WebDriverWait(
231 self.driver,
232 Wait._TIMEOUT,
233 poll_frequency=poll
234 ).until(
235 EC.element_to_be_clickable((By.ID, selector.removeprefix('#')
236 )
237 )
238 )
185 return self.find(selector) 239 return self.find(selector)
186 240
187 def wait_until_focused(self, selector): 241 def wait_until_focused(self, selector):
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..b9356a0344 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,25 @@ 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')
141 selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id 203 selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id
142 run_again_button = self.find_all(selector) 204 run_again_button = self.find_all(selector)
143 self.assertEqual(len(run_again_button), 1, 205 self.assertEqual(len(run_again_button), 1,
144 'should see a rebuild button for non-cli builds') 206 'should see a rebuild button for non-cli builds')
145 207
208 # shouldn't see a rebuild button for command-line builds
209 selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % default_build.id
210 run_again_button = self.find_all(selector)
211 self.assertEqual(len(run_again_button), 0,
212 'should not see a rebuild button for cli builds')
213
146 def test_tooltips_on_project_name(self): 214 def test_tooltips_on_project_name(self):
147 """ 215 """
148 Test tooltips shown next to project name in the main table 216 Test tooltips shown next to project name in the main table
@@ -156,6 +224,7 @@ class TestAllBuildsPage(SeleniumTestCase):
156 224
157 url = reverse('all-builds') 225 url = reverse('all-builds')
158 self.get(url) 226 self.get(url)
227 self.wait_until_visible('#allbuildstable', poll=3)
159 228
160 # get the project name cells from the table 229 # get the project name cells from the table
161 cells = self.find_all('#allbuildstable td[class="project"]') 230 cells = self.find_all('#allbuildstable td[class="project"]')
@@ -164,7 +233,7 @@ class TestAllBuildsPage(SeleniumTestCase):
164 233
165 for cell in cells: 234 for cell in cells:
166 content = cell.get_attribute('innerHTML') 235 content = cell.get_attribute('innerHTML')
167 help_icons = cell.find_elements_by_css_selector(selector) 236 help_icons = cell.find_elements(By.CSS_SELECTOR, selector)
168 237
169 if re.search(self.PROJECT_NAME, content): 238 if re.search(self.PROJECT_NAME, content):
170 # no help icon next to non-cli project name 239 # no help icon next to non-cli project name
@@ -184,38 +253,224 @@ class TestAllBuildsPage(SeleniumTestCase):
184 recent builds area; failed builds should not have links on the time column, 253 recent builds area; failed builds should not have links on the time column,
185 or in the recent builds area 254 or in the recent builds area
186 """ 255 """
187 build1 = Build.objects.create(**self.project1_build_success) 256 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 257
196 url = reverse('all-builds') 258 url = reverse('all-builds')
197 self.get(url) 259 self.get(url)
260 self.wait_until_visible('#allbuildstable', poll=3)
198 261
199 # test recent builds area for successful build 262 # test recent builds area for successful build
200 element = self._get_build_time_element(build1) 263 element = self._get_build_time_element(build1)
201 links = element.find_elements_by_css_selector('a') 264 links = element.find_elements(By.CSS_SELECTOR, 'a')
202 msg = 'should be a link on the build time for a successful recent build' 265 msg = 'should be a link on the build time for a successful recent build'
203 self.assertEquals(len(links), 1, msg) 266 self.assertEqual(len(links), 1, msg)
204 267
205 # test recent builds area for failed build 268 # test recent builds area for failed build
206 element = self._get_build_time_element(build2) 269 element = self._get_build_time_element(build2)
207 links = element.find_elements_by_css_selector('a') 270 links = element.find_elements(By.CSS_SELECTOR, 'a')
208 msg = 'should not be a link on the build time for a failed recent build' 271 msg = 'should not be a link on the build time for a failed recent build'
209 self.assertEquals(len(links), 0, msg) 272 self.assertEqual(len(links), 0, msg)
210 273
211 # test the time column for successful build 274 # test the time column for successful build
212 build1_row = self._get_row_for_build(build1) 275 build1_row = self._get_row_for_build(build1)
213 links = build1_row.find_elements_by_css_selector('td.time a') 276 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' 277 msg = 'should be a link on the build time for a successful build'
215 self.assertEquals(len(links), 1, msg) 278 self.assertEqual(len(links), 1, msg)
216 279
217 # test the time column for failed build 280 # test the time column for failed build
218 build2_row = self._get_row_for_build(build2) 281 build2_row = self._get_row_for_build(build2)
219 links = build2_row.find_elements_by_css_selector('td.time a') 282 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' 283 msg = 'should not be a link on the build time for a failed build'
221 self.assertEquals(len(links), 0, msg) 284 self.assertEqual(len(links), 0, msg)
285
286 def test_builds_table_search_box(self):
287 """ Test the search box in the builds table on the all builds page """
288 self._get_create_builds()
289
290 url = reverse('all-builds')
291 self.get(url)
292
293 # Check search box is present and works
294 self.wait_until_visible('#allbuildstable tbody tr')
295 search_box = self.find('#search-input-allbuildstable')
296 self.assertTrue(search_box.is_displayed())
297
298 # Check that we can search for a build by recipe name
299 search_box.send_keys('foo')
300 search_btn = self.find('#search-submit-allbuildstable')
301 search_btn.click()
302 self.wait_until_visible('#allbuildstable tbody tr')
303 rows = self.find_all('#allbuildstable tbody tr')
304 self.assertTrue(len(rows) >= 1)
305
306 def test_filtering_on_failure_tasks_column(self):
307 """ Test the filtering on failure tasks column in the builds table on the all builds page """
308 def _check_if_filter_failed_tasks_column_is_visible():
309 # check if failed tasks filter column is visible, if not click on it
310 # Check edit column
311 edit_column = self.find('#edit-columns-button')
312 self.assertTrue(edit_column.is_displayed())
313 edit_column.click()
314 # Check dropdown is visible
315 self.wait_until_visible('ul.dropdown-menu.editcol')
316 filter_fails_task_checkbox = self.find('#checkbox-failed_tasks')
317 if not filter_fails_task_checkbox.is_selected():
318 filter_fails_task_checkbox.click()
319 edit_column.click()
320
321 self._get_create_builds(success=10, failure=10)
322
323 url = reverse('all-builds')
324 self.get(url)
325
326 # Check filtering on failure tasks column
327 self.wait_until_visible('#allbuildstable tbody tr')
328 _check_if_filter_failed_tasks_column_is_visible()
329 failed_tasks_filter = self.find('#failed_tasks_filter')
330 failed_tasks_filter.click()
331 # Check popup is visible
332 self.wait_until_visible('#filter-modal-allbuildstable')
333 self.assertTrue(
334 self.find('#filter-modal-allbuildstable').is_displayed())
335 # Check that we can filter by failure tasks
336 build_without_failure_tasks = self.find(
337 '#failed_tasks_filter\\:without_failed_tasks')
338 build_without_failure_tasks.click()
339 # click on apply button
340 self.find('#filter-modal-allbuildstable .btn-primary').click()
341 self.wait_until_visible('#allbuildstable tbody tr')
342 # Check if filter is applied, by checking if failed_tasks_filter has btn-primary class
343 self.assertTrue(self.find('#failed_tasks_filter').get_attribute(
344 'class').find('btn-primary') != -1)
345
346 def test_filtering_on_completedOn_column(self):
347 """ Test the filtering on completed_on column in the builds table on the all builds page """
348 self._get_create_builds(success=10, failure=10)
349
350 url = reverse('all-builds')
351 self.get(url)
352
353 # Check filtering on failure tasks column
354 self.wait_until_visible('#allbuildstable tbody tr')
355 completed_on_filter = self.find('#completed_on_filter')
356 completed_on_filter.click()
357 # Check popup is visible
358 self.wait_until_visible('#filter-modal-allbuildstable')
359 self.assertTrue(
360 self.find('#filter-modal-allbuildstable').is_displayed())
361 # Check that we can filter by failure tasks
362 build_without_failure_tasks = self.find(
363 '#completed_on_filter\\:date_range')
364 build_without_failure_tasks.click()
365 # click on apply button
366 self.find('#filter-modal-allbuildstable .btn-primary').click()
367 self.wait_until_visible('#allbuildstable tbody tr')
368 # Check if filter is applied, by checking if completed_on_filter has btn-primary class
369 self.assertTrue(self.find('#completed_on_filter').get_attribute(
370 'class').find('btn-primary') != -1)
371
372 # Filter by date range
373 self.find('#completed_on_filter').click()
374 self.wait_until_visible('#filter-modal-allbuildstable')
375 date_ranges = self.driver.find_elements(
376 By.XPATH, '//input[@class="form-control hasDatepicker"]')
377 today = timezone.now()
378 yestersday = today - timezone.timedelta(days=1)
379 date_ranges[0].send_keys(yestersday.strftime('%Y-%m-%d'))
380 date_ranges[1].send_keys(today.strftime('%Y-%m-%d'))
381 self.find('#filter-modal-allbuildstable .btn-primary').click()
382 self.wait_until_visible('#allbuildstable tbody tr')
383 self.assertTrue(self.find('#completed_on_filter').get_attribute(
384 'class').find('btn-primary') != -1)
385 # Check if filter is applied, number of builds displayed should be 6
386 self.assertTrue(len(self.find_all('#allbuildstable tbody tr')) >= 4)
387
388 def test_builds_table_editColumn(self):
389 """ Test the edit column feature in the builds table on the all builds page """
390 self._get_create_builds(success=10, failure=10)
391
392 def test_edit_column(check_box_id):
393 # Check that we can hide/show table column
394 check_box = self.find(f'#{check_box_id}')
395 th_class = str(check_box_id).replace('checkbox-', '')
396 if check_box.is_selected():
397 # check if column is visible in table
398 self.assertTrue(
399 self.find(
400 f'#allbuildstable thead th.{th_class}'
401 ).is_displayed(),
402 f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
403 )
404 check_box.click()
405 # check if column is hidden in table
406 self.assertFalse(
407 self.find(
408 f'#allbuildstable thead th.{th_class}'
409 ).is_displayed(),
410 f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
411 )
412 else:
413 # check if column is hidden in table
414 self.assertFalse(
415 self.find(
416 f'#allbuildstable thead th.{th_class}'
417 ).is_displayed(),
418 f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
419 )
420 check_box.click()
421 # check if column is visible in table
422 self.assertTrue(
423 self.find(
424 f'#allbuildstable thead th.{th_class}'
425 ).is_displayed(),
426 f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
427 )
428 url = reverse('all-builds')
429 self.get(url)
430 self.wait_until_visible('#allbuildstable tbody tr')
431
432 # Check edit column
433 edit_column = self.find('#edit-columns-button')
434 self.assertTrue(edit_column.is_displayed())
435 edit_column.click()
436 # Check dropdown is visible
437 self.wait_until_visible('ul.dropdown-menu.editcol')
438
439 # Check that we can hide the edit column
440 test_edit_column('checkbox-errors_no')
441 test_edit_column('checkbox-failed_tasks')
442 test_edit_column('checkbox-image_files')
443 test_edit_column('checkbox-project')
444 test_edit_column('checkbox-started_on')
445 test_edit_column('checkbox-time')
446 test_edit_column('checkbox-warnings_no')
447
448 def test_builds_table_show_rows(self):
449 """ Test the show rows feature in the builds table on the all builds page """
450 self._get_create_builds(success=100, failure=100)
451
452 def test_show_rows(row_to_show, show_row_link):
453 # Check that we can show rows == row_to_show
454 show_row_link.select_by_value(str(row_to_show))
455 self.wait_until_visible('#allbuildstable tbody tr', poll=3)
456 # check at least some rows are visible
457 self.assertTrue(
458 len(self.find_all('#allbuildstable tbody tr')) > 0
459 )
460
461 url = reverse('all-builds')
462 self.get(url)
463 self.wait_until_visible('#allbuildstable tbody tr')
464
465 show_rows = self.driver.find_elements(
466 By.XPATH,
467 '//select[@class="form-control pagesize-allbuildstable"]'
468 )
469 # Check show rows
470 for show_row_link in show_rows:
471 show_row_link = Select(show_row_link)
472 test_show_rows(10, show_row_link)
473 test_show_rows(25, show_row_link)
474 test_show_rows(50, show_row_link)
475 test_show_rows(100, show_row_link)
476 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..9ed1901cc9 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', poll=3)
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', poll=3)
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', poll=3)
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', poll=3)
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', poll=3)
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', poll=3)
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..d838ce363a 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', poll=3)
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..8fe5fea467 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,6 +31,130 @@ 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 info_sign = self.find('#toaster-version-info-sign')
38
39 # check that the info sign is visible
40 self.assertTrue(info_sign.is_displayed())
41
42 # check that the info sign is clickable
43 # and info modal is appearing when clicking on the info sign
44 info_sign.click() # click on the info sign make attribute 'aria-describedby' visible
45 info_model_id = info_sign.get_attribute('aria-describedby')
46 info_modal = self.find(f'#{info_model_id}')
47 self.assertTrue(info_modal.is_displayed())
48 self.assertTrue("Toaster version information" in info_modal.text)
49
50 def test_documentation_link_displayed(self):
51 """ Test that the documentation link is displayed """
52 self.get(reverse('landing'))
53 documentation_link = self.find('#navbar-docs > a')
54
55 # check that the documentation link is visible
56 self.assertTrue(documentation_link.is_displayed())
57
58 # check browser open new tab toaster manual when clicking on the documentation link
59 self.assertEqual(documentation_link.get_attribute('target'), '_blank')
60 self.assertEqual(
61 documentation_link.get_attribute('href'),
62 'http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual')
63 self.assertTrue("Documentation" in documentation_link.text)
64
65 def test_openembedded_jumbotron_link_visible_and_clickable(self):
66 """ Test OpenEmbedded link jumbotron is visible and clickable: """
67 self.get(reverse('landing'))
68 jumbotron = self.find('.jumbotron')
69
70 # check OpenEmbedded
71 openembedded = jumbotron.find_element(By.LINK_TEXT, 'OpenEmbedded')
72 self.assertTrue(openembedded.is_displayed())
73 openembedded.click()
74 self.assertTrue("openembedded.org" in self.driver.current_url)
75
76 def test_bitbake_jumbotron_link_visible_and_clickable(self):
77 """ Test BitBake link jumbotron is visible and clickable: """
78 self.get(reverse('landing'))
79 jumbotron = self.find('.jumbotron')
80
81 # check BitBake
82 bitbake = jumbotron.find_element(By.LINK_TEXT, 'BitBake')
83 self.assertTrue(bitbake.is_displayed())
84 bitbake.click()
85 self.assertTrue(
86 "docs.yoctoproject.org/bitbake.html" in self.driver.current_url)
87
88 def test_yoctoproject_jumbotron_link_visible_and_clickable(self):
89 """ Test Yocto Project link jumbotron is visible and clickable: """
90 self.get(reverse('landing'))
91 jumbotron = self.find('.jumbotron')
92
93 # check Yocto Project
94 yoctoproject = jumbotron.find_element(By.LINK_TEXT, 'Yocto Project')
95 self.assertTrue(yoctoproject.is_displayed())
96 yoctoproject.click()
97 self.assertTrue("yoctoproject.org" in self.driver.current_url)
98
99 def test_link_setup_using_toaster_visible_and_clickable(self):
100 """ Test big magenta button setting up and using toaster link in jumbotron
101 if visible and clickable
102 """
103 self.get(reverse('landing'))
104 jumbotron = self.find('.jumbotron')
105
106 # check Big magenta button
107 big_magenta_button = jumbotron.find_element(By.LINK_TEXT,
108 'Toaster is ready to capture your command line builds'
109 )
110 self.assertTrue(big_magenta_button.is_displayed())
111 big_magenta_button.click()
112 self.assertTrue(
113 "docs.yoctoproject.org/toaster-manual/setup-and-use.html#setting-up-and-using-toaster" in self.driver.current_url)
114
115 def test_link_create_new_project_in_jumbotron_visible_and_clickable(self):
116 """ Test big blue button create new project jumbotron if visible and clickable """
117 # Create a layer and a layer version to make visible the big blue button
118 layer = Layer.objects.create(name='bar')
119 Layer_Version.objects.create(layer=layer)
120
121 self.get(reverse('landing'))
122 jumbotron = self.find('.jumbotron')
123
124 # check Big Blue button
125 big_blue_button = jumbotron.find_element(By.LINK_TEXT,
126 'Create your first Toaster project to run manage builds'
127 )
128 self.assertTrue(big_blue_button.is_displayed())
129 big_blue_button.click()
130 self.assertTrue("toastergui/newproject/" in self.driver.current_url)
131
132 def test_toaster_manual_link_visible_and_clickable(self):
133 """ Test Read the Toaster manual link jumbotron is visible and clickable: """
134 self.get(reverse('landing'))
135 jumbotron = self.find('.jumbotron')
136
137 # check Read the Toaster manual
138 toaster_manual = jumbotron.find_element(
139 By.LINK_TEXT, 'Read the Toaster manual')
140 self.assertTrue(toaster_manual.is_displayed())
141 toaster_manual.click()
142 self.assertTrue(
143 "https://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual" in self.driver.current_url)
144
145 def test_contrib_to_toaster_link_visible_and_clickable(self):
146 """ Test Contribute to Toaster link jumbotron is visible and clickable: """
147 self.get(reverse('landing'))
148 jumbotron = self.find('.jumbotron')
149
150 # check Contribute to Toaster
151 contribute_to_toaster = jumbotron.find_element(
152 By.LINK_TEXT, 'Contribute to Toaster')
153 self.assertTrue(contribute_to_toaster.is_displayed())
154 contribute_to_toaster.click()
155 self.assertTrue(
156 "wiki.yoctoproject.org/wiki/contribute_to_toaster" in str(self.driver.current_url).lower())
157
32 def test_only_default_project(self): 158 def test_only_default_project(self):
33 """ 159 """
34 No projects except default 160 No projects except default
@@ -87,10 +213,9 @@ class TestLandingPage(SeleniumTestCase):
87 213
88 self.get(reverse('landing')) 214 self.get(reverse('landing'))
89 215
216 self.wait_until_visible("#latest-builds", poll=3)
90 elements = self.find_all('#allbuildstable') 217 elements = self.find_all('#allbuildstable')
91 self.assertEqual(len(elements), 1, 'should redirect to builds') 218 self.assertEqual(len(elements), 1, 'should redirect to builds')
92 content = self.get_page_source() 219 content = self.get_page_source()
93 self.assertTrue(self.PROJECT_NAME in content, 220 self.assertTrue(self.PROJECT_NAME in content,
94 'should show builds for project %s' % self.PROJECT_NAME) 221 '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..5c29548b78 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 _edit_layerdetails(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,26 @@ 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_visible("#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 try:
111 self.wait_until_visible("#save-changes-for-switch", poll=3)
112 btn_save_chg_for_switch = self.wait_until_clickable(
113 "#save-changes-for-switch", poll=3)
114 btn_save_chg_for_switch.click()
115 except ElementClickInterceptedException:
116 self.skipTest(
117 "save-changes-for-switch click intercepted. Element not visible or maybe covered by another element.")
118 except TimeoutException:
119 self.skipTest(
120 "save-changes-for-switch is not clickable within the specified timeout.")
121
107 self.wait_until_visible("#edit-layer-source") 122 self.wait_until_visible("#edit-layer-source")
108 123
109 # Refresh the page to see if the new values are returned 124 # Refresh the page to see if the new values are returned
@@ -132,7 +147,18 @@ class TestLayerDetailsPage(SeleniumTestCase):
132 new_dir = "/home/test/my-meta-dir" 147 new_dir = "/home/test/my-meta-dir"
133 dir_input.send_keys(new_dir) 148 dir_input.send_keys(new_dir)
134 149
135 self.click("#save-changes-for-switch") 150 try:
151 self.wait_until_visible("#save-changes-for-switch", poll=3)
152 btn_save_chg_for_switch = self.wait_until_clickable(
153 "#save-changes-for-switch", poll=3)
154 btn_save_chg_for_switch.click()
155 except ElementClickInterceptedException:
156 self.skipTest(
157 "save-changes-for-switch click intercepted. Element not properly visible or maybe behind another element.")
158 except TimeoutException:
159 self.skipTest(
160 "save-changes-for-switch is not clickable within the specified timeout.")
161
136 self.wait_until_visible("#edit-layer-source") 162 self.wait_until_visible("#edit-layer-source")
137 163
138 # Refresh the page to see if the new values are returned 164 # Refresh the page to see if the new values are returned
@@ -142,6 +168,13 @@ class TestLayerDetailsPage(SeleniumTestCase):
142 "Expected %s in the dir value for layer directory" % 168 "Expected %s in the dir value for layer directory" %
143 new_dir) 169 new_dir)
144 170
171 def test_edit_layerdetails_page(self):
172 try:
173 self._edit_layerdetails()
174 except ElementClickInterceptedException:
175 self.skipTest(
176 "ElementClickInterceptedException occured. Element not visible or maybe covered by another element.")
177
145 def test_delete_layer(self): 178 def test_delete_layer(self):
146 """ Delete the layer """ 179 """ Delete the layer """
147 180
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..9f0b6397fe 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', poll=3)
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..458bb6538d 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', poll=3)
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', poll=3)
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,13 +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', poll=3)
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.click("#create-project-button")
93
94 self.wait_until_present('#hint-error-project-name', poll=3)
95 element = self.find('#hint-error-project-name')
88 96
89 self.assertTrue(("Project names must be unique" in element.text), 97 self.assertTrue(("Project names must be unique" in element.text),
90 "Did not find unique project name error message") 98 "Did not find unique project name error message")
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()