summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
blob: b9356a034490b694eb043b2a120f6b1e5dfd2d2c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
#! /usr/bin/env python3
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013-2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
#

import os
import re

from django.urls import reverse
from selenium.webdriver.support.select import Select
from django.utils import timezone
from bldcontrol.models import BuildRequest
from tests.browser.selenium_helpers import SeleniumTestCase

from orm.models import BitbakeVersion, Layer, Layer_Version, Recipe, Release, Project, Build, Target, Task

from selenium.webdriver.common.by import By


class TestAllBuildsPage(SeleniumTestCase):
    """ Tests for all builds page /builds/ """

    PROJECT_NAME = 'test project'
    CLI_BUILDS_PROJECT_NAME = 'command line builds'

    def setUp(self):
        builldir = os.environ.get('BUILDDIR', './')
        bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
                                            branch='master', dirpath='')
        release = Release.objects.create(name='release1',
                                         bitbake_version=bbv)
        self.project1 = Project.objects.create_project(name=self.PROJECT_NAME,
                                                       release=release)
        self.default_project = Project.objects.create_project(
            name=self.CLI_BUILDS_PROJECT_NAME,
            release=release
        )
        self.default_project.is_default = True
        self.default_project.save()

        # parameters for builds to associate with the projects
        now = timezone.now()

        self.project1_build_success = {
            'project': self.project1,
            'started_on': now,
            'completed_on': now,
            'outcome': Build.SUCCEEDED
        }

        self.project1_build_failure = {
            'project': self.project1,
            'started_on': now,
            'completed_on': now,
            'outcome': Build.FAILED
        }

        self.default_project_build_success = {
            'project': self.default_project,
            'started_on': now,
            'completed_on': now,
            'outcome': Build.SUCCEEDED
        }

    def _get_build_time_element(self, build):
        """
        Return the HTML element containing the build time for a build
        in the recent builds area
        """
        selector = 'div[data-latest-build-result="%s"] ' \
            '[data-role="data-recent-build-buildtime-field"]' % build.id

        # because this loads via Ajax, wait for it to be visible
        self.wait_until_visible(selector)

        build_time_spans = self.find_all(selector)

        self.assertEqual(len(build_time_spans), 1)

        return build_time_spans[0]

    def _get_row_for_build(self, build):
        """ Get the table row for the build from the all builds table """
        self.wait_until_visible('#allbuildstable')

        rows = self.find_all('#allbuildstable tr')

        # look for the row with a download link on the recipe which matches the
        # build ID
        url = reverse('builddashboard', args=(build.id,))
        selector = 'td.target a[href="%s"]' % url

        found_row = None
        for row in rows:

            outcome_links = row.find_elements(By.CSS_SELECTOR, selector)
            if len(outcome_links) == 1:
                found_row = row
                break

        self.assertNotEqual(found_row, None)

        return found_row

    def _get_create_builds(self, **kwargs):
        """ Create a build and return the build object """
        build1 = Build.objects.create(**self.project1_build_success)
        build2 = Build.objects.create(**self.project1_build_failure)

        # add some targets to these builds so they have recipe links
        # (and so we can find the row in the ToasterTable corresponding to
        # a particular build)
        Target.objects.create(build=build1, target='foo')
        Target.objects.create(build=build2, target='bar')

        if kwargs:
            # Create kwargs.get('success') builds with success status with target
            # and kwargs.get('failure') builds with failure status with target
            for i in range(kwargs.get('success', 0)):
                now = timezone.now()
                self.project1_build_success['started_on'] = now
                self.project1_build_success[
                    'completed_on'] = now - timezone.timedelta(days=i)
                build = Build.objects.create(**self.project1_build_success)
                Target.objects.create(build=build,
                                      target=f'{i}_success_recipe',
                                      task=f'{i}_success_task')

                self._set_buildRequest_and_task_on_build(build)
            for i in range(kwargs.get('failure', 0)):
                now = timezone.now()
                self.project1_build_failure['started_on'] = now
                self.project1_build_failure[
                    'completed_on'] = now - timezone.timedelta(days=i)
                build = Build.objects.create(**self.project1_build_failure)
                Target.objects.create(build=build,
                                      target=f'{i}_fail_recipe',
                                      task=f'{i}_fail_task')
                self._set_buildRequest_and_task_on_build(build)
        return build1, build2

    def _create_recipe(self):
        """ Add a recipe to the database and return it """
        layer = Layer.objects.create()
        layer_version = Layer_Version.objects.create(layer=layer)
        return Recipe.objects.create(name='recipe_foo', layer_version=layer_version)

    def _set_buildRequest_and_task_on_build(self, build):
        """ Set buildRequest and task on build """
        build.recipes_parsed = 1
        build.save()
        buildRequest = BuildRequest.objects.create(
            build=build,
            project=self.project1,
            state=BuildRequest.REQ_COMPLETED)
        build.build_request = buildRequest
        recipe = self._create_recipe()
        task = Task.objects.create(build=build,
                                   recipe=recipe,
                                   task_name='task',
                                   outcome=Task.OUTCOME_SUCCESS)
        task.save()
        build.save()

    def test_show_tasks_with_suffix(self):
        """ Task should be shown as suffix on build name """
        build = Build.objects.create(**self.project1_build_success)
        target = 'bash'
        task = 'clean'
        Target.objects.create(build=build, target=target, task=task)

        url = reverse('all-builds')
        self.get(url)
        self.wait_until_visible('td[class="target"]')

        cell = self.find('td[class="target"]')
        content = cell.get_attribute('innerHTML')
        expected_text = '%s:%s' % (target, task)

        self.assertTrue(re.search(expected_text, content),
                        '"target" cell should contain text %s' % expected_text)

    def test_rebuild_buttons(self):
        """
        Test 'Rebuild' buttons in recent builds section

        'Rebuild' button should not be shown for command-line builds,
        but should be shown for other builds
        """
        build1 = Build.objects.create(**self.project1_build_success)
        default_build = Build.objects.create(
            **self.default_project_build_success)

        url = reverse('all-builds')
        self.get(url)

        # should see a rebuild button for non-command-line builds
        self.wait_until_visible('#allbuildstable tbody tr')
        selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id
        run_again_button = self.find_all(selector)
        self.assertEqual(len(run_again_button), 1,
                         'should see a rebuild button for non-cli builds')

        # shouldn't see a rebuild button for command-line builds
        selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % default_build.id
        run_again_button = self.find_all(selector)
        self.assertEqual(len(run_again_button), 0,
                         'should not see a rebuild button for cli builds')

    def test_tooltips_on_project_name(self):
        """
        Test tooltips shown next to project name in the main table

        A tooltip should be present next to the command line
        builds project name in the all builds page, but not for
        other projects
        """
        Build.objects.create(**self.project1_build_success)
        Build.objects.create(**self.default_project_build_success)

        url = reverse('all-builds')
        self.get(url)
        self.wait_until_visible('#allbuildstable', poll=3)

        # get the project name cells from the table
        cells = self.find_all('#allbuildstable td[class="project"]')

        selector = 'span.get-help'

        for cell in cells:
            content = cell.get_attribute('innerHTML')
            help_icons = cell.find_elements(By.CSS_SELECTOR, selector)

            if re.search(self.PROJECT_NAME, content):
                # no help icon next to non-cli project name
                msg = 'should not be a help icon for non-cli builds name'
                self.assertEqual(len(help_icons), 0, msg)
            elif re.search(self.CLI_BUILDS_PROJECT_NAME, content):
                # help icon next to cli project name
                msg = 'should be a help icon for cli builds name'
                self.assertEqual(len(help_icons), 1, msg)
            else:
                msg = 'found unexpected project name cell in all builds table'
                self.fail(msg)

    def test_builds_time_links(self):
        """
        Successful builds should have links on the time column and in the
        recent builds area; failed builds should not have links on the time column,
        or in the recent builds area
        """
        build1, build2 = self._get_create_builds()

        url = reverse('all-builds')
        self.get(url)
        self.wait_until_visible('#allbuildstable', poll=3)

        # test recent builds area for successful build
        element = self._get_build_time_element(build1)
        links = element.find_elements(By.CSS_SELECTOR, 'a')
        msg = 'should be a link on the build time for a successful recent build'
        self.assertEqual(len(links), 1, msg)

        # test recent builds area for failed build
        element = self._get_build_time_element(build2)
        links = element.find_elements(By.CSS_SELECTOR, 'a')
        msg = 'should not be a link on the build time for a failed recent build'
        self.assertEqual(len(links), 0, msg)

        # test the time column for successful build
        build1_row = self._get_row_for_build(build1)
        links = build1_row.find_elements(By.CSS_SELECTOR, 'td.time a')
        msg = 'should be a link on the build time for a successful build'
        self.assertEqual(len(links), 1, msg)

        # test the time column for failed build
        build2_row = self._get_row_for_build(build2)
        links = build2_row.find_elements(By.CSS_SELECTOR, 'td.time a')
        msg = 'should not be a link on the build time for a failed build'
        self.assertEqual(len(links), 0, msg)

    def test_builds_table_search_box(self):
        """ Test the search box in the builds table on the all builds page """
        self._get_create_builds()

        url = reverse('all-builds')
        self.get(url)

        # Check search box is present and works
        self.wait_until_visible('#allbuildstable tbody tr')
        search_box = self.find('#search-input-allbuildstable')
        self.assertTrue(search_box.is_displayed())

        # Check that we can search for a build by recipe name
        search_box.send_keys('foo')
        search_btn = self.find('#search-submit-allbuildstable')
        search_btn.click()
        self.wait_until_visible('#allbuildstable tbody tr')
        rows = self.find_all('#allbuildstable tbody tr')
        self.assertTrue(len(rows) >= 1)

    def test_filtering_on_failure_tasks_column(self):
        """ Test the filtering on failure tasks column in the builds table on the all builds page """
        def _check_if_filter_failed_tasks_column_is_visible():
            # check if failed tasks filter column is visible, if not click on it
            # Check edit column
            edit_column = self.find('#edit-columns-button')
            self.assertTrue(edit_column.is_displayed())
            edit_column.click()
            # Check dropdown is visible
            self.wait_until_visible('ul.dropdown-menu.editcol')
            filter_fails_task_checkbox = self.find('#checkbox-failed_tasks')
            if not filter_fails_task_checkbox.is_selected():
                filter_fails_task_checkbox.click()
            edit_column.click()

        self._get_create_builds(success=10, failure=10)

        url = reverse('all-builds')
        self.get(url)

        # Check filtering on failure tasks column
        self.wait_until_visible('#allbuildstable tbody tr')
        _check_if_filter_failed_tasks_column_is_visible()
        failed_tasks_filter = self.find('#failed_tasks_filter')
        failed_tasks_filter.click()
        # Check popup is visible
        self.wait_until_visible('#filter-modal-allbuildstable')
        self.assertTrue(
            self.find('#filter-modal-allbuildstable').is_displayed())
        # Check that we can filter by failure tasks
        build_without_failure_tasks = self.find(
            '#failed_tasks_filter\\:without_failed_tasks')
        build_without_failure_tasks.click()
        # click on apply button
        self.find('#filter-modal-allbuildstable .btn-primary').click()
        self.wait_until_visible('#allbuildstable tbody tr')
        # Check if filter is applied, by checking if failed_tasks_filter has btn-primary class
        self.assertTrue(self.find('#failed_tasks_filter').get_attribute(
            'class').find('btn-primary') != -1)

    def test_filtering_on_completedOn_column(self):
        """ Test the filtering on completed_on column in the builds table on the all builds page """
        self._get_create_builds(success=10, failure=10)

        url = reverse('all-builds')
        self.get(url)

        # Check filtering on failure tasks column
        self.wait_until_visible('#allbuildstable tbody tr')
        completed_on_filter = self.find('#completed_on_filter')
        completed_on_filter.click()
        # Check popup is visible
        self.wait_until_visible('#filter-modal-allbuildstable')
        self.assertTrue(
            self.find('#filter-modal-allbuildstable').is_displayed())
        # Check that we can filter by failure tasks
        build_without_failure_tasks = self.find(
            '#completed_on_filter\\:date_range')
        build_without_failure_tasks.click()
        # click on apply button
        self.find('#filter-modal-allbuildstable .btn-primary').click()
        self.wait_until_visible('#allbuildstable tbody tr')
        # Check if filter is applied, by checking if completed_on_filter has btn-primary class
        self.assertTrue(self.find('#completed_on_filter').get_attribute(
            'class').find('btn-primary') != -1)

        # Filter by date range
        self.find('#completed_on_filter').click()
        self.wait_until_visible('#filter-modal-allbuildstable')
        date_ranges = self.driver.find_elements(
            By.XPATH, '//input[@class="form-control hasDatepicker"]')
        today = timezone.now()
        yestersday = today - timezone.timedelta(days=1)
        date_ranges[0].send_keys(yestersday.strftime('%Y-%m-%d'))
        date_ranges[1].send_keys(today.strftime('%Y-%m-%d'))
        self.find('#filter-modal-allbuildstable .btn-primary').click()
        self.wait_until_visible('#allbuildstable tbody tr')
        self.assertTrue(self.find('#completed_on_filter').get_attribute(
            'class').find('btn-primary') != -1)
        # Check if filter is applied, number of builds displayed should be 6
        self.assertTrue(len(self.find_all('#allbuildstable tbody tr')) >= 4)

    def test_builds_table_editColumn(self):
        """ Test the edit column feature in the builds table on the all builds page """
        self._get_create_builds(success=10, failure=10)

        def test_edit_column(check_box_id):
            # Check that we can hide/show table column
            check_box = self.find(f'#{check_box_id}')
            th_class = str(check_box_id).replace('checkbox-', '')
            if check_box.is_selected():
                # check if column is visible in table
                self.assertTrue(
                    self.find(
                        f'#allbuildstable thead th.{th_class}'
                    ).is_displayed(),
                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
                )
                check_box.click()
                # check if column is hidden in table
                self.assertFalse(
                    self.find(
                        f'#allbuildstable thead th.{th_class}'
                    ).is_displayed(),
                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
                )
            else:
                # check if column is hidden in table
                self.assertFalse(
                    self.find(
                        f'#allbuildstable thead th.{th_class}'
                    ).is_displayed(),
                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
                )
                check_box.click()
                # check if column is visible in table
                self.assertTrue(
                    self.find(
                        f'#allbuildstable thead th.{th_class}'
                    ).is_displayed(),
                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
                )
        url = reverse('all-builds')
        self.get(url)
        self.wait_until_visible('#allbuildstable tbody tr')

        # Check edit column
        edit_column = self.find('#edit-columns-button')
        self.assertTrue(edit_column.is_displayed())
        edit_column.click()
        # Check dropdown is visible
        self.wait_until_visible('ul.dropdown-menu.editcol')

        # Check that we can hide the edit column
        test_edit_column('checkbox-errors_no')
        test_edit_column('checkbox-failed_tasks')
        test_edit_column('checkbox-image_files')
        test_edit_column('checkbox-project')
        test_edit_column('checkbox-started_on')
        test_edit_column('checkbox-time')
        test_edit_column('checkbox-warnings_no')

    def test_builds_table_show_rows(self):
        """ Test the show rows feature in the builds table on the all builds page """
        self._get_create_builds(success=100, failure=100)

        def test_show_rows(row_to_show, show_row_link):
            # Check that we can show rows == row_to_show
            show_row_link.select_by_value(str(row_to_show))
            self.wait_until_visible('#allbuildstable tbody tr', poll=3)
            # check at least some rows are visible
            self.assertTrue(
                len(self.find_all('#allbuildstable tbody tr')) > 0
            )

        url = reverse('all-builds')
        self.get(url)
        self.wait_until_visible('#allbuildstable tbody tr')

        show_rows = self.driver.find_elements(
            By.XPATH,
            '//select[@class="form-control pagesize-allbuildstable"]'
        )
        # Check show rows
        for show_row_link in show_rows:
            show_row_link = Select(show_row_link)
            test_show_rows(10, show_row_link)
            test_show_rows(25, show_row_link)
            test_show_rows(50, show_row_link)
            test_show_rows(100, show_row_link)
            test_show_rows(150, show_row_link)