test_all_builds_page.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. #! /usr/bin/env python3
  2. #
  3. # BitBake Toaster Implementation
  4. #
  5. # Copyright (C) 2013-2016 Intel Corporation
  6. #
  7. # SPDX-License-Identifier: GPL-2.0-only
  8. #
  9. import re
  10. import time
  11. from django.urls import reverse
  12. from selenium.webdriver.support.select import Select
  13. from django.utils import timezone
  14. from bldcontrol.models import BuildRequest
  15. from tests.browser.selenium_helpers import SeleniumTestCase
  16. from orm.models import BitbakeVersion, Layer, Layer_Version, Recipe, Release, Project, Build, Target, Task
  17. from selenium.webdriver.common.by import By
  18. class TestAllBuildsPage(SeleniumTestCase):
  19. """ Tests for all builds page /builds/ """
  20. PROJECT_NAME = 'test project'
  21. CLI_BUILDS_PROJECT_NAME = 'command line builds'
  22. def setUp(self):
  23. bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/',
  24. branch='master', dirpath='')
  25. release = Release.objects.create(name='release1',
  26. bitbake_version=bbv)
  27. self.project1 = Project.objects.create_project(name=self.PROJECT_NAME,
  28. release=release)
  29. self.default_project = Project.objects.create_project(
  30. name=self.CLI_BUILDS_PROJECT_NAME,
  31. release=release
  32. )
  33. self.default_project.is_default = True
  34. self.default_project.save()
  35. # parameters for builds to associate with the projects
  36. now = timezone.now()
  37. self.project1_build_success = {
  38. 'project': self.project1,
  39. 'started_on': now,
  40. 'completed_on': now,
  41. 'outcome': Build.SUCCEEDED
  42. }
  43. self.project1_build_failure = {
  44. 'project': self.project1,
  45. 'started_on': now,
  46. 'completed_on': now,
  47. 'outcome': Build.FAILED
  48. }
  49. self.default_project_build_success = {
  50. 'project': self.default_project,
  51. 'started_on': now,
  52. 'completed_on': now,
  53. 'outcome': Build.SUCCEEDED
  54. }
  55. def _get_build_time_element(self, build):
  56. """
  57. Return the HTML element containing the build time for a build
  58. in the recent builds area
  59. """
  60. selector = 'div[data-latest-build-result="%s"] ' \
  61. '[data-role="data-recent-build-buildtime-field"]' % build.id
  62. # because this loads via Ajax, wait for it to be visible
  63. self.wait_until_present(selector)
  64. build_time_spans = self.find_all(selector)
  65. self.assertEqual(len(build_time_spans), 1)
  66. return build_time_spans[0]
  67. def _get_row_for_build(self, build):
  68. """ Get the table row for the build from the all builds table """
  69. self.wait_until_present('#allbuildstable')
  70. rows = self.find_all('#allbuildstable tr')
  71. # look for the row with a download link on the recipe which matches the
  72. # build ID
  73. url = reverse('builddashboard', args=(build.id,))
  74. selector = 'td.target a[href="%s"]' % url
  75. found_row = None
  76. for row in rows:
  77. outcome_links = row.find_elements(By.CSS_SELECTOR, selector)
  78. if len(outcome_links) == 1:
  79. found_row = row
  80. break
  81. self.assertNotEqual(found_row, None)
  82. return found_row
  83. def _get_create_builds(self, **kwargs):
  84. """ Create a build and return the build object """
  85. build1 = Build.objects.create(**self.project1_build_success)
  86. build2 = Build.objects.create(**self.project1_build_failure)
  87. # add some targets to these builds so they have recipe links
  88. # (and so we can find the row in the ToasterTable corresponding to
  89. # a particular build)
  90. Target.objects.create(build=build1, target='foo')
  91. Target.objects.create(build=build2, target='bar')
  92. if kwargs:
  93. # Create kwargs.get('success') builds with success status with target
  94. # and kwargs.get('failure') builds with failure status with target
  95. for i in range(kwargs.get('success', 0)):
  96. now = timezone.now()
  97. self.project1_build_success['started_on'] = now
  98. self.project1_build_success[
  99. 'completed_on'] = now - timezone.timedelta(days=i)
  100. build = Build.objects.create(**self.project1_build_success)
  101. Target.objects.create(build=build,
  102. target=f'{i}_success_recipe',
  103. task=f'{i}_success_task')
  104. self._set_buildRequest_and_task_on_build(build)
  105. for i in range(kwargs.get('failure', 0)):
  106. now = timezone.now()
  107. self.project1_build_failure['started_on'] = now
  108. self.project1_build_failure[
  109. 'completed_on'] = now - timezone.timedelta(days=i)
  110. build = Build.objects.create(**self.project1_build_failure)
  111. Target.objects.create(build=build,
  112. target=f'{i}_fail_recipe',
  113. task=f'{i}_fail_task')
  114. self._set_buildRequest_and_task_on_build(build)
  115. return build1, build2
  116. def _create_recipe(self):
  117. """ Add a recipe to the database and return it """
  118. layer = Layer.objects.create()
  119. layer_version = Layer_Version.objects.create(layer=layer)
  120. return Recipe.objects.create(name='recipe_foo', layer_version=layer_version)
  121. def _set_buildRequest_and_task_on_build(self, build):
  122. """ Set buildRequest and task on build """
  123. build.recipes_parsed = 1
  124. build.save()
  125. buildRequest = BuildRequest.objects.create(
  126. build=build,
  127. project=self.project1,
  128. state=BuildRequest.REQ_COMPLETED)
  129. build.build_request = buildRequest
  130. recipe = self._create_recipe()
  131. task = Task.objects.create(build=build,
  132. recipe=recipe,
  133. task_name='task',
  134. outcome=Task.OUTCOME_SUCCESS)
  135. task.save()
  136. build.save()
  137. def test_show_tasks_with_suffix(self):
  138. """ Task should be shown as suffix on build name """
  139. build = Build.objects.create(**self.project1_build_success)
  140. target = 'bash'
  141. task = 'clean'
  142. Target.objects.create(build=build, target=target, task=task)
  143. url = reverse('all-builds')
  144. self.get(url)
  145. self.wait_until_present('td[class="target"]')
  146. cell = self.find('td[class="target"]')
  147. content = cell.get_attribute('innerHTML')
  148. expected_text = '%s:%s' % (target, task)
  149. self.assertTrue(re.search(expected_text, content),
  150. '"target" cell should contain text %s' % expected_text)
  151. def test_rebuild_buttons(self):
  152. """
  153. Test 'Rebuild' buttons in recent builds section
  154. 'Rebuild' button should not be shown for command-line builds,
  155. but should be shown for other builds
  156. """
  157. build1 = Build.objects.create(**self.project1_build_success)
  158. default_build = Build.objects.create(
  159. **self.default_project_build_success)
  160. url = reverse('all-builds')
  161. self.get(url)
  162. # should see a rebuild button for non-command-line builds
  163. selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id
  164. time.sleep(2)
  165. run_again_button = self.find_all(selector)
  166. self.assertEqual(len(run_again_button), 1,
  167. 'should see a rebuild button for non-cli builds')
  168. # shouldn't see a rebuild button for command-line builds
  169. selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % default_build.id
  170. run_again_button = self.find_all(selector)
  171. self.assertEqual(len(run_again_button), 0,
  172. 'should not see a rebuild button for cli builds')
  173. def test_tooltips_on_project_name(self):
  174. """
  175. Test tooltips shown next to project name in the main table
  176. A tooltip should be present next to the command line
  177. builds project name in the all builds page, but not for
  178. other projects
  179. """
  180. Build.objects.create(**self.project1_build_success)
  181. Build.objects.create(**self.default_project_build_success)
  182. url = reverse('all-builds')
  183. self.get(url)
  184. # get the project name cells from the table
  185. cells = self.find_all('#allbuildstable td[class="project"]')
  186. selector = 'span.get-help'
  187. for cell in cells:
  188. content = cell.get_attribute('innerHTML')
  189. help_icons = cell.find_elements_by_css_selector(selector)
  190. if re.search(self.PROJECT_NAME, content):
  191. # no help icon next to non-cli project name
  192. msg = 'should not be a help icon for non-cli builds name'
  193. self.assertEqual(len(help_icons), 0, msg)
  194. elif re.search(self.CLI_BUILDS_PROJECT_NAME, content):
  195. # help icon next to cli project name
  196. msg = 'should be a help icon for cli builds name'
  197. self.assertEqual(len(help_icons), 1, msg)
  198. else:
  199. msg = 'found unexpected project name cell in all builds table'
  200. self.fail(msg)
  201. def test_builds_time_links(self):
  202. """
  203. Successful builds should have links on the time column and in the
  204. recent builds area; failed builds should not have links on the time column,
  205. or in the recent builds area
  206. """
  207. build1, build2 = self._get_create_builds()
  208. url = reverse('all-builds')
  209. self.get(url)
  210. # test recent builds area for successful build
  211. element = self._get_build_time_element(build1)
  212. links = element.find_elements(By.CSS_SELECTOR, 'a')
  213. msg = 'should be a link on the build time for a successful recent build'
  214. self.assertEquals(len(links), 1, msg)
  215. # test recent builds area for failed build
  216. element = self._get_build_time_element(build2)
  217. links = element.find_elements(By.CSS_SELECTOR, 'a')
  218. msg = 'should not be a link on the build time for a failed recent build'
  219. self.assertEquals(len(links), 0, msg)
  220. # test the time column for successful build
  221. build1_row = self._get_row_for_build(build1)
  222. links = build1_row.find_elements(By.CSS_SELECTOR, 'td.time a')
  223. msg = 'should be a link on the build time for a successful build'
  224. self.assertEquals(len(links), 1, msg)
  225. # test the time column for failed build
  226. build2_row = self._get_row_for_build(build2)
  227. links = build2_row.find_elements(By.CSS_SELECTOR, 'td.time a')
  228. msg = 'should not be a link on the build time for a failed build'
  229. self.assertEquals(len(links), 0, msg)
  230. def test_builds_table_search_box(self):
  231. """ Test the search box in the builds table on the all builds page """
  232. self._get_create_builds()
  233. url = reverse('all-builds')
  234. self.get(url)
  235. # Check search box is present and works
  236. self.wait_until_present('#allbuildstable tbody tr')
  237. search_box = self.find('#search-input-allbuildstable')
  238. self.assertTrue(search_box.is_displayed())
  239. # Check that we can search for a build by recipe name
  240. search_box.send_keys('foo')
  241. search_btn = self.find('#search-submit-allbuildstable')
  242. search_btn.click()
  243. self.wait_until_present('#allbuildstable tbody tr')
  244. rows = self.find_all('#allbuildstable tbody tr')
  245. self.assertTrue(len(rows) >= 1)
  246. def test_filtering_on_failure_tasks_column(self):
  247. """ Test the filtering on failure tasks column in the builds table on the all builds page """
  248. self._get_create_builds(success=10, failure=10)
  249. url = reverse('all-builds')
  250. self.get(url)
  251. # Check filtering on failure tasks column
  252. self.wait_until_present('#allbuildstable tbody tr')
  253. failed_tasks_filter = self.find('#failed_tasks_filter')
  254. failed_tasks_filter.click()
  255. # Check popup is visible
  256. time.sleep(1)
  257. self.wait_until_present('#filter-modal-allbuildstable')
  258. self.assertTrue(
  259. self.find('#filter-modal-allbuildstable').is_displayed())
  260. # Check that we can filter by failure tasks
  261. build_without_failure_tasks = self.find(
  262. '#failed_tasks_filter\\:without_failed_tasks')
  263. build_without_failure_tasks.click()
  264. # click on apply button
  265. self.find('#filter-modal-allbuildstable .btn-primary').click()
  266. self.wait_until_present('#allbuildstable tbody tr')
  267. # Check if filter is applied, by checking if failed_tasks_filter has btn-primary class
  268. self.assertTrue(self.find('#failed_tasks_filter').get_attribute(
  269. 'class').find('btn-primary') != -1)
  270. def test_filtering_on_completedOn_column(self):
  271. """ Test the filtering on completed_on column in the builds table on the all builds page """
  272. self._get_create_builds(success=10, failure=10)
  273. url = reverse('all-builds')
  274. self.get(url)
  275. # Check filtering on failure tasks column
  276. self.wait_until_present('#allbuildstable tbody tr')
  277. completed_on_filter = self.find('#completed_on_filter')
  278. completed_on_filter.click()
  279. # Check popup is visible
  280. time.sleep(1)
  281. self.wait_until_present('#filter-modal-allbuildstable')
  282. self.assertTrue(
  283. self.find('#filter-modal-allbuildstable').is_displayed())
  284. # Check that we can filter by failure tasks
  285. build_without_failure_tasks = self.find(
  286. '#completed_on_filter\\:date_range')
  287. build_without_failure_tasks.click()
  288. # click on apply button
  289. self.find('#filter-modal-allbuildstable .btn-primary').click()
  290. self.wait_until_present('#allbuildstable tbody tr')
  291. # Check if filter is applied, by checking if completed_on_filter has btn-primary class
  292. self.assertTrue(self.find('#completed_on_filter').get_attribute(
  293. 'class').find('btn-primary') != -1)
  294. # Filter by date range
  295. self.find('#completed_on_filter').click()
  296. self.wait_until_present('#filter-modal-allbuildstable')
  297. date_ranges = self.driver.find_elements(
  298. By.XPATH, '//input[@class="form-control hasDatepicker"]')
  299. today = timezone.now()
  300. yestersday = today - timezone.timedelta(days=1)
  301. time.sleep(1)
  302. date_ranges[0].send_keys(yestersday.strftime('%Y-%m-%d'))
  303. date_ranges[1].send_keys(today.strftime('%Y-%m-%d'))
  304. self.find('#filter-modal-allbuildstable .btn-primary').click()
  305. self.wait_until_present('#allbuildstable tbody tr')
  306. self.assertTrue(self.find('#completed_on_filter').get_attribute(
  307. 'class').find('btn-primary') != -1)
  308. # Check if filter is applied, number of builds displayed should be 6
  309. time.sleep(1)
  310. self.assertTrue(len(self.find_all('#allbuildstable tbody tr')) == 6)
  311. def test_builds_table_editColumn(self):
  312. """ Test the edit column feature in the builds table on the all builds page """
  313. self._get_create_builds(success=10, failure=10)
  314. def test_edit_column(check_box_id):
  315. # Check that we can hide/show table column
  316. check_box = self.find(f'#{check_box_id}')
  317. th_class = str(check_box_id).replace('checkbox-', '')
  318. if check_box.is_selected():
  319. # check if column is visible in table
  320. self.assertTrue(
  321. self.find(
  322. f'#allbuildstable thead th.{th_class}'
  323. ).is_displayed(),
  324. f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
  325. )
  326. check_box.click()
  327. # check if column is hidden in table
  328. self.assertFalse(
  329. self.find(
  330. f'#allbuildstable thead th.{th_class}'
  331. ).is_displayed(),
  332. f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
  333. )
  334. else:
  335. # check if column is hidden in table
  336. self.assertFalse(
  337. self.find(
  338. f'#allbuildstable thead th.{th_class}'
  339. ).is_displayed(),
  340. f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
  341. )
  342. check_box.click()
  343. # check if column is visible in table
  344. self.assertTrue(
  345. self.find(
  346. f'#allbuildstable thead th.{th_class}'
  347. ).is_displayed(),
  348. f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
  349. )
  350. url = reverse('all-builds')
  351. self.get(url)
  352. self.wait_until_present('#allbuildstable tbody tr')
  353. # Check edit column
  354. edit_column = self.find('#edit-columns-button')
  355. self.assertTrue(edit_column.is_displayed())
  356. edit_column.click()
  357. # Check dropdown is visible
  358. self.wait_until_visible('ul.dropdown-menu.editcol')
  359. # Check that we can hide the edit column
  360. test_edit_column('checkbox-errors_no')
  361. test_edit_column('checkbox-failed_tasks')
  362. test_edit_column('checkbox-image_files')
  363. test_edit_column('checkbox-project')
  364. test_edit_column('checkbox-started_on')
  365. test_edit_column('checkbox-time')
  366. test_edit_column('checkbox-warnings_no')
  367. def test_builds_table_show_rows(self):
  368. """ Test the show rows feature in the builds table on the all builds page """
  369. self._get_create_builds(success=100, failure=100)
  370. def test_show_rows(row_to_show, show_row_link):
  371. # Check that we can show rows == row_to_show
  372. show_row_link.select_by_value(str(row_to_show))
  373. self.wait_until_present('#allbuildstable tbody tr')
  374. time.sleep(1)
  375. self.assertTrue(
  376. len(self.find_all('#allbuildstable tbody tr')) == row_to_show
  377. )
  378. url = reverse('all-builds')
  379. self.get(url)
  380. self.wait_until_present('#allbuildstable tbody tr')
  381. show_rows = self.driver.find_elements(
  382. By.XPATH,
  383. '//select[@class="form-control pagesize-allbuildstable"]'
  384. )
  385. # Check show rows
  386. for show_row_link in show_rows:
  387. show_row_link = Select(show_row_link)
  388. test_show_rows(10, show_row_link)
  389. test_show_rows(25, show_row_link)
  390. test_show_rows(50, show_row_link)
  391. test_show_rows(100, show_row_link)
  392. test_show_rows(150, show_row_link)