summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster')
-rwxr-xr-xbitbake/lib/toaster/orm/fixtures/check_fixtures.py38
-rwxr-xr-xbitbake/lib/toaster/orm/fixtures/gen_fixtures.py22
-rw-r--r--bitbake/lib/toaster/orm/fixtures/oe-core.xml68
-rw-r--r--bitbake/lib/toaster/orm/fixtures/poky.xml166
-rw-r--r--bitbake/lib/toaster/orm/models.py3
-rw-r--r--bitbake/lib/toaster/tests/browser/selenium_helpers_base.py50
-rw-r--r--bitbake/lib/toaster/tests/browser/test_all_builds_page.py7
-rw-r--r--bitbake/lib/toaster/tests/browser/test_all_projects_page.py12
-rw-r--r--bitbake/lib/toaster/tests/browser/test_builddashboard_page.py2
-rw-r--r--bitbake/lib/toaster/tests/browser/test_landing_page.py14
-rw-r--r--bitbake/lib/toaster/tests/browser/test_layerdetails_page.py52
-rw-r--r--bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py2
-rw-r--r--bitbake/lib/toaster/tests/browser/test_new_project_page.py12
-rw-r--r--bitbake/lib/toaster/tests/builds/buildtest.py2
-rw-r--r--bitbake/lib/toaster/tests/functional/functional_helpers.py86
-rw-r--r--bitbake/lib/toaster/tests/functional/test_create_new_project.py91
-rw-r--r--bitbake/lib/toaster/tests/functional/test_functional_basic.py198
-rw-r--r--bitbake/lib/toaster/tests/functional/test_project_config.py97
-rw-r--r--bitbake/lib/toaster/tests/functional/test_project_page.py415
-rw-r--r--bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py311
-rw-r--r--bitbake/lib/toaster/tests/functional/utils.py21
-rw-r--r--bitbake/lib/toaster/tests/toaster-tests-requirements.txt2
-rw-r--r--bitbake/lib/toaster/toastergui/templatetags/projecttags.py1
-rw-r--r--bitbake/lib/toaster/toastergui/views.py2
-rw-r--r--bitbake/lib/toaster/toastermain/settings.py1
25 files changed, 895 insertions, 780 deletions
diff --git a/bitbake/lib/toaster/orm/fixtures/check_fixtures.py b/bitbake/lib/toaster/orm/fixtures/check_fixtures.py
new file mode 100755
index 0000000000..ae3722e0f6
--- /dev/null
+++ b/bitbake/lib/toaster/orm/fixtures/check_fixtures.py
@@ -0,0 +1,38 @@
1#!/usr/bin/env python3
2#
3# Copyright (C) 2025 Linux Foundation
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7import json
8import urllib.request
9
10import gen_fixtures as fixtures
11
12RELEASE_URL = "https://dashboard.yoctoproject.org/releases.json"
13
14with urllib.request.urlopen(RELEASE_URL) as response:
15 if response.getcode() == 200:
16 data = response.read().decode("utf-8")
17 releases = json.loads(data)
18 else:
19 print("Couldn't access %s: %s" % (RELEASE_URL, reponse.getcode()))
20 exit(1)
21
22
23# grab the recent release branches and add master, so we can ignore old branches
24active_releases = [
25 e["release_codename"].lower() for e in releases if e["series"] == "current"
26]
27active_releases.append("master")
28active_releases.append("head")
29
30fixtures_releases = [x[0].lower() for x in fixtures.current_releases]
31
32if set(active_releases) != set(fixtures_releases):
33 print("WARNING: Active releases don't match toaster configured releases, the difference is: %s" % set(active_releases).difference(set(fixtures_releases)))
34 print("Active releases: %s" % sorted(active_releases))
35 print("Toaster configured releases: %s" % sorted(fixtures_releases))
36else:
37 print("Success, configuration matches")
38
diff --git a/bitbake/lib/toaster/orm/fixtures/gen_fixtures.py b/bitbake/lib/toaster/orm/fixtures/gen_fixtures.py
index 71afe3914e..6201f679b9 100755
--- a/bitbake/lib/toaster/orm/fixtures/gen_fixtures.py
+++ b/bitbake/lib/toaster/orm/fixtures/gen_fixtures.py
@@ -9,7 +9,7 @@
9# 9#
10# Edit the 'current_releases' table for each new release cycle 10# Edit the 'current_releases' table for each new release cycle
11# 11#
12# Usage: ./get_fixtures all 12# Usage: ./get_fixtures --all
13# 13#
14 14
15import os 15import os
@@ -35,19 +35,23 @@ verbose = False
35# [Codename, Yocto Project Version, Release Date, Current Version, Support Level, Poky Version, BitBake branch] 35# [Codename, Yocto Project Version, Release Date, Current Version, Support Level, Poky Version, BitBake branch]
36current_releases = [ 36current_releases = [
37 # Release slot #1 37 # Release slot #1
38 ['Kirkstone','4.0','April 2022','4.0.8 (March 2023)','Stable - Long Term Support (until Apr. 2024)','','2.0'], 38 ['Scarthgap','5.0','April 2024','5.0.0 (April 2024)','Long Term Support (until April 2028)','','2.8'],
39 # Release slot #2 'local' 39 # Release slot #2 'local'
40 ['HEAD','HEAD','','Local Yocto Project','HEAD','','HEAD'], 40 ['HEAD','HEAD','','Local Yocto Project','HEAD','','HEAD'],
41 # Release slot #3 'master' 41 # Release slot #3 'master'
42 ['Master','master','','Yocto Project master','master','','master'], 42 ['Master','master','','Yocto Project master','master','','master'],
43 # Release slot #4 43 # Release slot #4
44 ['Mickledore','4.2','April 2023','4.2.0 (April 2023)','Support for 7 months (until October 2023)','','2.4'], 44 ['Whinlatter','5.3','October 2025','5.3.0 (October 2024)','Support for 7 months (until May 2026)','','2.14'],
45# ['Langdale','4.1','October 2022','4.1.2 (January 2023)','Support for 7 months (until May 2023)','','2.2'], 45 ['Walnascar','5.2','April 2025','5.2.0 (April 2025)','Support for 7 months (until October 2025)','','2.12'],
46# ['Honister','3.4','October 2021','3.4.2 (February 2022)','Support for 7 months (until May 2022)','26.0','1.52'], 46 #['Styhead','5.1','November 2024','5.1.0 (November 2024)','Support for 7 months (until May 2025)','','2.10'],
47# ['Hardknott','3.3','April 2021','3.3.5 (March 2022)','Stable - Support for 13 months (until Apr. 2022)','25.0','1.50'], 47 #['Nanbield','4.3','November 2023','4.3.0 (November 2023)','Support for 7 months (until May 2024)','','2.6'],
48# ['Gatesgarth','3.2','Oct 2020','3.2.4 (May 2021)','EOL','24.0','1.48'], 48 #['Mickledore','4.2','April 2023','4.2.0 (April 2023)','Support for 7 months (until October 2023)','','2.4'],
49 # Optional Release slot #5 49 #['Langdale','4.1','October 2022','4.1.2 (January 2023)','Support for 7 months (until May 2023)','','2.2'],
50 ['Dunfell','3.1','April 2020','3.1.23 (February 2023)','Stable - Long Term Support (until Apr. 2024)','23.0','1.46'], 50 ['Kirkstone','4.0','April 2022','4.0.8 (March 2023)','Stable - Long Term Support (until Apr. 2024)','','2.0'],
51 #['Honister','3.4','October 2021','3.4.2 (February 2022)','Support for 7 months (until May 2022)','26.0','1.52'],
52 #['Hardknott','3.3','April 2021','3.3.5 (March 2022)','Stable - Support for 13 months (until Apr. 2022)','25.0','1.50'],
53 #['Gatesgarth','3.2','Oct 2020','3.2.4 (May 2021)','EOL','24.0','1.48'],
54 #['Dunfell','3.1','April 2020','3.1.23 (February 2023)','Stable - Long Term Support (until Apr. 2024)','23.0','1.46'],
51] 55]
52 56
53default_poky_layers = [ 57default_poky_layers = [
diff --git a/bitbake/lib/toaster/orm/fixtures/oe-core.xml b/bitbake/lib/toaster/orm/fixtures/oe-core.xml
index 950f2a98af..264231d139 100644
--- a/bitbake/lib/toaster/orm/fixtures/oe-core.xml
+++ b/bitbake/lib/toaster/orm/fixtures/oe-core.xml
@@ -8,9 +8,9 @@
8 8
9 <!-- Bitbake versions which correspond to the metadata release --> 9 <!-- Bitbake versions which correspond to the metadata release -->
10 <object model="orm.bitbakeversion" pk="1"> 10 <object model="orm.bitbakeversion" pk="1">
11 <field type="CharField" name="name">kirkstone</field> 11 <field type="CharField" name="name">scarthgap</field>
12 <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field> 12 <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field>
13 <field type="CharField" name="branch">2.0</field> 13 <field type="CharField" name="branch">2.8</field>
14 </object> 14 </object>
15 <object model="orm.bitbakeversion" pk="2"> 15 <object model="orm.bitbakeversion" pk="2">
16 <field type="CharField" name="name">HEAD</field> 16 <field type="CharField" name="name">HEAD</field>
@@ -23,23 +23,33 @@
23 <field type="CharField" name="branch">master</field> 23 <field type="CharField" name="branch">master</field>
24 </object> 24 </object>
25 <object model="orm.bitbakeversion" pk="4"> 25 <object model="orm.bitbakeversion" pk="4">
26 <field type="CharField" name="name">mickledore</field> 26 <field type="CharField" name="name">whinlatter</field>
27 <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field> 27 <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field>
28 <field type="CharField" name="branch">2.4</field> 28 <field type="CharField" name="branch">2.14</field>
29 </object> 29 </object>
30 <object model="orm.bitbakeversion" pk="5"> 30 <object model="orm.bitbakeversion" pk="5">
31 <field type="CharField" name="name">dunfell</field> 31 <field type="CharField" name="name">walnascar</field>
32 <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field>
33 <field type="CharField" name="branch">2.12</field>
34 </object>
35 <object model="orm.bitbakeversion" pk="6">
36 <field type="CharField" name="name">styhead</field>
32 <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field> 37 <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field>
33 <field type="CharField" name="branch">1.46</field> 38 <field type="CharField" name="branch">2.10</field>
39 </object>
40 <object model="orm.bitbakeversion" pk="7">
41 <field type="CharField" name="name">kirkstone</field>
42 <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field>
43 <field type="CharField" name="branch">2.0</field>
34 </object> 44 </object>
35 45
36 <!-- Releases available --> 46 <!-- Releases available -->
37 <object model="orm.release" pk="1"> 47 <object model="orm.release" pk="1">
38 <field type="CharField" name="name">kirkstone</field> 48 <field type="CharField" name="name">scarthgap</field>
39 <field type="CharField" name="description">Openembedded Kirkstone</field> 49 <field type="CharField" name="description">Openembedded Scarthgap</field>
40 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">1</field> 50 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">1</field>
41 <field type="CharField" name="branch_name">kirkstone</field> 51 <field type="CharField" name="branch_name">scarthgap</field>
42 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/?h=kirkstone\"&gt;OpenEmbedded Kirkstone&lt;/a&gt; branch.</field> 52 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/?h=scarthgap\"&gt;OpenEmbedded Scarthgap&lt;/a&gt; branch.</field>
43 </object> 53 </object>
44 <object model="orm.release" pk="2"> 54 <object model="orm.release" pk="2">
45 <field type="CharField" name="name">local</field> 55 <field type="CharField" name="name">local</field>
@@ -56,18 +66,32 @@
56 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/\"&gt;OpenEmbedded master&lt;/a&gt; branch.</field> 66 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/\"&gt;OpenEmbedded master&lt;/a&gt; branch.</field>
57 </object> 67 </object>
58 <object model="orm.release" pk="4"> 68 <object model="orm.release" pk="4">
59 <field type="CharField" name="name">mickledore</field> 69 <field type="CharField" name="name">whinlatter</field>
60 <field type="CharField" name="description">Openembedded Mickledore</field> 70 <field type="CharField" name="description">Openembedded Whinlatter</field>
61 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">4</field> 71 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">4</field>
62 <field type="CharField" name="branch_name">mickledore</field> 72 <field type="CharField" name="branch_name">whinlatter</field>
63 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/?h=mickledore\"&gt;OpenEmbedded Mickledore&lt;/a&gt; branch.</field> 73 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/?h=whinlatter\"&gt;OpenEmbedded Whinlatter&lt;/a&gt; branch.</field>
64 </object> 74 </object>
65 <object model="orm.release" pk="5"> 75 <object model="orm.release" pk="5">
66 <field type="CharField" name="name">dunfell</field> 76 <field type="CharField" name="name">walnascar</field>
67 <field type="CharField" name="description">Openembedded Dunfell</field> 77 <field type="CharField" name="description">Openembedded Walnascar</field>
68 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">5</field> 78 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">5</field>
69 <field type="CharField" name="branch_name">dunfell</field> 79 <field type="CharField" name="branch_name">walnascar</field>
70 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/?h=dunfell\"&gt;OpenEmbedded Dunfell&lt;/a&gt; branch.</field> 80 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/?h=walnascar\"&gt;OpenEmbedded Walnascar&lt;/a&gt; branch.</field>
81 </object>
82 <object model="orm.release" pk="6">
83 <field type="CharField" name="name">styhead</field>
84 <field type="CharField" name="description">Openembedded Styhead</field>
85 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">6</field>
86 <field type="CharField" name="branch_name">styhead</field>
87 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/?h=styhead\"&gt;OpenEmbedded Styhead&lt;/a&gt; branch.</field>
88 </object>
89 <object model="orm.release" pk="7">
90 <field type="CharField" name="name">kirkstone</field>
91 <field type="CharField" name="description">Openembedded Kirkstone</field>
92 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">7</field>
93 <field type="CharField" name="branch_name">kirkstone</field>
94 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"https://cgit.openembedded.org/openembedded-core/log/?h=kirkstone\"&gt;OpenEmbedded Kirkstone&lt;/a&gt; branch.</field>
71 </object> 95 </object>
72 96
73 <!-- Default layers for each release --> 97 <!-- Default layers for each release -->
@@ -91,6 +115,14 @@
91 <field rel="ManyToOneRel" to="orm.release" name="release">5</field> 115 <field rel="ManyToOneRel" to="orm.release" name="release">5</field>
92 <field type="CharField" name="layer_name">openembedded-core</field> 116 <field type="CharField" name="layer_name">openembedded-core</field>
93 </object> 117 </object>
118 <object model="orm.releasedefaultlayer" pk="6">
119 <field rel="ManyToOneRel" to="orm.release" name="release">6</field>
120 <field type="CharField" name="layer_name">openembedded-core</field>
121 </object>
122 <object model="orm.releasedefaultlayer" pk="7">
123 <field rel="ManyToOneRel" to="orm.release" name="release">7</field>
124 <field type="CharField" name="layer_name">openembedded-core</field>
125 </object>
94 126
95 127
96 <!-- Layer for the Local release --> 128 <!-- Layer for the Local release -->
diff --git a/bitbake/lib/toaster/orm/fixtures/poky.xml b/bitbake/lib/toaster/orm/fixtures/poky.xml
index 121e52fd45..6cf4f0687a 100644
--- a/bitbake/lib/toaster/orm/fixtures/poky.xml
+++ b/bitbake/lib/toaster/orm/fixtures/poky.xml
@@ -8,9 +8,9 @@
8 8
9 <!-- Bitbake versions which correspond to the metadata release --> 9 <!-- Bitbake versions which correspond to the metadata release -->
10 <object model="orm.bitbakeversion" pk="1"> 10 <object model="orm.bitbakeversion" pk="1">
11 <field type="CharField" name="name">kirkstone</field> 11 <field type="CharField" name="name">scarthgap</field>
12 <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field> 12 <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field>
13 <field type="CharField" name="branch">kirkstone</field> 13 <field type="CharField" name="branch">scarthgap</field>
14 <field type="CharField" name="dirpath">bitbake</field> 14 <field type="CharField" name="dirpath">bitbake</field>
15 </object> 15 </object>
16 <object model="orm.bitbakeversion" pk="2"> 16 <object model="orm.bitbakeversion" pk="2">
@@ -26,26 +26,38 @@
26 <field type="CharField" name="dirpath">bitbake</field> 26 <field type="CharField" name="dirpath">bitbake</field>
27 </object> 27 </object>
28 <object model="orm.bitbakeversion" pk="4"> 28 <object model="orm.bitbakeversion" pk="4">
29 <field type="CharField" name="name">mickledore</field> 29 <field type="CharField" name="name">whinlatter</field>
30 <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field> 30 <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field>
31 <field type="CharField" name="branch">mickledore</field> 31 <field type="CharField" name="branch">whinlatter</field>
32 <field type="CharField" name="dirpath">bitbake</field> 32 <field type="CharField" name="dirpath">bitbake</field>
33 </object> 33 </object>
34 <object model="orm.bitbakeversion" pk="5"> 34 <object model="orm.bitbakeversion" pk="5">
35 <field type="CharField" name="name">dunfell</field> 35 <field type="CharField" name="name">walnascar</field>
36 <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field>
37 <field type="CharField" name="branch">walnascar</field>
38 <field type="CharField" name="dirpath">bitbake</field>
39 </object>
40 <object model="orm.bitbakeversion" pk="6">
41 <field type="CharField" name="name">styhead</field>
36 <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field> 42 <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field>
37 <field type="CharField" name="branch">dunfell</field> 43 <field type="CharField" name="branch">styhead</field>
44 <field type="CharField" name="dirpath">bitbake</field>
45 </object>
46 <object model="orm.bitbakeversion" pk="7">
47 <field type="CharField" name="name">kirkstone</field>
48 <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field>
49 <field type="CharField" name="branch">kirkstone</field>
38 <field type="CharField" name="dirpath">bitbake</field> 50 <field type="CharField" name="dirpath">bitbake</field>
39 </object> 51 </object>
40 52
41 53
42 <!-- Releases available --> 54 <!-- Releases available -->
43 <object model="orm.release" pk="1"> 55 <object model="orm.release" pk="1">
44 <field type="CharField" name="name">kirkstone</field> 56 <field type="CharField" name="name">scarthgap</field>
45 <field type="CharField" name="description">Yocto Project 4.0 "Kirkstone"</field> 57 <field type="CharField" name="description">Yocto Project 5.0 "Scarthgap"</field>
46 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">1</field> 58 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">1</field>
47 <field type="CharField" name="branch_name">kirkstone</field> 59 <field type="CharField" name="branch_name">scarthgap</field>
48 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=kirkstone"&gt;Yocto Project Kirkstone branch&lt;/a&gt;.</field> 60 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=scarthgap"&gt;Yocto Project Scarthgap branch&lt;/a&gt;.</field>
49 </object> 61 </object>
50 <object model="orm.release" pk="2"> 62 <object model="orm.release" pk="2">
51 <field type="CharField" name="name">local</field> 63 <field type="CharField" name="name">local</field>
@@ -62,18 +74,32 @@
62 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/"&gt;Yocto Project Master branch&lt;/a&gt;.</field> 74 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/"&gt;Yocto Project Master branch&lt;/a&gt;.</field>
63 </object> 75 </object>
64 <object model="orm.release" pk="4"> 76 <object model="orm.release" pk="4">
65 <field type="CharField" name="name">mickledore</field> 77 <field type="CharField" name="name">whinlatter</field>
66 <field type="CharField" name="description">Yocto Project 4.2 "Mickledore"</field> 78 <field type="CharField" name="description">Yocto Project 5.3 "Whinlatter"</field>
67 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">4</field> 79 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">4</field>
68 <field type="CharField" name="branch_name">mickledore</field> 80 <field type="CharField" name="branch_name">whinlatter</field>
69 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=mickledore"&gt;Yocto Project Mickledore branch&lt;/a&gt;.</field> 81 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=whinlatter"&gt;Yocto Project Whinlatter branch&lt;/a&gt;.</field>
70 </object> 82 </object>
71 <object model="orm.release" pk="5"> 83 <object model="orm.release" pk="5">
72 <field type="CharField" name="name">dunfell</field> 84 <field type="CharField" name="name">walnascar</field>
73 <field type="CharField" name="description">Yocto Project 3.1 "Dunfell"</field> 85 <field type="CharField" name="description">Yocto Project 5.2 "Walnascar"</field>
74 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">5</field> 86 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">5</field>
75 <field type="CharField" name="branch_name">dunfell</field> 87 <field type="CharField" name="branch_name">walnascar</field>
76 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=dunfell"&gt;Yocto Project Dunfell branch&lt;/a&gt;.</field> 88 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=walnascar"&gt;Yocto Project Walnascar branch&lt;/a&gt;.</field>
89 </object>
90 <object model="orm.release" pk="6">
91 <field type="CharField" name="name">styhead</field>
92 <field type="CharField" name="description">Yocto Project 5.1 "Styhead"</field>
93 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">6</field>
94 <field type="CharField" name="branch_name">styhead</field>
95 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=styhead"&gt;Yocto Project Styhead branch&lt;/a&gt;.</field>
96 </object>
97 <object model="orm.release" pk="7">
98 <field type="CharField" name="name">kirkstone</field>
99 <field type="CharField" name="description">Yocto Project 4.0 "Kirkstone"</field>
100 <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">7</field>
101 <field type="CharField" name="branch_name">kirkstone</field>
102 <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="https://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=kirkstone"&gt;Yocto Project Kirkstone branch&lt;/a&gt;.</field>
77 </object> 103 </object>
78 104
79 <!-- Default project layers for each release --> 105 <!-- Default project layers for each release -->
@@ -137,6 +163,30 @@
137 <field rel="ManyToOneRel" to="orm.release" name="release">5</field> 163 <field rel="ManyToOneRel" to="orm.release" name="release">5</field>
138 <field type="CharField" name="layer_name">meta-yocto-bsp</field> 164 <field type="CharField" name="layer_name">meta-yocto-bsp</field>
139 </object> 165 </object>
166 <object model="orm.releasedefaultlayer" pk="16">
167 <field rel="ManyToOneRel" to="orm.release" name="release">6</field>
168 <field type="CharField" name="layer_name">openembedded-core</field>
169 </object>
170 <object model="orm.releasedefaultlayer" pk="17">
171 <field rel="ManyToOneRel" to="orm.release" name="release">6</field>
172 <field type="CharField" name="layer_name">meta-poky</field>
173 </object>
174 <object model="orm.releasedefaultlayer" pk="18">
175 <field rel="ManyToOneRel" to="orm.release" name="release">6</field>
176 <field type="CharField" name="layer_name">meta-yocto-bsp</field>
177 </object>
178 <object model="orm.releasedefaultlayer" pk="19">
179 <field rel="ManyToOneRel" to="orm.release" name="release">7</field>
180 <field type="CharField" name="layer_name">openembedded-core</field>
181 </object>
182 <object model="orm.releasedefaultlayer" pk="20">
183 <field rel="ManyToOneRel" to="orm.release" name="release">7</field>
184 <field type="CharField" name="layer_name">meta-poky</field>
185 </object>
186 <object model="orm.releasedefaultlayer" pk="21">
187 <field rel="ManyToOneRel" to="orm.release" name="release">7</field>
188 <field type="CharField" name="layer_name">meta-yocto-bsp</field>
189 </object>
140 190
141 <!-- Default layers provided by poky 191 <!-- Default layers provided by poky
142 openembedded-core 192 openembedded-core
@@ -155,7 +205,7 @@
155 <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field> 205 <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
156 <field type="IntegerField" name="layer_source">0</field> 206 <field type="IntegerField" name="layer_source">0</field>
157 <field rel="ManyToOneRel" to="orm.release" name="release">1</field> 207 <field rel="ManyToOneRel" to="orm.release" name="release">1</field>
158 <field type="CharField" name="branch">kirkstone</field> 208 <field type="CharField" name="branch">scarthgap</field>
159 <field type="CharField" name="dirpath">meta</field> 209 <field type="CharField" name="dirpath">meta</field>
160 </object> 210 </object>
161 <object model="orm.layer_version" pk="2"> 211 <object model="orm.layer_version" pk="2">
@@ -177,14 +227,28 @@
177 <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field> 227 <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
178 <field type="IntegerField" name="layer_source">0</field> 228 <field type="IntegerField" name="layer_source">0</field>
179 <field rel="ManyToOneRel" to="orm.release" name="release">4</field> 229 <field rel="ManyToOneRel" to="orm.release" name="release">4</field>
180 <field type="CharField" name="branch">mickledore</field> 230 <field type="CharField" name="branch">whinlatter</field>
181 <field type="CharField" name="dirpath">meta</field> 231 <field type="CharField" name="dirpath">meta</field>
182 </object> 232 </object>
183 <object model="orm.layer_version" pk="5"> 233 <object model="orm.layer_version" pk="5">
184 <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field> 234 <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
185 <field type="IntegerField" name="layer_source">0</field> 235 <field type="IntegerField" name="layer_source">0</field>
186 <field rel="ManyToOneRel" to="orm.release" name="release">5</field> 236 <field rel="ManyToOneRel" to="orm.release" name="release">5</field>
187 <field type="CharField" name="branch">dunfell</field> 237 <field type="CharField" name="branch">walnascar</field>
238 <field type="CharField" name="dirpath">meta</field>
239 </object>
240 <object model="orm.layer_version" pk="6">
241 <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
242 <field type="IntegerField" name="layer_source">0</field>
243 <field rel="ManyToOneRel" to="orm.release" name="release">6</field>
244 <field type="CharField" name="branch">styhead</field>
245 <field type="CharField" name="dirpath">meta</field>
246 </object>
247 <object model="orm.layer_version" pk="7">
248 <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
249 <field type="IntegerField" name="layer_source">0</field>
250 <field rel="ManyToOneRel" to="orm.release" name="release">7</field>
251 <field type="CharField" name="branch">kirkstone</field>
188 <field type="CharField" name="dirpath">meta</field> 252 <field type="CharField" name="dirpath">meta</field>
189 </object> 253 </object>
190 254
@@ -196,14 +260,14 @@
196 <field type="CharField" name="vcs_web_tree_base_url">https://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field> 260 <field type="CharField" name="vcs_web_tree_base_url">https://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
197 <field type="CharField" name="vcs_web_file_base_url">https://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field> 261 <field type="CharField" name="vcs_web_file_base_url">https://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
198 </object> 262 </object>
199 <object model="orm.layer_version" pk="6"> 263 <object model="orm.layer_version" pk="8">
200 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field> 264 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
201 <field type="IntegerField" name="layer_source">0</field> 265 <field type="IntegerField" name="layer_source">0</field>
202 <field rel="ManyToOneRel" to="orm.release" name="release">1</field> 266 <field rel="ManyToOneRel" to="orm.release" name="release">1</field>
203 <field type="CharField" name="branch">kirkstone</field> 267 <field type="CharField" name="branch">scarthgap</field>
204 <field type="CharField" name="dirpath">meta-poky</field> 268 <field type="CharField" name="dirpath">meta-poky</field>
205 </object> 269 </object>
206 <object model="orm.layer_version" pk="7"> 270 <object model="orm.layer_version" pk="9">
207 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field> 271 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
208 <field type="IntegerField" name="layer_source">0</field> 272 <field type="IntegerField" name="layer_source">0</field>
209 <field rel="ManyToOneRel" to="orm.release" name="release">2</field> 273 <field rel="ManyToOneRel" to="orm.release" name="release">2</field>
@@ -211,25 +275,39 @@
211 <field type="CharField" name="commit">HEAD</field> 275 <field type="CharField" name="commit">HEAD</field>
212 <field type="CharField" name="dirpath">meta-poky</field> 276 <field type="CharField" name="dirpath">meta-poky</field>
213 </object> 277 </object>
214 <object model="orm.layer_version" pk="8"> 278 <object model="orm.layer_version" pk="10">
215 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field> 279 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
216 <field type="IntegerField" name="layer_source">0</field> 280 <field type="IntegerField" name="layer_source">0</field>
217 <field rel="ManyToOneRel" to="orm.release" name="release">3</field> 281 <field rel="ManyToOneRel" to="orm.release" name="release">3</field>
218 <field type="CharField" name="branch">master</field> 282 <field type="CharField" name="branch">master</field>
219 <field type="CharField" name="dirpath">meta-poky</field> 283 <field type="CharField" name="dirpath">meta-poky</field>
220 </object> 284 </object>
221 <object model="orm.layer_version" pk="9"> 285 <object model="orm.layer_version" pk="11">
222 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field> 286 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
223 <field type="IntegerField" name="layer_source">0</field> 287 <field type="IntegerField" name="layer_source">0</field>
224 <field rel="ManyToOneRel" to="orm.release" name="release">4</field> 288 <field rel="ManyToOneRel" to="orm.release" name="release">4</field>
225 <field type="CharField" name="branch">mickledore</field> 289 <field type="CharField" name="branch">whinlatter</field>
226 <field type="CharField" name="dirpath">meta-poky</field> 290 <field type="CharField" name="dirpath">meta-poky</field>
227 </object> 291 </object>
228 <object model="orm.layer_version" pk="10"> 292 <object model="orm.layer_version" pk="12">
229 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field> 293 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
230 <field type="IntegerField" name="layer_source">0</field> 294 <field type="IntegerField" name="layer_source">0</field>
231 <field rel="ManyToOneRel" to="orm.release" name="release">5</field> 295 <field rel="ManyToOneRel" to="orm.release" name="release">5</field>
232 <field type="CharField" name="branch">dunfell</field> 296 <field type="CharField" name="branch">walnascar</field>
297 <field type="CharField" name="dirpath">meta-poky</field>
298 </object>
299 <object model="orm.layer_version" pk="13">
300 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
301 <field type="IntegerField" name="layer_source">0</field>
302 <field rel="ManyToOneRel" to="orm.release" name="release">6</field>
303 <field type="CharField" name="branch">styhead</field>
304 <field type="CharField" name="dirpath">meta-poky</field>
305 </object>
306 <object model="orm.layer_version" pk="14">
307 <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
308 <field type="IntegerField" name="layer_source">0</field>
309 <field rel="ManyToOneRel" to="orm.release" name="release">7</field>
310 <field type="CharField" name="branch">kirkstone</field>
233 <field type="CharField" name="dirpath">meta-poky</field> 311 <field type="CharField" name="dirpath">meta-poky</field>
234 </object> 312 </object>
235 313
@@ -241,14 +319,14 @@
241 <field type="CharField" name="vcs_web_tree_base_url">https://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field> 319 <field type="CharField" name="vcs_web_tree_base_url">https://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
242 <field type="CharField" name="vcs_web_file_base_url">https://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field> 320 <field type="CharField" name="vcs_web_file_base_url">https://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
243 </object> 321 </object>
244 <object model="orm.layer_version" pk="11"> 322 <object model="orm.layer_version" pk="15">
245 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field> 323 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
246 <field type="IntegerField" name="layer_source">0</field> 324 <field type="IntegerField" name="layer_source">0</field>
247 <field rel="ManyToOneRel" to="orm.release" name="release">1</field> 325 <field rel="ManyToOneRel" to="orm.release" name="release">1</field>
248 <field type="CharField" name="branch">kirkstone</field> 326 <field type="CharField" name="branch">scarthgap</field>
249 <field type="CharField" name="dirpath">meta-yocto-bsp</field> 327 <field type="CharField" name="dirpath">meta-yocto-bsp</field>
250 </object> 328 </object>
251 <object model="orm.layer_version" pk="12"> 329 <object model="orm.layer_version" pk="16">
252 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field> 330 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
253 <field type="IntegerField" name="layer_source">0</field> 331 <field type="IntegerField" name="layer_source">0</field>
254 <field rel="ManyToOneRel" to="orm.release" name="release">2</field> 332 <field rel="ManyToOneRel" to="orm.release" name="release">2</field>
@@ -256,25 +334,39 @@
256 <field type="CharField" name="commit">HEAD</field> 334 <field type="CharField" name="commit">HEAD</field>
257 <field type="CharField" name="dirpath">meta-yocto-bsp</field> 335 <field type="CharField" name="dirpath">meta-yocto-bsp</field>
258 </object> 336 </object>
259 <object model="orm.layer_version" pk="13"> 337 <object model="orm.layer_version" pk="17">
260 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field> 338 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
261 <field type="IntegerField" name="layer_source">0</field> 339 <field type="IntegerField" name="layer_source">0</field>
262 <field rel="ManyToOneRel" to="orm.release" name="release">3</field> 340 <field rel="ManyToOneRel" to="orm.release" name="release">3</field>
263 <field type="CharField" name="branch">master</field> 341 <field type="CharField" name="branch">master</field>
264 <field type="CharField" name="dirpath">meta-yocto-bsp</field> 342 <field type="CharField" name="dirpath">meta-yocto-bsp</field>
265 </object> 343 </object>
266 <object model="orm.layer_version" pk="14"> 344 <object model="orm.layer_version" pk="18">
267 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field> 345 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
268 <field type="IntegerField" name="layer_source">0</field> 346 <field type="IntegerField" name="layer_source">0</field>
269 <field rel="ManyToOneRel" to="orm.release" name="release">4</field> 347 <field rel="ManyToOneRel" to="orm.release" name="release">4</field>
270 <field type="CharField" name="branch">mickledore</field> 348 <field type="CharField" name="branch">whinlatter</field>
271 <field type="CharField" name="dirpath">meta-yocto-bsp</field> 349 <field type="CharField" name="dirpath">meta-yocto-bsp</field>
272 </object> 350 </object>
273 <object model="orm.layer_version" pk="15"> 351 <object model="orm.layer_version" pk="19">
274 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field> 352 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
275 <field type="IntegerField" name="layer_source">0</field> 353 <field type="IntegerField" name="layer_source">0</field>
276 <field rel="ManyToOneRel" to="orm.release" name="release">5</field> 354 <field rel="ManyToOneRel" to="orm.release" name="release">5</field>
277 <field type="CharField" name="branch">dunfell</field> 355 <field type="CharField" name="branch">walnascar</field>
356 <field type="CharField" name="dirpath">meta-yocto-bsp</field>
357 </object>
358 <object model="orm.layer_version" pk="20">
359 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
360 <field type="IntegerField" name="layer_source">0</field>
361 <field rel="ManyToOneRel" to="orm.release" name="release">6</field>
362 <field type="CharField" name="branch">styhead</field>
363 <field type="CharField" name="dirpath">meta-yocto-bsp</field>
364 </object>
365 <object model="orm.layer_version" pk="21">
366 <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
367 <field type="IntegerField" name="layer_source">0</field>
368 <field rel="ManyToOneRel" to="orm.release" name="release">7</field>
369 <field type="CharField" name="branch">kirkstone</field>
278 <field type="CharField" name="dirpath">meta-yocto-bsp</field> 370 <field type="CharField" name="dirpath">meta-yocto-bsp</field>
279 </object> 371 </object>
280</django-objects> 372</django-objects>
diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index 19c9686206..e2f488ed89 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -79,7 +79,6 @@ if 'sqlite' in settings.DATABASES['default']['ENGINE']:
79 # end of HACK 79 # end of HACK
80 80
81class GitURLValidator(validators.URLValidator): 81class GitURLValidator(validators.URLValidator):
82 import re
83 regex = re.compile( 82 regex = re.compile(
84 r'^(?:ssh|git|http|ftp)s?://' # http:// or https:// 83 r'^(?:ssh|git|http|ftp)s?://' # http:// or https://
85 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... 84 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
@@ -1500,7 +1499,7 @@ class Layer_Version(models.Model):
1500 # code lifted, with adaptations, from the layerindex-web application 1499 # code lifted, with adaptations, from the layerindex-web application
1501 # https://git.yoctoproject.org/cgit/cgit.cgi/layerindex-web/ 1500 # https://git.yoctoproject.org/cgit/cgit.cgi/layerindex-web/
1502 def _handle_url_path(self, base_url, path): 1501 def _handle_url_path(self, base_url, path):
1503 import re, posixpath 1502 import posixpath
1504 if base_url: 1503 if base_url:
1505 if self.dirpath: 1504 if self.dirpath:
1506 if path: 1505 if path:
diff --git a/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py b/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
index 393be75496..6953541ab5 100644
--- a/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
+++ b/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
@@ -27,7 +27,7 @@ from selenium.webdriver.common.by import By
27from selenium.webdriver.common.desired_capabilities import DesiredCapabilities 27from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
28from selenium.common.exceptions import NoSuchElementException, \ 28from selenium.common.exceptions import NoSuchElementException, \
29 StaleElementReferenceException, TimeoutException, \ 29 StaleElementReferenceException, TimeoutException, \
30 SessionNotCreatedException 30 SessionNotCreatedException, WebDriverException
31 31
32def create_selenium_driver(cls,browser='chrome'): 32def create_selenium_driver(cls,browser='chrome'):
33 # set default browser string based on env (if available) 33 # set default browser string based on env (if available)
@@ -90,7 +90,7 @@ class Wait(WebDriverWait):
90 Subclass of WebDriverWait with predetermined timeout and poll 90 Subclass of WebDriverWait with predetermined timeout and poll
91 frequency. Also deals with a wider variety of exceptions. 91 frequency. Also deals with a wider variety of exceptions.
92 """ 92 """
93 _TIMEOUT = 10 93 _TIMEOUT = 20
94 _POLL_FREQUENCY = 0.5 94 _POLL_FREQUENCY = 0.5
95 95
96 def __init__(self, driver, timeout=_TIMEOUT, poll=_POLL_FREQUENCY): 96 def __init__(self, driver, timeout=_TIMEOUT, poll=_POLL_FREQUENCY):
@@ -114,6 +114,9 @@ class Wait(WebDriverWait):
114 pass 114 pass
115 except StaleElementReferenceException: 115 except StaleElementReferenceException:
116 pass 116 pass
117 except WebDriverException:
118 # selenium.common.exceptions.WebDriverException: Message: unknown error: unhandled inspector error: {"code":-32000,"message":"Node with given id does not belong to the document"}
119 pass
117 120
118 time.sleep(self._poll) 121 time.sleep(self._poll)
119 if time.time() > end_time: 122 if time.time() > end_time:
@@ -183,7 +186,7 @@ class SeleniumTestCaseBase(unittest.TestCase):
183 self.driver.get(abs_url) 186 self.driver.get(abs_url)
184 187
185 try: # Ensure page is loaded before proceeding 188 try: # Ensure page is loaded before proceeding
186 self.wait_until_visible("#global-nav", poll=3) 189 self.wait_until_visible("#global-nav")
187 except NoSuchElementException: 190 except NoSuchElementException:
188 self.driver.implicitly_wait(3) 191 self.driver.implicitly_wait(3)
189 except TimeoutException: 192 except TimeoutException:
@@ -208,36 +211,43 @@ class SeleniumTestCaseBase(unittest.TestCase):
208 """ Return the element which currently has focus on the page """ 211 """ Return the element which currently has focus on the page """
209 return self.driver.switch_to.active_element 212 return self.driver.switch_to.active_element
210 213
211 def wait_until_present(self, selector, poll=0.5): 214 def wait_until_present(self, selector, timeout=Wait._TIMEOUT):
212 """ Wait until element matching CSS selector is on the page """ 215 """ Wait until element matching CSS selector is on the page """
213 is_present = lambda driver: self.find(selector) 216 is_present = lambda driver: self.find(selector)
214 msg = 'An element matching "%s" should be on the page' % selector 217 msg = 'An element matching "%s" should be on the page' % selector
215 element = Wait(self.driver, poll=poll).until(is_present, msg) 218 element = Wait(self.driver, timeout=timeout).until(is_present, msg)
216 if poll > 2:
217 time.sleep(poll) # element need more delay to be present
218 return element 219 return element
219 220
220 def wait_until_visible(self, selector, poll=1): 221 def wait_until_visible(self, selector, timeout=Wait._TIMEOUT):
221 """ Wait until element matching CSS selector is visible on the page """ 222 """ Wait until element matching CSS selector is visible on the page """
222 is_visible = lambda driver: self.find(selector).is_displayed() 223 is_visible = lambda driver: self.find(selector).is_displayed()
223 msg = 'An element matching "%s" should be visible' % selector 224 msg = 'An element matching "%s" should be visible' % selector
224 Wait(self.driver, poll=poll).until(is_visible, msg) 225 Wait(self.driver, timeout=timeout).until(is_visible, msg)
225 time.sleep(poll) # wait for visibility to settle 226 return self.find(selector)
227
228 def wait_until_not_visible(self, selector, timeout=Wait._TIMEOUT):
229 """ Wait until element matching CSS selector is not visible on the page """
230 is_visible = lambda driver: self.find(selector).is_displayed()
231 msg = 'An element matching "%s" should be visible' % selector
232 Wait(self.driver, timeout=timeout).until_not(is_visible, msg)
226 return self.find(selector) 233 return self.find(selector)
227 234
228 def wait_until_clickable(self, selector, poll=1): 235 def wait_until_clickable(self, selector, timeout=Wait._TIMEOUT):
229 """ Wait until element matching CSS selector is visible on the page """ 236 """ Wait until element matching CSS selector is visible on the page """
230 WebDriverWait( 237 WebDriverWait(self.driver, timeout=timeout).until(lambda driver: self.driver.execute_script("return jQuery.active == 0"))
231 self.driver, 238 is_clickable = lambda driver: (self.find(selector).is_displayed() and self.find(selector).is_enabled())
232 Wait._TIMEOUT, 239 msg = 'An element matching "%s" should be clickable' % selector
233 poll_frequency=poll 240 Wait(self.driver, timeout=timeout).until(is_clickable, msg)
234 ).until(
235 EC.element_to_be_clickable((By.ID, selector.removeprefix('#')
236 )
237 )
238 )
239 return self.find(selector) 241 return self.find(selector)
240 242
243 def wait_until_element_clickable(self, finder, timeout=Wait._TIMEOUT):
244 """ Wait until element is clickable """
245 WebDriverWait(self.driver, timeout=timeout).until(lambda driver: self.driver.execute_script("return jQuery.active == 0"))
246 is_clickable = lambda driver: (finder(driver).is_displayed() and finder(driver).is_enabled())
247 msg = 'A matching element never became be clickable'
248 Wait(self.driver, timeout=timeout).until(is_clickable, msg)
249 return finder(self.driver)
250
241 def wait_until_focused(self, selector): 251 def wait_until_focused(self, selector):
242 """ Wait until element matching CSS selector has focus """ 252 """ Wait until element matching CSS selector has focus """
243 is_focused = \ 253 is_focused = \
diff --git a/bitbake/lib/toaster/tests/browser/test_all_builds_page.py b/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
index b9356a0344..9ab81fb11b 100644
--- a/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
@@ -200,6 +200,7 @@ class TestAllBuildsPage(SeleniumTestCase):
200 200
201 # 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') 202 self.wait_until_visible('#allbuildstable tbody tr')
203 self.wait_until_visible('.rebuild-btn')
203 selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id 204 selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id
204 run_again_button = self.find_all(selector) 205 run_again_button = self.find_all(selector)
205 self.assertEqual(len(run_again_button), 1, 206 self.assertEqual(len(run_again_button), 1,
@@ -224,7 +225,7 @@ class TestAllBuildsPage(SeleniumTestCase):
224 225
225 url = reverse('all-builds') 226 url = reverse('all-builds')
226 self.get(url) 227 self.get(url)
227 self.wait_until_visible('#allbuildstable', poll=3) 228 self.wait_until_visible('#allbuildstable')
228 229
229 # get the project name cells from the table 230 # get the project name cells from the table
230 cells = self.find_all('#allbuildstable td[class="project"]') 231 cells = self.find_all('#allbuildstable td[class="project"]')
@@ -257,7 +258,7 @@ class TestAllBuildsPage(SeleniumTestCase):
257 258
258 url = reverse('all-builds') 259 url = reverse('all-builds')
259 self.get(url) 260 self.get(url)
260 self.wait_until_visible('#allbuildstable', poll=3) 261 self.wait_until_visible('#allbuildstable')
261 262
262 # test recent builds area for successful build 263 # test recent builds area for successful build
263 element = self._get_build_time_element(build1) 264 element = self._get_build_time_element(build1)
@@ -452,7 +453,7 @@ class TestAllBuildsPage(SeleniumTestCase):
452 def test_show_rows(row_to_show, show_row_link): 453 def test_show_rows(row_to_show, show_row_link):
453 # Check that we can show rows == row_to_show 454 # Check that we can show rows == row_to_show
454 show_row_link.select_by_value(str(row_to_show)) 455 show_row_link.select_by_value(str(row_to_show))
455 self.wait_until_visible('#allbuildstable tbody tr', poll=3) 456 self.wait_until_visible('#allbuildstable tbody tr')
456 # check at least some rows are visible 457 # check at least some rows are visible
457 self.assertTrue( 458 self.assertTrue(
458 len(self.find_all('#allbuildstable tbody tr')) > 0 459 len(self.find_all('#allbuildstable tbody tr')) > 0
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 9ed1901cc9..05e12892be 100644
--- a/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
@@ -81,7 +81,7 @@ class TestAllProjectsPage(SeleniumTestCase):
81 81
82 def _get_row_for_project(self, project_name): 82 def _get_row_for_project(self, project_name):
83 """ 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 """
84 self.wait_until_visible('#projectstable tbody tr', poll=3) 84 self.wait_until_visible('#projectstable tbody tr')
85 rows = self.find_all('#projectstable tbody tr') 85 rows = self.find_all('#projectstable tbody tr')
86 86
87 # find the row with a project name matching the one supplied 87 # find the row with a project name matching the one supplied
@@ -236,7 +236,7 @@ class TestAllProjectsPage(SeleniumTestCase):
236 self.get(url) 236 self.get(url)
237 237
238 # Chseck search box is present and works 238 # Chseck search box is present and works
239 self.wait_until_visible('#projectstable tbody tr', poll=3) 239 self.wait_until_visible('#projectstable tbody tr')
240 search_box = self.find('#search-input-projectstable') 240 search_box = self.find('#search-input-projectstable')
241 self.assertTrue(search_box.is_displayed()) 241 self.assertTrue(search_box.is_displayed())
242 242
@@ -244,7 +244,7 @@ class TestAllProjectsPage(SeleniumTestCase):
244 search_box.send_keys('test project 10') 244 search_box.send_keys('test project 10')
245 search_btn = self.find('#search-submit-projectstable') 245 search_btn = self.find('#search-submit-projectstable')
246 search_btn.click() 246 search_btn.click()
247 self.wait_until_visible('#projectstable tbody tr', poll=3) 247 self.wait_until_visible('#projectstable tbody tr')
248 rows = self.find_all('#projectstable tbody tr') 248 rows = self.find_all('#projectstable tbody tr')
249 self.assertTrue(len(rows) == 1) 249 self.assertTrue(len(rows) == 1)
250 250
@@ -290,7 +290,7 @@ class TestAllProjectsPage(SeleniumTestCase):
290 ) 290 )
291 url = reverse('all-projects') 291 url = reverse('all-projects')
292 self.get(url) 292 self.get(url)
293 self.wait_until_visible('#projectstable tbody tr', poll=3) 293 self.wait_until_visible('#projectstable tbody tr')
294 294
295 # Check edit column 295 # Check edit column
296 edit_column = self.find('#edit-columns-button') 296 edit_column = self.find('#edit-columns-button')
@@ -313,7 +313,7 @@ class TestAllProjectsPage(SeleniumTestCase):
313 def test_show_rows(row_to_show, show_row_link): 313 def test_show_rows(row_to_show, show_row_link):
314 # Check that we can show rows == row_to_show 314 # Check that we can show rows == row_to_show
315 show_row_link.select_by_value(str(row_to_show)) 315 show_row_link.select_by_value(str(row_to_show))
316 self.wait_until_visible('#projectstable tbody tr', poll=3) 316 self.wait_until_visible('#projectstable tbody tr')
317 # check at least some rows are visible 317 # check at least some rows are visible
318 self.assertTrue( 318 self.assertTrue(
319 len(self.find_all('#projectstable tbody tr')) > 0 319 len(self.find_all('#projectstable tbody tr')) > 0
@@ -321,7 +321,7 @@ class TestAllProjectsPage(SeleniumTestCase):
321 321
322 url = reverse('all-projects') 322 url = reverse('all-projects')
323 self.get(url) 323 self.get(url)
324 self.wait_until_visible('#projectstable tbody tr', poll=3) 324 self.wait_until_visible('#projectstable tbody tr')
325 325
326 show_rows = self.driver.find_elements( 326 show_rows = self.driver.find_elements(
327 By.XPATH, 327 By.XPATH,
diff --git a/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py b/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
index d838ce363a..82367108e2 100644
--- a/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
@@ -162,7 +162,7 @@ class TestBuildDashboardPage(SeleniumTestCase):
162 """ 162 """
163 url = reverse('builddashboard', args=(build.id,)) 163 url = reverse('builddashboard', args=(build.id,))
164 self.get(url) 164 self.get(url)
165 self.wait_until_visible('#global-nav', poll=3) 165 self.wait_until_visible('#global-nav')
166 166
167 def _get_build_dashboard_errors(self, build): 167 def _get_build_dashboard_errors(self, build):
168 """ 168 """
diff --git a/bitbake/lib/toaster/tests/browser/test_landing_page.py b/bitbake/lib/toaster/tests/browser/test_landing_page.py
index 8fe5fea467..210359d561 100644
--- a/bitbake/lib/toaster/tests/browser/test_landing_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_landing_page.py
@@ -34,6 +34,7 @@ class TestLandingPage(SeleniumTestCase):
34 def test_icon_info_visible_and_clickable(self): 34 def test_icon_info_visible_and_clickable(self):
35 """ Test that the information icon is visible and clickable """ 35 """ Test that the information icon is visible and clickable """
36 self.get(reverse('landing')) 36 self.get(reverse('landing'))
37 self.wait_until_visible('#toaster-version-info-sign')
37 info_sign = self.find('#toaster-version-info-sign') 38 info_sign = self.find('#toaster-version-info-sign')
38 39
39 # check that the info sign is visible 40 # check that the info sign is visible
@@ -43,6 +44,7 @@ class TestLandingPage(SeleniumTestCase):
43 # and info modal is appearing when clicking on the info sign 44 # 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_sign.click() # click on the info sign make attribute 'aria-describedby' visible
45 info_model_id = info_sign.get_attribute('aria-describedby') 46 info_model_id = info_sign.get_attribute('aria-describedby')
47 self.wait_until_visible(f'#{info_model_id}')
46 info_modal = self.find(f'#{info_model_id}') 48 info_modal = self.find(f'#{info_model_id}')
47 self.assertTrue(info_modal.is_displayed()) 49 self.assertTrue(info_modal.is_displayed())
48 self.assertTrue("Toaster version information" in info_modal.text) 50 self.assertTrue("Toaster version information" in info_modal.text)
@@ -50,6 +52,7 @@ class TestLandingPage(SeleniumTestCase):
50 def test_documentation_link_displayed(self): 52 def test_documentation_link_displayed(self):
51 """ Test that the documentation link is displayed """ 53 """ Test that the documentation link is displayed """
52 self.get(reverse('landing')) 54 self.get(reverse('landing'))
55 self.wait_until_visible('#navbar-docs')
53 documentation_link = self.find('#navbar-docs > a') 56 documentation_link = self.find('#navbar-docs > a')
54 57
55 # check that the documentation link is visible 58 # check that the documentation link is visible
@@ -65,6 +68,7 @@ class TestLandingPage(SeleniumTestCase):
65 def test_openembedded_jumbotron_link_visible_and_clickable(self): 68 def test_openembedded_jumbotron_link_visible_and_clickable(self):
66 """ Test OpenEmbedded link jumbotron is visible and clickable: """ 69 """ Test OpenEmbedded link jumbotron is visible and clickable: """
67 self.get(reverse('landing')) 70 self.get(reverse('landing'))
71 self.wait_until_visible('.jumbotron')
68 jumbotron = self.find('.jumbotron') 72 jumbotron = self.find('.jumbotron')
69 73
70 # check OpenEmbedded 74 # check OpenEmbedded
@@ -76,6 +80,7 @@ class TestLandingPage(SeleniumTestCase):
76 def test_bitbake_jumbotron_link_visible_and_clickable(self): 80 def test_bitbake_jumbotron_link_visible_and_clickable(self):
77 """ Test BitBake link jumbotron is visible and clickable: """ 81 """ Test BitBake link jumbotron is visible and clickable: """
78 self.get(reverse('landing')) 82 self.get(reverse('landing'))
83 self.wait_until_visible('.jumbotron')
79 jumbotron = self.find('.jumbotron') 84 jumbotron = self.find('.jumbotron')
80 85
81 # check BitBake 86 # check BitBake
@@ -88,6 +93,7 @@ class TestLandingPage(SeleniumTestCase):
88 def test_yoctoproject_jumbotron_link_visible_and_clickable(self): 93 def test_yoctoproject_jumbotron_link_visible_and_clickable(self):
89 """ Test Yocto Project link jumbotron is visible and clickable: """ 94 """ Test Yocto Project link jumbotron is visible and clickable: """
90 self.get(reverse('landing')) 95 self.get(reverse('landing'))
96 self.wait_until_visible('.jumbotron')
91 jumbotron = self.find('.jumbotron') 97 jumbotron = self.find('.jumbotron')
92 98
93 # check Yocto Project 99 # check Yocto Project
@@ -101,6 +107,7 @@ class TestLandingPage(SeleniumTestCase):
101 if visible and clickable 107 if visible and clickable
102 """ 108 """
103 self.get(reverse('landing')) 109 self.get(reverse('landing'))
110 self.wait_until_visible('.jumbotron')
104 jumbotron = self.find('.jumbotron') 111 jumbotron = self.find('.jumbotron')
105 112
106 # check Big magenta button 113 # check Big magenta button
@@ -119,6 +126,7 @@ class TestLandingPage(SeleniumTestCase):
119 Layer_Version.objects.create(layer=layer) 126 Layer_Version.objects.create(layer=layer)
120 127
121 self.get(reverse('landing')) 128 self.get(reverse('landing'))
129 self.wait_until_visible('.jumbotron')
122 jumbotron = self.find('.jumbotron') 130 jumbotron = self.find('.jumbotron')
123 131
124 # check Big Blue button 132 # check Big Blue button
@@ -132,6 +140,7 @@ class TestLandingPage(SeleniumTestCase):
132 def test_toaster_manual_link_visible_and_clickable(self): 140 def test_toaster_manual_link_visible_and_clickable(self):
133 """ Test Read the Toaster manual link jumbotron is visible and clickable: """ 141 """ Test Read the Toaster manual link jumbotron is visible and clickable: """
134 self.get(reverse('landing')) 142 self.get(reverse('landing'))
143 self.wait_until_visible('.jumbotron')
135 jumbotron = self.find('.jumbotron') 144 jumbotron = self.find('.jumbotron')
136 145
137 # check Read the Toaster manual 146 # check Read the Toaster manual
@@ -145,6 +154,7 @@ class TestLandingPage(SeleniumTestCase):
145 def test_contrib_to_toaster_link_visible_and_clickable(self): 154 def test_contrib_to_toaster_link_visible_and_clickable(self):
146 """ Test Contribute to Toaster link jumbotron is visible and clickable: """ 155 """ Test Contribute to Toaster link jumbotron is visible and clickable: """
147 self.get(reverse('landing')) 156 self.get(reverse('landing'))
157 self.wait_until_visible('.jumbotron')
148 jumbotron = self.find('.jumbotron') 158 jumbotron = self.find('.jumbotron')
149 159
150 # check Contribute to Toaster 160 # check Contribute to Toaster
@@ -161,6 +171,7 @@ class TestLandingPage(SeleniumTestCase):
161 => should see the landing page 171 => should see the landing page
162 """ 172 """
163 self.get(reverse('landing')) 173 self.get(reverse('landing'))
174 self.wait_until_visible('.jumbotron')
164 self.assertTrue(self.LANDING_PAGE_TITLE in self.get_page_source()) 175 self.assertTrue(self.LANDING_PAGE_TITLE in self.get_page_source())
165 176
166 def test_default_project_has_build(self): 177 def test_default_project_has_build(self):
@@ -193,6 +204,7 @@ class TestLandingPage(SeleniumTestCase):
193 user_project.save() 204 user_project.save()
194 205
195 self.get(reverse('landing')) 206 self.get(reverse('landing'))
207 self.wait_until_visible('#projectstable')
196 208
197 elements = self.find_all('#projectstable') 209 elements = self.find_all('#projectstable')
198 self.assertEqual(len(elements), 1, 'should redirect to projects') 210 self.assertEqual(len(elements), 1, 'should redirect to projects')
@@ -213,7 +225,7 @@ class TestLandingPage(SeleniumTestCase):
213 225
214 self.get(reverse('landing')) 226 self.get(reverse('landing'))
215 227
216 self.wait_until_visible("#latest-builds", poll=3) 228 self.wait_until_visible("#latest-builds")
217 elements = self.find_all('#allbuildstable') 229 elements = self.find_all('#allbuildstable')
218 self.assertEqual(len(elements), 1, 'should redirect to builds') 230 self.assertEqual(len(elements), 1, 'should redirect to builds')
219 content = self.get_page_source() 231 content = self.get_page_source()
diff --git a/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
index 5c29548b78..6abfdef699 100644
--- a/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
@@ -64,7 +64,7 @@ class TestLayerDetailsPage(SeleniumTestCase):
64 args=(self.project.pk, 64 args=(self.project.pk,
65 self.imported_layer_version.pk)) 65 self.imported_layer_version.pk))
66 66
67 def _edit_layerdetails(self): 67 def test_edit_layerdetails_page(self):
68 """ 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
69 check that the new values exist""" 69 check that the new values exist"""
70 70
@@ -100,24 +100,19 @@ class TestLayerDetailsPage(SeleniumTestCase):
100 (self.initial_values, value)) 100 (self.initial_values, value))
101 101
102 # Make sure the input visible beofre sending keys 102 # Make sure the input visible beofre sending keys
103 self.wait_until_visible("#layer-git input[type=text]") 103 self.wait_until_clickable("#layer-git input[type=text]")
104 inputs.send_keys("-edited") 104 inputs.send_keys("-edited")
105 105
106 # Save the new values 106 # Save the new values
107 for save_btn in self.find_all(".change-btn"): 107 for save_btn in self.find_all(".change-btn"):
108 save_btn.click() 108 save_btn.click()
109 109
110 try: 110 self.wait_until_visible("#save-changes-for-switch")
111 self.wait_until_visible("#save-changes-for-switch", poll=3) 111 # Ensure scrolled into view
112 btn_save_chg_for_switch = self.wait_until_clickable( 112 self.driver.execute_script('window.scrollTo({behavior: "instant", top: 0, left: 0})')
113 "#save-changes-for-switch", poll=3) 113 btn_save_chg_for_switch = self.wait_until_clickable(
114 btn_save_chg_for_switch.click() 114 "#save-changes-for-switch")
115 except ElementClickInterceptedException: 115 btn_save_chg_for_switch.click()
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 116
122 self.wait_until_visible("#edit-layer-source") 117 self.wait_until_visible("#edit-layer-source")
123 118
@@ -147,17 +142,10 @@ class TestLayerDetailsPage(SeleniumTestCase):
147 new_dir = "/home/test/my-meta-dir" 142 new_dir = "/home/test/my-meta-dir"
148 dir_input.send_keys(new_dir) 143 dir_input.send_keys(new_dir)
149 144
150 try: 145 self.wait_until_visible("#save-changes-for-switch")
151 self.wait_until_visible("#save-changes-for-switch", poll=3) 146 btn_save_chg_for_switch = self.wait_until_clickable(
152 btn_save_chg_for_switch = self.wait_until_clickable( 147 "#save-changes-for-switch")
153 "#save-changes-for-switch", poll=3) 148 btn_save_chg_for_switch.click()
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 149
162 self.wait_until_visible("#edit-layer-source") 150 self.wait_until_visible("#edit-layer-source")
163 151
@@ -168,12 +156,6 @@ class TestLayerDetailsPage(SeleniumTestCase):
168 "Expected %s in the dir value for layer directory" % 156 "Expected %s in the dir value for layer directory" %
169 new_dir) 157 new_dir)
170 158
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 159
178 def test_delete_layer(self): 160 def test_delete_layer(self):
179 """ Delete the layer """ 161 """ Delete the layer """
@@ -211,6 +193,7 @@ class TestLayerDetailsPage(SeleniumTestCase):
211 self.get(self.url) 193 self.get(self.url)
212 194
213 # Add the layer 195 # Add the layer
196 self.wait_until_clickable("#add-remove-layer-btn")
214 self.click("#add-remove-layer-btn") 197 self.click("#add-remove-layer-btn")
215 198
216 notification = self.wait_until_visible("#change-notification-msg") 199 notification = self.wait_until_visible("#change-notification-msg")
@@ -218,12 +201,17 @@ class TestLayerDetailsPage(SeleniumTestCase):
218 expected_text = "You have added 1 layer to your project: %s" % \ 201 expected_text = "You have added 1 layer to your project: %s" % \
219 self.imported_layer_version.layer.name 202 self.imported_layer_version.layer.name
220 203
221 self.assertTrue(expected_text in notification.text, 204 self.assertIn(expected_text, notification.text,
222 "Expected notification text %s not found was " 205 "Expected notification text %s not found was "
223 " \"%s\" instead" % 206 " \"%s\" instead" %
224 (expected_text, notification.text)) 207 (expected_text, notification.text))
225 208
209 hide_button = self.find('#hide-alert')
210 hide_button.click()
211 self.wait_until_not_visible('#change-notification')
212
226 # Remove the layer 213 # Remove the layer
214 self.wait_until_clickable("#add-remove-layer-btn")
227 self.click("#add-remove-layer-btn") 215 self.click("#add-remove-layer-btn")
228 216
229 notification = self.wait_until_visible("#change-notification-msg") 217 notification = self.wait_until_visible("#change-notification-msg")
@@ -231,7 +219,7 @@ class TestLayerDetailsPage(SeleniumTestCase):
231 expected_text = "You have removed 1 layer from your project: %s" % \ 219 expected_text = "You have removed 1 layer from your project: %s" % \
232 self.imported_layer_version.layer.name 220 self.imported_layer_version.layer.name
233 221
234 self.assertTrue(expected_text in notification.text, 222 self.assertIn(expected_text, notification.text,
235 "Expected notification text %s not found was " 223 "Expected notification text %s not found was "
236 " \"%s\" instead" % 224 " \"%s\" instead" %
237 (expected_text, notification.text)) 225 (expected_text, notification.text))
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 9f0b6397fe..bf0304dbec 100644
--- a/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
@@ -90,7 +90,7 @@ class TestNewCustomImagePage(SeleniumTestCase):
90 """ 90 """
91 url = reverse('newcustomimage', args=(self.project.id,)) 91 url = reverse('newcustomimage', args=(self.project.id,))
92 self.get(url) 92 self.get(url)
93 self.wait_until_visible('#global-nav', poll=3) 93 self.wait_until_visible('#global-nav')
94 94
95 self.click('button[data-recipe="%s"]' % self.recipe.id) 95 self.click('button[data-recipe="%s"]' % self.recipe.id)
96 96
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 458bb6538d..e50f236c32 100644
--- a/bitbake/lib/toaster/tests/browser/test_new_project_page.py
+++ b/bitbake/lib/toaster/tests/browser/test_new_project_page.py
@@ -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 self.wait_until_visible('#new-project-name', poll=3) 50 self.wait_until_visible('#new-project-name')
51 self.enter_text('#new-project-name', project_name) 51 self.enter_text('#new-project-name', project_name)
52 52
53 select = Select(self.find('#projectversion')) 53 select = Select(self.find('#projectversion'))
@@ -58,7 +58,7 @@ class TestNewProjectPage(SeleniumTestCase):
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( 60 element = self.wait_until_visible(
61 '#project-created-notification', poll=3) 61 '#project-created-notification')
62 62
63 self.assertTrue(project_name in element.text, 63 self.assertTrue(project_name in element.text,
64 "New project name not in new project notification") 64 "New project name not in new project notification")
@@ -79,7 +79,7 @@ class TestNewProjectPage(SeleniumTestCase):
79 79
80 url = reverse('newproject') 80 url = reverse('newproject')
81 self.get(url) 81 self.get(url)
82 self.wait_until_visible('#new-project-name', poll=3) 82 self.wait_until_visible('#new-project-name')
83 83
84 self.enter_text('#new-project-name', project_name) 84 self.enter_text('#new-project-name', project_name)
85 85
@@ -89,12 +89,10 @@ class TestNewProjectPage(SeleniumTestCase):
89 radio = self.driver.find_element(By.ID, 'type-new') 89 radio = self.driver.find_element(By.ID, 'type-new')
90 radio.click() 90 radio.click()
91 91
92 self.click("#create-project-button") 92 self.wait_until_visible('#hint-error-project-name')
93
94 self.wait_until_present('#hint-error-project-name', poll=3)
95 element = self.find('#hint-error-project-name') 93 element = self.find('#hint-error-project-name')
96 94
97 self.assertTrue(("Project names must be unique" in element.text), 95 self.assertIn("Project names must be unique", element.text,
98 "Did not find unique project name error message") 96 "Did not find unique project name error message")
99 97
100 # Try and click it anyway, if it submits we'll have a new project in 98 # Try and click it anyway, if it submits we'll have a new project in
diff --git a/bitbake/lib/toaster/tests/builds/buildtest.py b/bitbake/lib/toaster/tests/builds/buildtest.py
index cacfccd4d3..e54d561334 100644
--- a/bitbake/lib/toaster/tests/builds/buildtest.py
+++ b/bitbake/lib/toaster/tests/builds/buildtest.py
@@ -128,7 +128,7 @@ class BuildTest(unittest.TestCase):
128 if os.environ.get("TOASTER_TEST_USE_SSTATE_MIRROR"): 128 if os.environ.get("TOASTER_TEST_USE_SSTATE_MIRROR"):
129 ProjectVariable.objects.get_or_create( 129 ProjectVariable.objects.get_or_create(
130 name="SSTATE_MIRRORS", 130 name="SSTATE_MIRRORS",
131 value="file://.* http://cdn.jsdelivr.net/yocto/sstate/all/PATH;downloadfilename=PATH", 131 value="file://.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH",
132 project=project) 132 project=project)
133 133
134 ProjectTarget.objects.create(project=project, 134 ProjectTarget.objects.create(project=project,
diff --git a/bitbake/lib/toaster/tests/functional/functional_helpers.py b/bitbake/lib/toaster/tests/functional/functional_helpers.py
index 7c20437d14..e28f2024f5 100644
--- a/bitbake/lib/toaster/tests/functional/functional_helpers.py
+++ b/bitbake/lib/toaster/tests/functional/functional_helpers.py
@@ -12,9 +12,12 @@ import logging
12import subprocess 12import subprocess
13import signal 13import signal
14import re 14import re
15import requests
15 16
17from django.urls import reverse
16from tests.browser.selenium_helpers_base import SeleniumTestCaseBase 18from tests.browser.selenium_helpers_base import SeleniumTestCaseBase
17from selenium.webdriver.common.by import By 19from selenium.webdriver.common.by import By
20from selenium.webdriver.support.select import Select
18from selenium.common.exceptions import NoSuchElementException 21from selenium.common.exceptions import NoSuchElementException
19 22
20logger = logging.getLogger("toaster") 23logger = logging.getLogger("toaster")
@@ -136,3 +139,86 @@ class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
136 except NoSuchElementException: 139 except NoSuchElementException:
137 return False 140 return False
138 return element 141 return element
142
143 def create_new_project(
144 self,
145 project_name,
146 release,
147 release_title,
148 merge_toaster_settings,
149 ):
150 """ Create/Test new project using:
151 - Project Name: Any string
152 - Release: Any string
153 - Merge Toaster settings: True or False
154 """
155
156 # Obtain a CSRF token from a suitable URL
157 projs = requests.get(self.live_server_url + reverse('newproject'))
158 csrftoken = projs.cookies.get('csrftoken')
159
160 # Use the projects typeahead to find out if the project already exists
161 req = requests.get(self.live_server_url + reverse('xhr_projectstypeahead'), {'search': project_name, 'format' : 'json'})
162 data = req.json()
163 # Delete any existing projects
164 for result in data['results']:
165 del_url = reverse('xhr_project', args=(result['id'],))
166 del_response = requests.delete(self.live_server_url + del_url, cookies={'csrftoken': csrftoken}, headers={'X-CSRFToken': csrftoken})
167 self.assertEqual(del_response.status_code, 200)
168
169 self.get(reverse('newproject'))
170 self.wait_until_visible('#new-project-name')
171 self.driver.find_element(By.ID,
172 "new-project-name").send_keys(project_name)
173
174 select = Select(self.find('#projectversion'))
175 select.select_by_value(release)
176
177 # check merge toaster settings
178 checkbox = self.find('.checkbox-mergeattr')
179 if merge_toaster_settings:
180 if not checkbox.is_selected():
181 checkbox.click()
182 else:
183 if checkbox.is_selected():
184 checkbox.click()
185
186 self.wait_until_clickable('#create-project-button')
187
188 self.driver.find_element(By.ID, "create-project-button").click()
189
190 element = self.wait_until_visible('#project-created-notification')
191 self.assertTrue(
192 self.element_exists('#project-created-notification'),
193 f"Project:{project_name} creation notification not shown"
194 )
195 self.assertTrue(
196 project_name in element.text,
197 f"New project name:{project_name} not in new project notification"
198 )
199
200 # Use the projects typeahead again to check the project now exists
201 req = requests.get(self.live_server_url + reverse('xhr_projectstypeahead'), {'search': project_name, 'format' : 'json'})
202 data = req.json()
203 self.assertGreater(len(data['results']), 0, f"New project:{project_name} not found in database")
204
205 project_id = data['results'][0]['id']
206
207 self.wait_until_visible('#project-release-title')
208
209 # check release
210 if release_title is not None:
211 self.assertTrue(re.search(
212 release_title,
213 self.driver.find_element(By.XPATH,
214 "//span[@id='project-release-title']"
215 ).text),
216 'The project release is not defined')
217
218 return project_id
219
220 def load_projects_page_helper(self):
221 self.wait_until_present('#projectstable')
222 # Need to wait for some data in the table too
223 self.wait_until_present('td[class="updated"]')
224
diff --git a/bitbake/lib/toaster/tests/functional/test_create_new_project.py b/bitbake/lib/toaster/tests/functional/test_create_new_project.py
index 94d90459e1..66213c736e 100644
--- a/bitbake/lib/toaster/tests/functional/test_create_new_project.py
+++ b/bitbake/lib/toaster/tests/functional/test_create_new_project.py
@@ -11,67 +11,10 @@ import pytest
11from django.urls import reverse 11from django.urls import reverse
12from selenium.webdriver.support.select import Select 12from selenium.webdriver.support.select import Select
13from tests.functional.functional_helpers import SeleniumFunctionalTestCase 13from tests.functional.functional_helpers import SeleniumFunctionalTestCase
14from orm.models import Project
15from selenium.webdriver.common.by import By 14from selenium.webdriver.common.by import By
16 15
17
18@pytest.mark.django_db
19@pytest.mark.order("last")
20class TestCreateNewProject(SeleniumFunctionalTestCase): 16class TestCreateNewProject(SeleniumFunctionalTestCase):
21 17
22 def _create_test_new_project(
23 self,
24 project_name,
25 release,
26 release_title,
27 merge_toaster_settings,
28 ):
29 """ Create/Test new project using:
30 - Project Name: Any string
31 - Release: Any string
32 - Merge Toaster settings: True or False
33 """
34 self.get(reverse('newproject'))
35 self.wait_until_visible('#new-project-name', poll=3)
36 self.driver.find_element(By.ID,
37 "new-project-name").send_keys(project_name)
38
39 select = Select(self.find('#projectversion'))
40 select.select_by_value(release)
41
42 # check merge toaster settings
43 checkbox = self.find('.checkbox-mergeattr')
44 if merge_toaster_settings:
45 if not checkbox.is_selected():
46 checkbox.click()
47 else:
48 if checkbox.is_selected():
49 checkbox.click()
50
51 self.driver.find_element(By.ID, "create-project-button").click()
52
53 element = self.wait_until_visible('#project-created-notification', poll=3)
54 self.assertTrue(
55 self.element_exists('#project-created-notification'),
56 f"Project:{project_name} creation notification not shown"
57 )
58 self.assertTrue(
59 project_name in element.text,
60 f"New project name:{project_name} not in new project notification"
61 )
62 self.assertTrue(
63 Project.objects.filter(name=project_name).count(),
64 f"New project:{project_name} not found in database"
65 )
66
67 # check release
68 self.assertTrue(re.search(
69 release_title,
70 self.driver.find_element(By.XPATH,
71 "//span[@id='project-release-title']"
72 ).text),
73 'The project release is not defined')
74
75 def test_create_new_project_master(self): 18 def test_create_new_project_master(self):
76 """ Test create new project using: 19 """ Test create new project using:
77 - Project Name: Any string 20 - Project Name: Any string
@@ -81,43 +24,43 @@ class TestCreateNewProject(SeleniumFunctionalTestCase):
81 release = '3' 24 release = '3'
82 release_title = 'Yocto Project master' 25 release_title = 'Yocto Project master'
83 project_name = 'projectmaster' 26 project_name = 'projectmaster'
84 self._create_test_new_project( 27 self.create_new_project(
85 project_name, 28 project_name,
86 release, 29 release,
87 release_title, 30 release_title,
88 False, 31 False,
89 ) 32 )
90 33
91 def test_create_new_project_kirkstone(self): 34 def test_create_new_project_scarthgap(self):
92 """ Test create new project using: 35 """ Test create new project using:
93 - Project Name: Any string 36 - Project Name: Any string
94 - Release: Yocto Project 4.0 "Kirkstone" (option value: 1) 37 - Release: Yocto Project 5.0 "Scarthgap" (option value: 1)
95 - Merge Toaster settings: True 38 - Merge Toaster settings: True
96 """ 39 """
97 release = '1' 40 release = '1'
98 release_title = 'Yocto Project 4.0 "Kirkstone"' 41 release_title = 'Yocto Project 5.0 "Scarthgap"'
99 project_name = 'projectkirkstone' 42 project_name = 'projectscarthgap'
100 self._create_test_new_project( 43 self.create_new_project(
101 project_name, 44 project_name,
102 release, 45 release,
103 release_title, 46 release_title,
104 True, 47 True,
105 ) 48 )
106 49
107 def test_create_new_project_dunfell(self): 50 def test_create_new_project_kirkstone(self):
108 """ Test create new project using: 51 """ Test create new project using:
109 - Project Name: Any string 52 - Project Name: Any string
110 - Release: Yocto Project 3.1 "Dunfell" (option value: 5) 53 - Release: Yocto Project 4.0 "Kirkstone" (option value: 6)
111 - Merge Toaster settings: False 54 - Merge Toaster settings: True
112 """ 55 """
113 release = '5' 56 release = '7'
114 release_title = 'Yocto Project 3.1 "Dunfell"' 57 release_title = 'Yocto Project 4.0 "Kirkstone"'
115 project_name = 'projectdunfell' 58 project_name = 'projectkirkstone'
116 self._create_test_new_project( 59 self.create_new_project(
117 project_name, 60 project_name,
118 release, 61 release,
119 release_title, 62 release_title,
120 False, 63 True,
121 ) 64 )
122 65
123 def test_create_new_project_local(self): 66 def test_create_new_project_local(self):
@@ -129,7 +72,7 @@ class TestCreateNewProject(SeleniumFunctionalTestCase):
129 release = '2' 72 release = '2'
130 release_title = 'Local Yocto Project' 73 release_title = 'Local Yocto Project'
131 project_name = 'projectlocal' 74 project_name = 'projectlocal'
132 self._create_test_new_project( 75 self.create_new_project(
133 project_name, 76 project_name,
134 release, 77 release,
135 release_title, 78 release_title,
@@ -172,8 +115,10 @@ class TestCreateNewProject(SeleniumFunctionalTestCase):
172 "import-project-dir").send_keys(wrong_path) 115 "import-project-dir").send_keys(wrong_path)
173 self.driver.find_element(By.ID, "create-project-button").click() 116 self.driver.find_element(By.ID, "create-project-button").click()
174 117
118 self.wait_until_visible('.alert-danger')
119
175 # check error message 120 # check error message
176 self.assertTrue(self.element_exists('.alert-danger'), 121 self.assertTrue(self.element_exists('.alert-danger'),
177 'Allert message not shown') 122 'Alert message not shown')
178 self.assertTrue(wrong_path in self.find('.alert-danger').text, 123 self.assertTrue(wrong_path in self.find('.alert-danger').text,
179 "Wrong path not in alert message") 124 "Wrong path not in alert message")
diff --git a/bitbake/lib/toaster/tests/functional/test_functional_basic.py b/bitbake/lib/toaster/tests/functional/test_functional_basic.py
index e4070fbb88..d5c9708617 100644
--- a/bitbake/lib/toaster/tests/functional/test_functional_basic.py
+++ b/bitbake/lib/toaster/tests/functional/test_functional_basic.py
@@ -17,145 +17,132 @@ from selenium.webdriver.common.by import By
17from tests.functional.utils import get_projectId_from_url 17from tests.functional.utils import get_projectId_from_url
18 18
19 19
20@pytest.mark.django_db
21@pytest.mark.order("second_to_last")
22class FuntionalTestBasic(SeleniumFunctionalTestCase): 20class FuntionalTestBasic(SeleniumFunctionalTestCase):
23 """Basic functional tests for Toaster""" 21 """Basic functional tests for Toaster"""
24 project_id = None 22 project_id = None
23 project_url = None
25 24
26 def setUp(self): 25 def setUp(self):
27 super(FuntionalTestBasic, self).setUp() 26 super(FuntionalTestBasic, self).setUp()
28 if not FuntionalTestBasic.project_id: 27 if not FuntionalTestBasic.project_id:
29 self._create_slenium_project() 28 FuntionalTestBasic.project_id = self.create_new_project('selenium-project', '3', None, False)
30 current_url = self.driver.current_url
31 FuntionalTestBasic.project_id = get_projectId_from_url(current_url)
32
33# testcase (1514)
34 def _create_slenium_project(self):
35 project_name = 'selenium-project'
36 self.get(reverse('newproject'))
37 self.wait_until_visible('#new-project-name', poll=3)
38 self.driver.find_element(By.ID, "new-project-name").send_keys(project_name)
39 self.driver.find_element(By.ID, 'projectversion').click()
40 self.driver.find_element(By.ID, "create-project-button").click()
41 element = self.wait_until_visible('#project-created-notification', poll=10)
42 self.assertTrue(self.element_exists('#project-created-notification'),'Project creation notification not shown')
43 self.assertTrue(project_name in element.text,
44 "New project name not in new project notification")
45 self.assertTrue(Project.objects.filter(name=project_name).count(),
46 "New project not found in database")
47 return Project.objects.last().id
48 29
49 # testcase (1515) 30 # testcase (1515)
50 def test_verify_left_bar_menu(self): 31 def test_verify_left_bar_menu(self):
51 self.get(reverse('all-projects')) 32 self.get(reverse('all-projects'))
52 self.wait_until_present('#projectstable', poll=10) 33 self.load_projects_page_helper()
53 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click() 34 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
54 self.wait_until_present('#config-nav', poll=10) 35 self.wait_until_present('#config-nav')
55 self.assertTrue(self.element_exists('#config-nav'),'Configuration Tab does not exist') 36 self.assertTrue(self.element_exists('#config-nav'),'Configuration Tab does not exist')
56 project_URL=self.get_URL() 37 project_URL=self.get_URL()
57 self.driver.find_element(By.XPATH, '//a[@href="'+project_URL+'"]').click() 38 self.driver.find_element(By.XPATH, '//a[@href="'+project_URL+'"]').click()
58 self.wait_until_present('#config-nav', poll=10)
59 39
60 try: 40 try:
41 self.wait_until_present('#config-nav')
61 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'customimages/"'+"]").click() 42 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'customimages/"'+"]").click()
62 self.wait_until_present('#config-nav', poll=10) 43 self.wait_until_present('#filter-modal-customimagestable')
63 self.assertTrue(re.search("Custom images",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'Custom images information is not loading properly')
64 except: 44 except:
65 self.fail(msg='No Custom images tab available') 45 self.fail(msg='No Custom images tab available')
46 self.assertTrue(re.search("Custom images",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'Custom images information is not loading properly')
66 47
67 try: 48 try:
68 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'images/"'+"]").click() 49 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'images/"'+"]").click()
69 self.wait_until_present('#config-nav', poll=10) 50 self.wait_until_present('#filter-modal-imagerecipestable')
70 self.assertTrue(re.search("Compatible image recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible image recipes information is not loading properly')
71 except: 51 except:
72 self.fail(msg='No Compatible image tab available') 52 self.fail(msg='No Compatible image tab available')
53 self.assertTrue(re.search("Compatible image recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible image recipes information is not loading properly')
73 54
74 try: 55 try:
75 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'softwarerecipes/"'+"]").click() 56 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'softwarerecipes/"'+"]").click()
76 self.wait_until_present('#config-nav', poll=10) 57 self.wait_until_present('#filter-modal-softwarerecipestable')
77 self.assertTrue(re.search("Compatible software recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible software recipe information is not loading properly')
78 except: 58 except:
79 self.fail(msg='No Compatible software recipe tab available') 59 self.fail(msg='No Compatible software recipe tab available')
60 self.assertTrue(re.search("Compatible software recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible software recipe information is not loading properly')
80 61
81 try: 62 try:
82 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'machines/"'+"]").click() 63 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'machines/"'+"]").click()
83 self.wait_until_present('#config-nav', poll=10) 64 self.wait_until_present('#filter-modal-machinestable')
84 self.assertTrue(re.search("Compatible machines",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible machine information is not loading properly')
85 except: 65 except:
86 self.fail(msg='No Compatible machines tab available') 66 self.fail(msg='No Compatible machines tab available')
67 self.assertTrue(re.search("Compatible machines",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible machine information is not loading properly')
87 68
88 try: 69 try:
89 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'layers/"'+"]").click() 70 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'layers/"'+"]").click()
90 self.wait_until_present('#config-nav', poll=10) 71 self.wait_until_present('#filter-modal-layerstable')
91 self.assertTrue(re.search("Compatible layers",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible layer information is not loading properly')
92 except: 72 except:
93 self.fail(msg='No Compatible layers tab available') 73 self.fail(msg='No Compatible layers tab available')
74 self.assertTrue(re.search("Compatible layers",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible layer information is not loading properly')
94 75
95 try: 76 try:
96 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'configuration"'+"]").click() 77 self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'configuration"'+"]").click()
97 self.wait_until_present('#config-nav', poll=10) 78 self.wait_until_present('#configvar-list')
98 self.assertTrue(re.search("Bitbake variables",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Bitbake variables information is not loading properly')
99 except: 79 except:
100 self.fail(msg='No Bitbake variables tab available') 80 self.fail(msg='No Bitbake variables tab available')
81 self.assertTrue(re.search("Bitbake variables",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Bitbake variables information is not loading properly')
101 82
102# testcase (1516) 83# testcase (1516)
103 def test_review_configuration_information(self): 84 def test_review_configuration_information(self):
104 self.get(reverse('all-projects')) 85 self.get(reverse('all-projects'))
105 self.wait_until_present('#projectstable', poll=10) 86 self.load_projects_page_helper()
106 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click() 87 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
107 project_URL=self.get_URL() 88 project_URL=self.get_URL()
108 self.wait_until_present('#config-nav', poll=10) 89
90 # Machine section of page
91 self.wait_until_visible('#machine-section')
92 self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
93 self.assertTrue(re.search("qemux86-64",self.driver.find_element(By.XPATH, "//span[@id='project-machine-name']").text),'The machine type is not assigned')
109 try: 94 try:
110 self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
111 self.assertTrue(re.search("qemux86-64",self.driver.find_element(By.XPATH, "//span[@id='project-machine-name']").text),'The machine type is not assigned')
112 self.driver.find_element(By.XPATH, "//span[@id='change-machine-toggle']").click() 95 self.driver.find_element(By.XPATH, "//span[@id='change-machine-toggle']").click()
113 self.wait_until_visible('#select-machine-form', poll=10) 96 self.wait_until_visible('#select-machine-form')
114 self.wait_until_visible('#cancel-machine-change', poll=10) 97 self.wait_until_visible('#cancel-machine-change')
115 self.driver.find_element(By.XPATH, "//form[@id='select-machine-form']/a[@id='cancel-machine-change']").click() 98 self.driver.find_element(By.XPATH, "//form[@id='select-machine-form']/a[@id='cancel-machine-change']").click()
116 except: 99 except:
117 self.fail(msg='The machine information is wrong in the configuration page') 100 self.fail(msg='The machine information is wrong in the configuration page')
118 101
102 # Most built recipes section
103 self.wait_until_visible('#no-most-built')
119 try: 104 try:
120 self.driver.find_element(By.ID, 'no-most-built') 105 self.driver.find_element(By.ID, 'no-most-built')
121 except: 106 except:
122 self.fail(msg='No Most built information in project detail page') 107 self.fail(msg='No Most built information in project detail page')
123 108
124 try: 109 # Project Release title
125 self.assertTrue(re.search("Yocto Project master",self.driver.find_element(By.XPATH, "//span[@id='project-release-title']").text),'The project release is not defined') 110 self.assertTrue(re.search("Yocto Project master",self.driver.find_element(By.XPATH, "//span[@id='project-release-title']").text), 'The project release is not defined in the project detail page')
126 except:
127 self.fail(msg='No project release title information in project detail page')
128 111
112 # List of layers in project
113 self.wait_until_visible('#layer-container')
114 self.driver.find_element(By.XPATH, "//div[@id='layer-container']")
115 self.assertTrue(re.search("3",self.driver.find_element(By.ID, "project-layers-count").text),'There should be 3 layers listed in the layer count')
129 try: 116 try:
130 self.driver.find_element(By.XPATH, "//div[@id='layer-container']")
131 self.assertTrue(re.search("3",self.driver.find_element(By.ID, "project-layers-count").text),'There should be 3 layers listed in the layer count')
132 layer_list = self.driver.find_element(By.ID, "layers-in-project-list") 117 layer_list = self.driver.find_element(By.ID, "layers-in-project-list")
133 layers = layer_list.find_elements(By.TAG_NAME, "li") 118 layers = layer_list.find_elements(By.TAG_NAME, "li")
134 for layer in layers:
135 if re.match ("openembedded-core",layer.text):
136 print ("openembedded-core layer is a default layer in the project configuration")
137 elif re.match ("meta-poky",layer.text):
138 print ("meta-poky layer is a default layer in the project configuration")
139 elif re.match ("meta-yocto-bsp",layer.text):
140 print ("meta-yocto-bsp is a default layer in the project configuratoin")
141 else:
142 self.fail(msg='default layers are missing from the project configuration')
143 except: 119 except:
144 self.fail(msg='No Layer information in project detail page') 120 self.fail(msg='No Layer information in project detail page')
145 121
122 for layer in layers:
123 if re.match ("openembedded-core", layer.text):
124 print ("openembedded-core layer is a default layer in the project configuration")
125 elif re.match ("meta-poky", layer.text):
126 print ("meta-poky layer is a default layer in the project configuration")
127 elif re.match ("meta-yocto-bsp", layer.text):
128 print ("meta-yocto-bsp is a default layer in the project configuratoin")
129 else:
130 self.fail(msg='default layers are missing from the project configuration')
131
146# testcase (1517) 132# testcase (1517)
147 def test_verify_machine_information(self): 133 def test_verify_machine_information(self):
148 self.get(reverse('all-projects')) 134 self.get(reverse('all-projects'))
149 self.wait_until_present('#projectstable', poll=10) 135 self.load_projects_page_helper()
150 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click() 136 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
151 self.wait_until_present('#config-nav', poll=10)
152 137
138 self.wait_until_visible('#machine-section')
139 self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
140 self.wait_until_visible('#project-machine-name')
141 self.assertTrue(re.search("qemux86-64",self.driver.find_element(By.ID, "project-machine-name").text),'The machine type is not assigned')
153 try: 142 try:
154 self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
155 self.assertTrue(re.search("qemux86-64",self.driver.find_element(By.ID, "project-machine-name").text),'The machine type is not assigned')
156 self.driver.find_element(By.ID, "change-machine-toggle").click() 143 self.driver.find_element(By.ID, "change-machine-toggle").click()
157 self.wait_until_visible('#select-machine-form', poll=10) 144 self.wait_until_visible('#select-machine-form')
158 self.wait_until_visible('#cancel-machine-change', poll=10) 145 self.wait_until_visible('#cancel-machine-change')
159 self.driver.find_element(By.ID, "cancel-machine-change").click() 146 self.driver.find_element(By.ID, "cancel-machine-change").click()
160 except: 147 except:
161 self.fail(msg='The machine information is wrong in the configuration page') 148 self.fail(msg='The machine information is wrong in the configuration page')
@@ -163,83 +150,95 @@ class FuntionalTestBasic(SeleniumFunctionalTestCase):
163# testcase (1518) 150# testcase (1518)
164 def test_verify_most_built_recipes_information(self): 151 def test_verify_most_built_recipes_information(self):
165 self.get(reverse('all-projects')) 152 self.get(reverse('all-projects'))
166 self.wait_until_present('#projectstable', poll=10) 153 self.load_projects_page_helper()
167 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click() 154 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
168 self.wait_until_present('#config-nav', poll=10) 155 self.wait_until_present('#config-nav')
169 project_URL=self.get_URL() 156 project_URL=self.get_URL()
157
158 self.wait_until_visible('#no-most-built')
159 self.assertTrue(re.search("You haven't built any recipes yet",self.driver.find_element(By.ID, "no-most-built").text),'Default message of no builds is not present')
170 try: 160 try:
171 self.assertTrue(re.search("You haven't built any recipes yet",self.driver.find_element(By.ID, "no-most-built").text),'Default message of no builds is not present')
172 self.driver.find_element(By.XPATH, "//div[@id='no-most-built']/p/a[@href="+'"'+project_URL+'images/"'+"]").click() 161 self.driver.find_element(By.XPATH, "//div[@id='no-most-built']/p/a[@href="+'"'+project_URL+'images/"'+"]").click()
173 self.wait_until_present('#config-nav', poll=10)
174 self.assertTrue(re.search("Compatible image recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Choose a recipe to build link is not working properly')
175 except: 162 except:
176 self.fail(msg='No Most built information in project detail page') 163 self.fail(msg='No Most built information in project detail page')
164 self.wait_until_visible('#config-nav')
165 self.assertTrue(re.search("Compatible image recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Choose a recipe to build link is not working properly')
177 166
178# testcase (1519) 167# testcase (1519)
179 def test_verify_project_release_information(self): 168 def test_verify_project_release_information(self):
180 self.get(reverse('all-projects')) 169 self.get(reverse('all-projects'))
181 self.wait_until_present('#projectstable', poll=10) 170 self.load_projects_page_helper()
182 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click() 171 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
183 self.wait_until_present('#config-nav', poll=10) 172 self.wait_until_visible('#project-release-title')
184 173 self.assertTrue(re.search("Yocto Project master",self.driver.find_element(By.ID, "project-release-title").text), 'No project release title information in project detail page')
185 try:
186 self.assertTrue(re.search("Yocto Project master",self.driver.find_element(By.ID, "project-release-title").text),'The project release is not defined')
187 except:
188 self.fail(msg='No project release title information in project detail page')
189 174
190# testcase (1520) 175# testcase (1520)
191 def test_verify_layer_information(self): 176 def test_verify_layer_information(self):
192 self.get(reverse('all-projects')) 177 self.get(reverse('all-projects'))
193 self.wait_until_present('#projectstable', poll=10) 178 self.load_projects_page_helper()
194 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click() 179 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
195 self.wait_until_present('#config-nav', poll=10) 180 self.wait_until_present('#config-nav')
196 project_URL=self.get_URL() 181 project_URL=self.get_URL()
182 self.wait_until_visible('#layer-container')
183 self.driver.find_element(By.XPATH, "//div[@id='layer-container']")
184 self.wait_until_visible('#project-layers-count')
185 self.assertTrue(re.search("3",self.driver.find_element(By.ID, "project-layers-count").text),'There should be 3 layers listed in the layer count')
186
197 try: 187 try:
198 self.driver.find_element(By.XPATH, "//div[@id='layer-container']")
199 self.assertTrue(re.search("3",self.driver.find_element(By.ID, "project-layers-count").text),'There should be 3 layers listed in the layer count')
200 layer_list = self.driver.find_element(By.ID, "layers-in-project-list") 188 layer_list = self.driver.find_element(By.ID, "layers-in-project-list")
201 layers = layer_list.find_elements(By.TAG_NAME, "li") 189 layers = layer_list.find_elements(By.TAG_NAME, "li")
190 except:
191 self.fail(msg='No Layer information in project detail page')
202 192
203 for layer in layers: 193 for layer in layers:
204 if re.match ("openembedded-core",layer.text): 194 if re.match ("openembedded-core",layer.text):
205 print ("openembedded-core layer is a default layer in the project configuration") 195 print ("openembedded-core layer is a default layer in the project configuration")
206 elif re.match ("meta-poky",layer.text): 196 elif re.match ("meta-poky",layer.text):
207 print ("meta-poky layer is a default layer in the project configuration") 197 print ("meta-poky layer is a default layer in the project configuration")
208 elif re.match ("meta-yocto-bsp",layer.text): 198 elif re.match ("meta-yocto-bsp",layer.text):
209 print ("meta-yocto-bsp is a default layer in the project configuratoin") 199 print ("meta-yocto-bsp is a default layer in the project configuratoin")
210 else: 200 else:
211 self.fail(msg='default layers are missing from the project configuration') 201 self.fail(msg='default layers are missing from the project configuration')
212 202
203 try:
213 self.driver.find_element(By.XPATH, "//input[@id='layer-add-input']") 204 self.driver.find_element(By.XPATH, "//input[@id='layer-add-input']")
214 self.driver.find_element(By.XPATH, "//button[@id='add-layer-btn']") 205 self.driver.find_element(By.XPATH, "//button[@id='add-layer-btn']")
215 self.driver.find_element(By.XPATH, "//div[@id='layer-container']/form[@class='form-inline']/p/a[@id='view-compatible-layers']") 206 self.driver.find_element(By.XPATH, "//div[@id='layer-container']/form[@class='form-inline']/p/a[@id='view-compatible-layers']")
216 self.driver.find_element(By.XPATH, "//div[@id='layer-container']/form[@class='form-inline']/p/a[@href="+'"'+project_URL+'importlayer"'+"]") 207 self.driver.find_element(By.XPATH, "//div[@id='layer-container']/form[@class='form-inline']/p/a[@href="+'"'+project_URL+'importlayer"'+"]")
217 except: 208 except:
218 self.fail(msg='No Layer information in project detail page') 209 self.fail(msg='Layer configuration controls missing')
219 210
220# testcase (1521) 211# testcase (1521)
221 def test_verify_project_detail_links(self): 212 def test_verify_project_detail_links(self):
222 self.get(reverse('all-projects')) 213 self.get(reverse('all-projects'))
223 self.wait_until_present('#projectstable', poll=10) 214 self.load_projects_page_helper()
224 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click() 215 self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
225 self.wait_until_present('#config-nav', poll=10) 216 self.wait_until_present('#config-nav')
226 project_URL=self.get_URL() 217 project_URL=self.get_URL()
227 self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").click() 218 self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").click()
228 self.wait_until_present('#config-nav', poll=10) 219 self.wait_until_visible('#topbar-configuration-tab')
229 self.assertTrue(re.search("Configuration",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").text), 'Configuration tab in project topbar is misspelled') 220 self.assertTrue(re.search("Configuration",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").text), 'Configuration tab in project topbar is misspelled')
230 221
231 try: 222 try:
232 self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").click() 223 self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").click()
233 self.wait_until_visible('#project-topbar', poll=10) 224 except:
234 self.assertTrue(re.search("Builds",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").text), 'Builds tab in project topbar is misspelled') 225 self.fail(msg='Builds tab information is not present')
226
227 self.wait_until_visible('#project-topbar')
228 self.assertTrue(re.search("Builds",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").text), 'Builds tab in project topbar is misspelled')
229 try:
235 self.driver.find_element(By.XPATH, "//div[@id='empty-state-projectbuildstable']") 230 self.driver.find_element(By.XPATH, "//div[@id='empty-state-projectbuildstable']")
236 except: 231 except:
237 self.fail(msg='Builds tab information is not present') 232 self.fail(msg='Builds tab information is not present')
238 233
239 try: 234 try:
240 self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").click() 235 self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").click()
241 self.wait_until_visible('#project-topbar', poll=10) 236 except:
242 self.assertTrue(re.search("Import layer",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").text), 'Import layer tab in project topbar is misspelled') 237 self.fail(msg='Import layer tab not loading properly')
238
239 self.wait_until_visible('#project-topbar')
240 self.assertTrue(re.search("Import layer",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").text), 'Import layer tab in project topbar is misspelled')
241 try:
243 self.driver.find_element(By.XPATH, "//fieldset[@id='repo-select']") 242 self.driver.find_element(By.XPATH, "//fieldset[@id='repo-select']")
244 self.driver.find_element(By.XPATH, "//fieldset[@id='git-repo']") 243 self.driver.find_element(By.XPATH, "//fieldset[@id='git-repo']")
245 except: 244 except:
@@ -247,11 +246,12 @@ class FuntionalTestBasic(SeleniumFunctionalTestCase):
247 246
248 try: 247 try:
249 self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").click() 248 self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").click()
250 self.wait_until_visible('#project-topbar', poll=10)
251 self.assertTrue(re.search("New custom image",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").text), 'New custom image tab in project topbar is misspelled')
252 self.assertTrue(re.search("Select the image recipe you want to customise",self.driver.find_element(By.XPATH, "//div[@class='col-md-12']/h2").text),'The new custom image tab is not loading correctly')
253 except: 249 except:
254 self.fail(msg='New custom image tab not loading properly') 250 self.fail(msg='New custom image tab not loading properly')
255 251
252 self.wait_until_visible('#project-topbar')
253 self.assertTrue(re.search("New custom image",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").text), 'New custom image tab in project topbar is misspelled')
254 self.assertTrue(re.search("Select the image recipe you want to customise",self.driver.find_element(By.XPATH, "//div[@class='col-md-12']/h2").text),'The new custom image tab is not loading correctly')
255
256 256
257 257
diff --git a/bitbake/lib/toaster/tests/functional/test_project_config.py b/bitbake/lib/toaster/tests/functional/test_project_config.py
index dbee36aa4e..fcb1bc3284 100644
--- a/bitbake/lib/toaster/tests/functional/test_project_config.py
+++ b/bitbake/lib/toaster/tests/functional/test_project_config.py
@@ -7,7 +7,6 @@
7# 7#
8 8
9import string 9import string
10import random
11import pytest 10import pytest
12from django.urls import reverse 11from django.urls import reverse
13from selenium.webdriver import Keys 12from selenium.webdriver import Keys
@@ -18,9 +17,6 @@ from selenium.webdriver.common.by import By
18 17
19from .utils import get_projectId_from_url 18from .utils import get_projectId_from_url
20 19
21
22@pytest.mark.django_db
23@pytest.mark.order("last")
24class TestProjectConfig(SeleniumFunctionalTestCase): 20class TestProjectConfig(SeleniumFunctionalTestCase):
25 project_id = None 21 project_id = None
26 PROJECT_NAME = 'TestProjectConfig' 22 PROJECT_NAME = 'TestProjectConfig'
@@ -28,42 +24,6 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
28 INVALID_PATH_CHAR_TEXT = 'The directory path cannot include spaces or ' \ 24 INVALID_PATH_CHAR_TEXT = 'The directory path cannot include spaces or ' \
29 'any of these characters' 25 'any of these characters'
30 26
31 def _create_project(self, project_name):
32 """ Create/Test new project using:
33 - Project Name: Any string
34 - Release: Any string
35 - Merge Toaster settings: True or False
36 """
37 self.get(reverse('newproject'))
38 self.wait_until_visible('#new-project-name', poll=2)
39 self.find("#new-project-name").send_keys(project_name)
40 select = Select(self.find("#projectversion"))
41 select.select_by_value('3')
42
43 # check merge toaster settings
44 checkbox = self.find('.checkbox-mergeattr')
45 if not checkbox.is_selected():
46 checkbox.click()
47
48 if self.PROJECT_NAME != 'TestProjectConfig':
49 # Reset project name if it's not the default one
50 self.PROJECT_NAME = 'TestProjectConfig'
51
52 self.find("#create-project-button").click()
53
54 try:
55 self.wait_until_visible('#hint-error-project-name', poll=2)
56 url = reverse('project', args=(TestProjectConfig.project_id, ))
57 self.get(url)
58 self.wait_until_visible('#config-nav', poll=3)
59 except TimeoutException:
60 self.wait_until_visible('#config-nav', poll=3)
61
62 def _random_string(self, length):
63 return ''.join(
64 random.choice(string.ascii_letters) for _ in range(length)
65 )
66
67 def _get_config_nav_item(self, index): 27 def _get_config_nav_item(self, index):
68 config_nav = self.find('#config-nav') 28 config_nav = self.find('#config-nav')
69 return config_nav.find_elements(By.TAG_NAME, 'li')[index] 29 return config_nav.find_elements(By.TAG_NAME, 'li')[index]
@@ -72,16 +32,14 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
72 """ Navigate to project BitBake variables page """ 32 """ Navigate to project BitBake variables page """
73 # check if the menu is displayed 33 # check if the menu is displayed
74 if TestProjectConfig.project_id is None: 34 if TestProjectConfig.project_id is None:
75 self._create_project(project_name=self._random_string(10)) 35 TestProjectConfig.project_id = self.create_new_project(self.PROJECT_NAME, '3', None, True)
76 current_url = self.driver.current_url 36
77 TestProjectConfig.project_id = get_projectId_from_url(current_url) 37 url = reverse('projectconf', args=(TestProjectConfig.project_id,))
78 else: 38 self.get(url)
79 url = reverse('projectconf', args=(TestProjectConfig.project_id,)) 39 self.wait_until_visible('#config-nav')
80 self.get(url)
81 self.wait_until_visible('#config-nav', poll=3)
82 bbv_page_link = self._get_config_nav_item(9) 40 bbv_page_link = self._get_config_nav_item(9)
83 bbv_page_link.click() 41 bbv_page_link.click()
84 self.wait_until_visible('#config-nav', poll=3) 42 self.wait_until_visible('#config-nav')
85 43
86 def test_no_underscore_iamgefs_type(self): 44 def test_no_underscore_iamgefs_type(self):
87 """ 45 """
@@ -90,13 +48,13 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
90 self._navigate_bbv_page() 48 self._navigate_bbv_page()
91 imagefs_type = "foo_bar" 49 imagefs_type = "foo_bar"
92 50
93 self.wait_until_visible('#change-image_fstypes-icon', poll=2) 51 self.wait_until_visible('#change-image_fstypes-icon')
94 52
95 self.click('#change-image_fstypes-icon') 53 self.click('#change-image_fstypes-icon')
96 54
97 self.enter_text('#new-imagefs_types', imagefs_type) 55 self.enter_text('#new-imagefs_types', imagefs_type)
98 56
99 element = self.wait_until_visible('#hintError-image-fs_type', poll=2) 57 element = self.wait_until_visible('#hintError-image-fs_type')
100 58
101 self.assertTrue(("A valid image type cannot include underscores" in element.text), 59 self.assertTrue(("A valid image type cannot include underscores" in element.text),
102 "Did not find underscore error message") 60 "Did not find underscore error message")
@@ -110,7 +68,7 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
110 68
111 imagefs_type = "btrfs" 69 imagefs_type = "btrfs"
112 70
113 self.wait_until_visible('#change-image_fstypes-icon', poll=2) 71 self.wait_until_visible('#change-image_fstypes-icon')
114 72
115 self.click('#change-image_fstypes-icon') 73 self.click('#change-image_fstypes-icon')
116 74
@@ -129,22 +87,20 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
129 """ 87 """
130 self._navigate_bbv_page() 88 self._navigate_bbv_page()
131 89
132 self.wait_until_visible('#change-image_fstypes-icon', poll=2) 90 self.wait_until_visible('#change-image_fstypes-icon')
133
134 self.click('#change-image_fstypes-icon') 91 self.click('#change-image_fstypes-icon')
135 92
136 checkboxes_selector = '.fs-checkbox-fstypes' 93 checkboxes_selector = '.fs-checkbox-fstypes'
137 94
138 self.wait_until_visible(checkboxes_selector, poll=2) 95 self.wait_until_visible(checkboxes_selector)
139 checkboxes = self.find_all(checkboxes_selector) 96 checkboxes = self.find_all(checkboxes_selector)
140 97
141 for checkbox in checkboxes: 98 for checkbox in checkboxes:
142 if checkbox.get_attribute("value") == "cpio": 99 if checkbox.get_attribute("value") == "cpio":
143 checkbox.click() 100 checkbox.click()
101 self.wait_until_visible('#new-imagefs_types')
144 element = self.driver.find_element(By.ID, 'new-imagefs_types') 102 element = self.driver.find_element(By.ID, 'new-imagefs_types')
145 103
146 self.wait_until_visible('#new-imagefs_types', poll=2)
147
148 self.assertTrue(("cpio" in element.get_attribute('value'), 104 self.assertTrue(("cpio" in element.get_attribute('value'),
149 "Imagefs not added into the textbox")) 105 "Imagefs not added into the textbox"))
150 checkbox.click() 106 checkbox.click()
@@ -160,20 +116,19 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
160 116
161 # activate the input to edit download dir 117 # activate the input to edit download dir
162 try: 118 try:
163 change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2) 119 change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon')
164 except TimeoutException: 120 except TimeoutException:
165 # If download dir is not displayed, test is skipped 121 # If download dir is not displayed, test is skipped
166 change_dl_dir_btn = None 122 change_dl_dir_btn = None
167 123
168 if change_dl_dir_btn: 124 if change_dl_dir_btn:
169 change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2)
170 change_dl_dir_btn.click() 125 change_dl_dir_btn.click()
171 126
172 # downloads dir path doesn't start with / or ${...} 127 # downloads dir path doesn't start with / or ${...}
173 input_field = self.wait_until_visible('#new-dl_dir', poll=2) 128 input_field = self.wait_until_visible('#new-dl_dir')
174 input_field.clear() 129 input_field.clear()
175 self.enter_text('#new-dl_dir', 'home/foo') 130 self.enter_text('#new-dl_dir', 'home/foo')
176 element = self.wait_until_visible('#hintError-initialChar-dl_dir', poll=2) 131 element = self.wait_until_visible('#hintError-initialChar-dl_dir')
177 132
178 msg = 'downloads directory path starts with invalid character but ' \ 133 msg = 'downloads directory path starts with invalid character but ' \
179 'treated as valid' 134 'treated as valid'
@@ -183,7 +138,7 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
183 self.driver.find_element(By.ID, 'new-dl_dir').clear() 138 self.driver.find_element(By.ID, 'new-dl_dir').clear()
184 self.enter_text('#new-dl_dir', '/foo/bar a') 139 self.enter_text('#new-dl_dir', '/foo/bar a')
185 140
186 element = self.wait_until_visible('#hintError-dl_dir', poll=2) 141 element = self.wait_until_visible('#hintError-dl_dir')
187 msg = 'downloads directory path characters invalid but treated as valid' 142 msg = 'downloads directory path characters invalid but treated as valid'
188 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) 143 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
189 144
@@ -191,7 +146,7 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
191 self.driver.find_element(By.ID,'new-dl_dir').clear() 146 self.driver.find_element(By.ID,'new-dl_dir').clear()
192 self.enter_text('#new-dl_dir', '${TOPDIR}/down foo') 147 self.enter_text('#new-dl_dir', '${TOPDIR}/down foo')
193 148
194 element = self.wait_until_visible('#hintError-dl_dir', poll=2) 149 element = self.wait_until_visible('#hintError-dl_dir')
195 msg = 'downloads directory path characters invalid but treated as valid' 150 msg = 'downloads directory path characters invalid but treated as valid'
196 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) 151 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
197 152
@@ -219,10 +174,7 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
219 self._navigate_bbv_page() 174 self._navigate_bbv_page()
220 175
221 try: 176 try:
222 btn_chg_sstate_dir = self.wait_until_visible( 177 btn_chg_sstate_dir = self.wait_until_visible('#change-sstate_dir-icon')
223 '#change-sstate_dir-icon',
224 poll=2
225 )
226 self.click('#change-sstate_dir-icon') 178 self.click('#change-sstate_dir-icon')
227 except TimeoutException: 179 except TimeoutException:
228 # If sstate_dir is not displayed, test is skipped 180 # If sstate_dir is not displayed, test is skipped
@@ -230,10 +182,10 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
230 182
231 if btn_chg_sstate_dir: # Skip continuation if sstate_dir is not displayed 183 if btn_chg_sstate_dir: # Skip continuation if sstate_dir is not displayed
232 # path doesn't start with / or ${...} 184 # path doesn't start with / or ${...}
233 input_field = self.wait_until_visible('#new-sstate_dir', poll=2) 185 input_field = self.wait_until_visible('#new-sstate_dir')
234 input_field.clear() 186 input_field.clear()
235 self.enter_text('#new-sstate_dir', 'home/foo') 187 self.enter_text('#new-sstate_dir', 'home/foo')
236 element = self.wait_until_visible('#hintError-initialChar-sstate_dir', poll=2) 188 element = self.wait_until_visible('#hintError-initialChar-sstate_dir')
237 189
238 msg = 'sstate directory path starts with invalid character but ' \ 190 msg = 'sstate directory path starts with invalid character but ' \
239 'treated as valid' 191 'treated as valid'
@@ -243,7 +195,7 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
243 self.driver.find_element(By.ID, 'new-sstate_dir').clear() 195 self.driver.find_element(By.ID, 'new-sstate_dir').clear()
244 self.enter_text('#new-sstate_dir', '/foo/bar a') 196 self.enter_text('#new-sstate_dir', '/foo/bar a')
245 197
246 element = self.wait_until_visible('#hintError-sstate_dir', poll=2) 198 element = self.wait_until_visible('#hintError-sstate_dir')
247 msg = 'sstate directory path characters invalid but treated as valid' 199 msg = 'sstate directory path characters invalid but treated as valid'
248 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) 200 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
249 201
@@ -251,7 +203,7 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
251 self.driver.find_element(By.ID,'new-sstate_dir').clear() 203 self.driver.find_element(By.ID,'new-sstate_dir').clear()
252 self.enter_text('#new-sstate_dir', '${TOPDIR}/down foo') 204 self.enter_text('#new-sstate_dir', '${TOPDIR}/down foo')
253 205
254 element = self.wait_until_visible('#hintError-sstate_dir', poll=2) 206 element = self.wait_until_visible('#hintError-sstate_dir')
255 msg = 'sstate directory path characters invalid but treated as valid' 207 msg = 'sstate directory path characters invalid but treated as valid'
256 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) 208 self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
257 209
@@ -275,13 +227,14 @@ class TestProjectConfig(SeleniumFunctionalTestCase):
275 var_name, field, btn_id, input_id, value, save_btn, *_ = kwargs.values() 227 var_name, field, btn_id, input_id, value, save_btn, *_ = kwargs.values()
276 """ Change bitbake variable value """ 228 """ Change bitbake variable value """
277 self._navigate_bbv_page() 229 self._navigate_bbv_page()
278 self.wait_until_visible(f'#{btn_id}', poll=2) 230 self.wait_until_visible(f'#{btn_id}')
279 if kwargs.get('new_variable'): 231 if kwargs.get('new_variable'):
280 self.find(f"#{btn_id}").clear() 232 self.find(f"#{btn_id}").clear()
281 self.enter_text(f"#{btn_id}", f"{var_name}") 233 self.enter_text(f"#{btn_id}", f"{var_name}")
282 else: 234 else:
283 self.click(f'#{btn_id}') 235 self.click(f'#{btn_id}')
284 self.wait_until_visible(f'#{input_id}', poll=2) 236
237 self.wait_until_visible(f'#{input_id}')
285 238
286 if kwargs.get('is_select'): 239 if kwargs.get('is_select'):
287 select = Select(self.find(f'#{input_id}')) 240 select = Select(self.find(f'#{input_id}'))
diff --git a/bitbake/lib/toaster/tests/functional/test_project_page.py b/bitbake/lib/toaster/tests/functional/test_project_page.py
index adbe3587e4..429d86feba 100644
--- a/bitbake/lib/toaster/tests/functional/test_project_page.py
+++ b/bitbake/lib/toaster/tests/functional/test_project_page.py
@@ -7,8 +7,8 @@
7# 7#
8 8
9import os 9import os
10import random
11import string 10import string
11import time
12from unittest import skip 12from unittest import skip
13import pytest 13import pytest
14from django.urls import reverse 14from django.urls import reverse
@@ -22,58 +22,17 @@ from selenium.webdriver.common.by import By
22 22
23from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled 23from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled
24 24
25 25class TestProjectPageBase(SeleniumFunctionalTestCase):
26@pytest.mark.django_db
27@pytest.mark.order("last")
28class TestProjectPage(SeleniumFunctionalTestCase):
29 project_id = None 26 project_id = None
30 PROJECT_NAME = 'TestProjectPage' 27 PROJECT_NAME = 'TestProjectPage'
31 28
32 def _create_project(self, project_name):
33 """ Create/Test new project using:
34 - Project Name: Any string
35 - Release: Any string
36 - Merge Toaster settings: True or False
37 """
38 self.get(reverse('newproject'))
39 self.wait_until_visible('#new-project-name')
40 self.find("#new-project-name").send_keys(project_name)
41 select = Select(self.find("#projectversion"))
42 select.select_by_value('3')
43
44 # check merge toaster settings
45 checkbox = self.find('.checkbox-mergeattr')
46 if not checkbox.is_selected():
47 checkbox.click()
48
49 if self.PROJECT_NAME != 'TestProjectPage':
50 # Reset project name if it's not the default one
51 self.PROJECT_NAME = 'TestProjectPage'
52
53 self.find("#create-project-button").click()
54
55 try:
56 self.wait_until_visible('#hint-error-project-name')
57 url = reverse('project', args=(TestProjectPage.project_id, ))
58 self.get(url)
59 self.wait_until_visible('#config-nav', poll=3)
60 except TimeoutException:
61 self.wait_until_visible('#config-nav', poll=3)
62
63 def _random_string(self, length):
64 return ''.join(
65 random.choice(string.ascii_letters) for _ in range(length)
66 )
67
68 def _navigate_to_project_page(self): 29 def _navigate_to_project_page(self):
69 # Navigate to project page 30 # Navigate to project page
70 if TestProjectPage.project_id is None: 31 if TestProjectPageBase.project_id is None:
71 self._create_project(project_name=self._random_string(10)) 32 TestProjectPageBase.project_id = self.create_new_project(self.PROJECT_NAME, '3', None, True)
72 current_url = self.driver.current_url 33
73 TestProjectPage.project_id = get_projectId_from_url(current_url) 34 url = reverse('project', args=(TestProjectPageBase.project_id,))
74 else: 35 self.get(url)
75 url = reverse('project', args=(TestProjectPage.project_id,))
76 self.get(url)
77 self.wait_until_visible('#config-nav') 36 self.wait_until_visible('#config-nav')
78 37
79 def _get_create_builds(self, **kwargs): 38 def _get_create_builds(self, **kwargs):
@@ -81,14 +40,14 @@ class TestProjectPage(SeleniumFunctionalTestCase):
81 # parameters for builds to associate with the projects 40 # parameters for builds to associate with the projects
82 now = timezone.now() 41 now = timezone.now()
83 self.project1_build_success = { 42 self.project1_build_success = {
84 'project': Project.objects.get(id=TestProjectPage.project_id), 43 'project': Project.objects.get(id=TestProjectPageBase.project_id),
85 'started_on': now, 44 'started_on': now,
86 'completed_on': now, 45 'completed_on': now,
87 'outcome': Build.SUCCEEDED 46 'outcome': Build.SUCCEEDED
88 } 47 }
89 48
90 self.project1_build_failure = { 49 self.project1_build_failure = {
91 'project': Project.objects.get(id=TestProjectPage.project_id), 50 'project': Project.objects.get(id=TestProjectPageBase.project_id),
92 'started_on': now, 51 'started_on': now,
93 'completed_on': now, 52 'completed_on': now,
94 'outcome': Build.FAILED 53 'outcome': Build.FAILED
@@ -133,7 +92,8 @@ class TestProjectPage(SeleniumFunctionalTestCase):
133 list_check_box_id: list 92 list_check_box_id: list
134 ): 93 ):
135 # Check edit column 94 # Check edit column
136 edit_column = self.find(f'#{edit_btn_id}') 95 finder = lambda driver: self.find(f'#{edit_btn_id}')
96 edit_column = self.wait_until_element_clickable(finder)
137 self.assertTrue(edit_column.is_displayed()) 97 self.assertTrue(edit_column.is_displayed())
138 edit_column.click() 98 edit_column.click()
139 # Check dropdown is visible 99 # Check dropdown is visible
@@ -192,7 +152,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
192 def test_show_rows(row_to_show, show_row_link): 152 def test_show_rows(row_to_show, show_row_link):
193 # Check that we can show rows == row_to_show 153 # Check that we can show rows == row_to_show
194 show_row_link.select_by_value(str(row_to_show)) 154 show_row_link.select_by_value(str(row_to_show))
195 self.wait_until_visible(f'#{table_selector} tbody tr', poll=3) 155 self.wait_until_visible(f'#{table_selector} tbody tr')
196 # check at least some rows are visible 156 # check at least some rows are visible
197 self.assertTrue( 157 self.assertTrue(
198 len(self.find_all(f'#{table_selector} tbody tr')) > 0 158 len(self.find_all(f'#{table_selector} tbody tr')) > 0
@@ -222,34 +182,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
222 rows = self.find_all(f'#{table_selector} tbody tr') 182 rows = self.find_all(f'#{table_selector} tbody tr')
223 self.assertTrue(len(rows) > 0) 183 self.assertTrue(len(rows) > 0)
224 184
225 def test_create_project(self): 185class TestProjectPage(TestProjectPageBase):
226 """ Create/Test new project using:
227 - Project Name: Any string
228 - Release: Any string
229 - Merge Toaster settings: True or False
230 """
231 self._create_project(project_name=self.PROJECT_NAME)
232
233 def test_image_recipe_editColumn(self):
234 """ Test the edit column feature in image recipe table on project page """
235 self._get_create_builds(success=10, failure=10)
236
237 url = reverse('projectimagerecipes', args=(TestProjectPage.project_id,))
238 self.get(url)
239 self.wait_until_present('#imagerecipestable tbody tr')
240
241 column_list = [
242 'get_description_or_summary', 'layer_version__get_vcs_reference',
243 'layer_version__layer__name', 'license', 'recipe-file', 'section',
244 'version'
245 ]
246
247 # Check that we can hide the edit column
248 self._mixin_test_table_edit_column(
249 'imagerecipestable',
250 'edit-columns-button',
251 [f'checkbox-{column}' for column in column_list]
252 )
253 186
254 def test_page_header_on_project_page(self): 187 def test_page_header_on_project_page(self):
255 """ Check page header in project page: 188 """ Check page header in project page:
@@ -272,8 +205,8 @@ class TestProjectPage(SeleniumFunctionalTestCase):
272 logo_img = logo.find_element(By.TAG_NAME, 'img') 205 logo_img = logo.find_element(By.TAG_NAME, 'img')
273 self.assertTrue(logo_img.is_displayed(), 206 self.assertTrue(logo_img.is_displayed(),
274 'Logo of Yocto project not found') 207 'Logo of Yocto project not found')
275 self.assertTrue( 208 self.assertIn(
276 '/static/img/logo.png' in str(logo_img.get_attribute('src')), 209 '/static/img/logo.png', str(logo_img.get_attribute('src')),
277 'Logo of Yocto project not found' 210 'Logo of Yocto project not found'
278 ) 211 )
279 # "Toaster"+" Information icon", clickable 212 # "Toaster"+" Information icon", clickable
@@ -282,34 +215,34 @@ class TestProjectPage(SeleniumFunctionalTestCase):
282 "//div[@class='toaster-navbar-brand']//a[@class='brand']", 215 "//div[@class='toaster-navbar-brand']//a[@class='brand']",
283 ) 216 )
284 self.assertTrue(toaster.is_displayed(), 'Toaster not found') 217 self.assertTrue(toaster.is_displayed(), 'Toaster not found')
285 self.assertTrue(toaster.text == 'Toaster') 218 self.assertEqual(toaster.text, 'Toaster')
286 info_sign = self.find('.glyphicon-info-sign') 219 info_sign = self.find('.glyphicon-info-sign')
287 self.assertTrue(info_sign.is_displayed()) 220 self.assertTrue(info_sign.is_displayed())
288 221
289 # "Server Icon" + "All builds" 222 # "Server Icon" + "All builds"
290 all_builds = self.find('#navbar-all-builds') 223 all_builds = self.find('#navbar-all-builds')
291 all_builds_link = all_builds.find_element(By.TAG_NAME, 'a') 224 all_builds_link = all_builds.find_element(By.TAG_NAME, 'a')
292 self.assertTrue("All builds" in all_builds_link.text) 225 self.assertIn("All builds", all_builds_link.text)
293 self.assertTrue( 226 self.assertIn(
294 '/toastergui/builds/' in str(all_builds_link.get_attribute('href')) 227 '/toastergui/builds/', str(all_builds_link.get_attribute('href'))
295 ) 228 )
296 server_icon = all_builds.find_element(By.TAG_NAME, 'i') 229 server_icon = all_builds.find_element(By.TAG_NAME, 'i')
297 self.assertTrue( 230 self.assertEqual(
298 server_icon.get_attribute('class') == 'glyphicon glyphicon-tasks' 231 server_icon.get_attribute('class'), 'glyphicon glyphicon-tasks'
299 ) 232 )
300 self.assertTrue(server_icon.is_displayed()) 233 self.assertTrue(server_icon.is_displayed())
301 234
302 # "Directory Icon" + "All projects" 235 # "Directory Icon" + "All projects"
303 all_projects = self.find('#navbar-all-projects') 236 all_projects = self.find('#navbar-all-projects')
304 all_projects_link = all_projects.find_element(By.TAG_NAME, 'a') 237 all_projects_link = all_projects.find_element(By.TAG_NAME, 'a')
305 self.assertTrue("All projects" in all_projects_link.text) 238 self.assertIn("All projects", all_projects_link.text)
306 self.assertTrue( 239 self.assertIn(
307 '/toastergui/projects/' in str(all_projects_link.get_attribute( 240 '/toastergui/projects/', str(all_projects_link.get_attribute(
308 'href')) 241 'href'))
309 ) 242 )
310 dir_icon = all_projects.find_element(By.TAG_NAME, 'i') 243 dir_icon = all_projects.find_element(By.TAG_NAME, 'i')
311 self.assertTrue( 244 self.assertEqual(
312 dir_icon.get_attribute('class') == 'icon-folder-open' 245 dir_icon.get_attribute('class'), 'icon-folder-open'
313 ) 246 )
314 self.assertTrue(dir_icon.is_displayed()) 247 self.assertTrue(dir_icon.is_displayed())
315 248
@@ -317,23 +250,23 @@ class TestProjectPage(SeleniumFunctionalTestCase):
317 toaster_docs_link = self.find('#navbar-docs') 250 toaster_docs_link = self.find('#navbar-docs')
318 toaster_docs_link_link = toaster_docs_link.find_element(By.TAG_NAME, 251 toaster_docs_link_link = toaster_docs_link.find_element(By.TAG_NAME,
319 'a') 252 'a')
320 self.assertTrue("Documentation" in toaster_docs_link_link.text) 253 self.assertIn("Documentation", toaster_docs_link_link.text)
321 self.assertTrue( 254 self.assertEqual(
322 toaster_docs_link_link.get_attribute('href') == 'http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual' 255 toaster_docs_link_link.get_attribute('href'), 'http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual'
323 ) 256 )
324 book_icon = toaster_docs_link.find_element(By.TAG_NAME, 'i') 257 book_icon = toaster_docs_link.find_element(By.TAG_NAME, 'i')
325 self.assertTrue( 258 self.assertEqual(
326 book_icon.get_attribute('class') == 'glyphicon glyphicon-book' 259 book_icon.get_attribute('class'), 'glyphicon glyphicon-book'
327 ) 260 )
328 self.assertTrue(book_icon.is_displayed()) 261 self.assertTrue(book_icon.is_displayed())
329 262
330 # AT RIGHT -> button "New project" 263 # AT RIGHT -> button "New project"
331 new_project_button = self.find('#new-project-button') 264 new_project_button = self.find('#new-project-button')
332 self.assertTrue(new_project_button.is_displayed()) 265 self.assertTrue(new_project_button.is_displayed())
333 self.assertTrue(new_project_button.text == 'New project') 266 self.assertEqual(new_project_button.text, 'New project')
334 new_project_button.click() 267 new_project_button.click()
335 self.assertTrue( 268 self.assertIn(
336 '/toastergui/newproject/' in str(self.driver.current_url) 269 '/toastergui/newproject/', str(self.driver.current_url)
337 ) 270 )
338 271
339 def test_edit_project_name(self): 272 def test_edit_project_name(self):
@@ -348,7 +281,8 @@ class TestProjectPage(SeleniumFunctionalTestCase):
348 281
349 # click on "Edit" icon button 282 # click on "Edit" icon button
350 self.wait_until_visible('#project-name-container') 283 self.wait_until_visible('#project-name-container')
351 edit_button = self.find('#project-change-form-toggle') 284 finder = lambda driver: self.find('#project-change-form-toggle')
285 edit_button = self.wait_until_element_clickable(finder)
352 edit_button.click() 286 edit_button.click()
353 project_name_input = self.find('#project-name-change-input') 287 project_name_input = self.find('#project-name-change-input')
354 self.assertTrue(project_name_input.is_displayed()) 288 self.assertTrue(project_name_input.is_displayed())
@@ -358,8 +292,8 @@ class TestProjectPage(SeleniumFunctionalTestCase):
358 292
359 # check project name is changed 293 # check project name is changed
360 self.wait_until_visible('#project-name-container') 294 self.wait_until_visible('#project-name-container')
361 self.assertTrue( 295 self.assertIn(
362 'New Name' in str(self.find('#project-name-container').text) 296 'New Name', str(self.find('#project-name-container').text)
363 ) 297 )
364 298
365 def test_project_page_tabs(self): 299 def test_project_page_tabs(self):
@@ -376,10 +310,10 @@ class TestProjectPage(SeleniumFunctionalTestCase):
376 # check "configuration" tab 310 # check "configuration" tab
377 self.wait_until_visible('#topbar-configuration-tab') 311 self.wait_until_visible('#topbar-configuration-tab')
378 config_tab = self.find('#topbar-configuration-tab') 312 config_tab = self.find('#topbar-configuration-tab')
379 self.assertTrue(config_tab.get_attribute('class') == 'active') 313 self.assertEqual(config_tab.get_attribute('class'), 'active')
380 self.assertTrue('Configuration' in str(config_tab.text)) 314 self.assertIn('Configuration', str(config_tab.text))
381 self.assertTrue( 315 self.assertIn(
382 f"/toastergui/project/{TestProjectPage.project_id}" in str(self.driver.current_url) 316 f"/toastergui/project/{TestProjectPageBase.project_id}", str(self.driver.current_url)
383 ) 317 )
384 318
385 def get_tabs(): 319 def get_tabs():
@@ -392,9 +326,9 @@ class TestProjectPage(SeleniumFunctionalTestCase):
392 def check_tab_link(tab_index, tab_name, url): 326 def check_tab_link(tab_index, tab_name, url):
393 tab = get_tabs()[tab_index] 327 tab = get_tabs()[tab_index]
394 tab_link = tab.find_element(By.TAG_NAME, 'a') 328 tab_link = tab.find_element(By.TAG_NAME, 'a')
395 self.assertTrue(url in tab_link.get_attribute('href')) 329 self.assertIn(url, tab_link.get_attribute('href'))
396 self.assertTrue(tab_name in tab_link.text) 330 self.assertIn(tab_name, tab_link.text)
397 self.assertTrue(tab.get_attribute('class') == 'active') 331 self.assertEqual(tab.get_attribute('class'), 'active')
398 332
399 # check "Builds" tab 333 # check "Builds" tab
400 builds_tab = get_tabs()[1] 334 builds_tab = get_tabs()[1]
@@ -402,7 +336,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
402 check_tab_link( 336 check_tab_link(
403 1, 337 1,
404 'Builds', 338 'Builds',
405 f"/toastergui/project/{TestProjectPage.project_id}/builds" 339 f"/toastergui/project/{TestProjectPageBase.project_id}/builds"
406 ) 340 )
407 341
408 # check "Import layers" tab 342 # check "Import layers" tab
@@ -411,7 +345,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
411 check_tab_link( 345 check_tab_link(
412 2, 346 2,
413 'Import layer', 347 'Import layer',
414 f"/toastergui/project/{TestProjectPage.project_id}/importlayer" 348 f"/toastergui/project/{TestProjectPageBase.project_id}/importlayer"
415 ) 349 )
416 350
417 # check "New custom image" tab 351 # check "New custom image" tab
@@ -420,7 +354,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
420 check_tab_link( 354 check_tab_link(
421 3, 355 3,
422 'New custom image', 356 'New custom image',
423 f"/toastergui/project/{TestProjectPage.project_id}/newcustomimage" 357 f"/toastergui/project/{TestProjectPageBase.project_id}/newcustomimage"
424 ) 358 )
425 359
426 # check search box can be use to build recipes 360 # check search box can be use to build recipes
@@ -428,13 +362,17 @@ class TestProjectPage(SeleniumFunctionalTestCase):
428 search_box.send_keys('core-image-minimal') 362 search_box.send_keys('core-image-minimal')
429 self.find('#build-button').click() 363 self.find('#build-button').click()
430 self.wait_until_visible('#latest-builds') 364 self.wait_until_visible('#latest-builds')
431 lastest_builds = self.driver.find_elements( 365 buildtext = "Loading"
432 By.XPATH, 366 while "Loading" in buildtext:
433 '//div[@id="latest-builds"]', 367 time.sleep(1)
434 ) 368 lastest_builds = self.driver.find_elements(
435 last_build = lastest_builds[0] 369 By.XPATH,
436 self.assertTrue( 370 '//div[@id="latest-builds"]',
437 'core-image-minimal' in str(last_build.text) 371 )
372 last_build = lastest_builds[0]
373 buildtext = last_build.text
374 self.assertIn(
375 'core-image-minimal', str(last_build.text)
438 ) 376 )
439 377
440 def test_softwareRecipe_page(self): 378 def test_softwareRecipe_page(self):
@@ -446,7 +384,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
446 """ 384 """
447 self._navigate_to_config_nav('softwarerecipestable', 4) 385 self._navigate_to_config_nav('softwarerecipestable', 4)
448 # check title "Compatible software recipes" is displayed 386 # check title "Compatible software recipes" is displayed
449 self.assertTrue("Compatible software recipes" in self.get_page_source()) 387 self.assertIn("Compatible software recipes", self.get_page_source())
450 # Test search input 388 # Test search input
451 self._mixin_test_table_search_input( 389 self._mixin_test_table_search_input(
452 input_selector='search-input-softwarerecipestable', 390 input_selector='search-input-softwarerecipestable',
@@ -455,12 +393,8 @@ class TestProjectPage(SeleniumFunctionalTestCase):
455 table_selector='softwarerecipestable' 393 table_selector='softwarerecipestable'
456 ) 394 )
457 # check "build recipe" button works 395 # check "build recipe" button works
458 rows = self.find_all('#softwarerecipestable tbody tr') 396 finder = lambda driver: self.find_all('#softwarerecipestable tbody tr')[0].find_element(By.XPATH, '//td[@class="add-del-layers"]/a')
459 image_to_build = rows[0] 397 build_btn = self.wait_until_element_clickable(finder)
460 build_btn = image_to_build.find_element(
461 By.XPATH,
462 '//td[@class="add-del-layers"]//a[1]'
463 )
464 build_btn.click() 398 build_btn.click()
465 build_state = wait_until_build(self, 'queued cloning starting parsing failed') 399 build_state = wait_until_build(self, 'queued cloning starting parsing failed')
466 lastest_builds = self.driver.find_elements( 400 lastest_builds = self.driver.find_elements(
@@ -468,11 +402,10 @@ class TestProjectPage(SeleniumFunctionalTestCase):
468 '//div[@id="latest-builds"]/div' 402 '//div[@id="latest-builds"]/div'
469 ) 403 )
470 self.assertTrue(len(lastest_builds) > 0) 404 self.assertTrue(len(lastest_builds) > 0)
471 last_build = lastest_builds[0] 405 # Find the latest builds, the last build and then the cancel button
472 cancel_button = last_build.find_element( 406
473 By.XPATH, 407 finder = lambda driver: driver.find_elements(By.XPATH, '//div[@id="latest-builds"]/div')[0].find_element(By.XPATH, '//span[@class="cancel-build-btn pull-right alert-link"]')
474 '//span[@class="cancel-build-btn pull-right alert-link"]', 408 cancel_button = self.wait_until_element_clickable(finder)
475 )
476 cancel_button.click() 409 cancel_button.click()
477 if 'starting' not in build_state: # change build state when cancelled in starting state 410 if 'starting' not in build_state: # change build state when cancelled in starting state
478 wait_until_build_cancelled(self) 411 wait_until_build_cancelled(self)
@@ -510,7 +443,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
510 """ 443 """
511 self._navigate_to_config_nav('machinestable', 5) 444 self._navigate_to_config_nav('machinestable', 5)
512 # check title "Compatible software recipes" is displayed 445 # check title "Compatible software recipes" is displayed
513 self.assertTrue("Compatible machines" in self.get_page_source()) 446 self.assertIn("Compatible machines", self.get_page_source())
514 # Test search input 447 # Test search input
515 self._mixin_test_table_search_input( 448 self._mixin_test_table_search_input(
516 input_selector='search-input-machinestable', 449 input_selector='search-input-machinestable',
@@ -519,17 +452,13 @@ class TestProjectPage(SeleniumFunctionalTestCase):
519 table_selector='machinestable' 452 table_selector='machinestable'
520 ) 453 )
521 # check "Select machine" button works 454 # check "Select machine" button works
522 rows = self.find_all('#machinestable tbody tr') 455 finder = lambda driver: self.find_all('#machinestable tbody tr')[0].find_element(By.XPATH, '//td[@class="add-del-layers"]')
523 machine_to_select = rows[0] 456 select_btn = self.wait_until_element_clickable(finder)
524 select_btn = machine_to_select.find_element( 457 select_btn.click()
525 By.XPATH, 458 self.wait_until_visible('#project-machine-name')
526 '//td[@class="add-del-layers"]//a[1]'
527 )
528 select_btn.send_keys(Keys.RETURN)
529 self.wait_until_visible('#config-nav')
530 project_machine_name = self.find('#project-machine-name') 459 project_machine_name = self.find('#project-machine-name')
531 self.assertTrue( 460 self.assertIn(
532 'qemux86-64' in project_machine_name.text 461 'qemux86-64', project_machine_name.text
533 ) 462 )
534 # check "Add layer" button works 463 # check "Add layer" button works
535 self._navigate_to_config_nav('machinestable', 5) 464 self._navigate_to_config_nav('machinestable', 5)
@@ -540,16 +469,23 @@ class TestProjectPage(SeleniumFunctionalTestCase):
540 searchBtn_selector='search-submit-machinestable', 469 searchBtn_selector='search-submit-machinestable',
541 table_selector='machinestable' 470 table_selector='machinestable'
542 ) 471 )
543 self.wait_until_visible('#machinestable tbody tr', poll=3) 472
544 rows = self.find_all('#machinestable tbody tr') 473 self.wait_until_visible('#machinestable tbody tr')
545 machine_to_add = rows[0] 474 # Locate a machine to add button
546 add_btn = machine_to_add.find_element(By.XPATH, '//td[@class="add-del-layers"]') 475 finder = lambda driver: self.find_all('#machinestable tbody tr')[0].find_element(By.XPATH, '//td[@class="add-del-layers"]')
476 add_btn = self.wait_until_element_clickable(finder)
547 add_btn.click() 477 add_btn.click()
548 self.wait_until_visible('#change-notification') 478 self.wait_until_visible('#change-notification')
549 change_notification = self.find('#change-notification') 479 change_notification = self.find('#change-notification')
550 self.assertTrue( 480 self.assertIn(
551 f'You have added 1 layer to your project' in str(change_notification.text) 481 f'You have added 1 layer to your project', str(change_notification.text)
552 ) 482 )
483
484 finder = lambda driver: self.find('#hide-alert')
485 hide_button = self.wait_until_element_clickable(finder)
486 hide_button.click()
487 self.wait_until_not_visible('#change-notification')
488
553 # check Machine table feature(show/hide column, pagination) 489 # check Machine table feature(show/hide column, pagination)
554 self._navigate_to_config_nav('machinestable', 5) 490 self._navigate_to_config_nav('machinestable', 5)
555 column_list = [ 491 column_list = [
@@ -580,7 +516,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
580 """ 516 """
581 self._navigate_to_config_nav('layerstable', 6) 517 self._navigate_to_config_nav('layerstable', 6)
582 # check title "Compatible layers" is displayed 518 # check title "Compatible layers" is displayed
583 self.assertTrue("Compatible layers" in self.get_page_source()) 519 self.assertIn("Compatible layers", self.get_page_source())
584 # Test search input 520 # Test search input
585 input_text='meta-tanowrt' 521 input_text='meta-tanowrt'
586 self._mixin_test_table_search_input( 522 self._mixin_test_table_search_input(
@@ -590,42 +526,44 @@ class TestProjectPage(SeleniumFunctionalTestCase):
590 table_selector='layerstable' 526 table_selector='layerstable'
591 ) 527 )
592 # check "Add layer" button works 528 # check "Add layer" button works
593 self.wait_until_visible('#layerstable tbody tr', poll=3) 529 self.wait_until_visible('#layerstable tbody tr')
594 rows = self.find_all('#layerstable tbody tr') 530 finder = lambda driver: self.find_all('#layerstable tbody tr')[0].find_element(By.XPATH, '//td[@class="add-del-layers"]/a[@data-directive="add"]')
595 layer_to_add = rows[0] 531 add_btn = self.wait_until_element_clickable(finder)
596 add_btn = layer_to_add.find_element(
597 By.XPATH,
598 '//td[@class="add-del-layers"]'
599 )
600 add_btn.click() 532 add_btn.click()
601 # check modal is displayed 533 # check modal is displayed
602 self.wait_until_visible('#dependencies-modal', poll=3) 534 self.wait_until_visible('#dependencies-modal')
603 list_dependencies = self.find_all('#dependencies-list li') 535 list_dependencies = self.find_all('#dependencies-list li')
604 # click on add-layers button 536 # click on add-layers button
605 add_layers_btn = self.driver.find_element( 537 finder = lambda driver: self.driver.find_element(By.XPATH, '//form[@id="dependencies-modal-form"]//button[@class="btn btn-primary"]')
606 By.XPATH, 538 add_layers_btn = self.wait_until_element_clickable(finder)
607 '//form[@id="dependencies-modal-form"]//button[@class="btn btn-primary"]'
608 )
609 add_layers_btn.click() 539 add_layers_btn.click()
610 self.wait_until_visible('#change-notification') 540 self.wait_until_visible('#change-notification')
611 change_notification = self.find('#change-notification') 541 change_notification = self.find('#change-notification')
612 self.assertTrue( 542 self.assertIn(
613 f'You have added {len(list_dependencies)+1} layers to your project: {input_text} and its dependencies' in str(change_notification.text) 543 f'You have added {len(list_dependencies)+1} layers to your project: {input_text} and its dependencies', str(change_notification.text)
614 ) 544 )
545
546 finder = lambda driver: self.find('#hide-alert')
547 hide_button = self.wait_until_element_clickable(finder)
548 hide_button.click()
549 self.wait_until_not_visible('#change-notification')
550
615 # check "Remove layer" button works 551 # check "Remove layer" button works
616 self.wait_until_visible('#layerstable tbody tr', poll=3) 552 self.wait_until_visible('#layerstable tbody tr')
617 rows = self.find_all('#layerstable tbody tr') 553 finder = lambda driver: self.find_all('#layerstable tbody tr')[0].find_element(By.XPATH, '//td[@class="add-del-layers"]/a[@data-directive="remove"]')
618 layer_to_remove = rows[0] 554 remove_btn = self.wait_until_element_clickable(finder)
619 remove_btn = layer_to_remove.find_element(
620 By.XPATH,
621 '//td[@class="add-del-layers"]'
622 )
623 remove_btn.click() 555 remove_btn.click()
624 self.wait_until_visible('#change-notification', poll=2) 556 self.wait_until_visible('#change-notification')
625 change_notification = self.find('#change-notification') 557 change_notification = self.find('#change-notification')
626 self.assertTrue( 558 self.assertIn(
627 f'You have removed 1 layer from your project: {input_text}' in str(change_notification.text) 559 f'You have removed 1 layer from your project: {input_text}', str(change_notification.text)
628 ) 560 )
561
562 finder = lambda driver: self.find('#hide-alert')
563 hide_button = self.wait_until_element_clickable(finder)
564 hide_button.click()
565 self.wait_until_not_visible('#change-notification')
566
629 # check layers table feature(show/hide column, pagination) 567 # check layers table feature(show/hide column, pagination)
630 self._navigate_to_config_nav('layerstable', 6) 568 self._navigate_to_config_nav('layerstable', 6)
631 column_list = [ 569 column_list = [
@@ -656,7 +594,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
656 """ 594 """
657 self._navigate_to_config_nav('distrostable', 7) 595 self._navigate_to_config_nav('distrostable', 7)
658 # check title "Compatible distros" is displayed 596 # check title "Compatible distros" is displayed
659 self.assertTrue("Compatible Distros" in self.get_page_source()) 597 self.assertIn("Compatible Distros", self.get_page_source())
660 # Test search input 598 # Test search input
661 input_text='poky-altcfg' 599 input_text='poky-altcfg'
662 self._mixin_test_table_search_input( 600 self._mixin_test_table_search_input(
@@ -666,17 +604,14 @@ class TestProjectPage(SeleniumFunctionalTestCase):
666 table_selector='distrostable' 604 table_selector='distrostable'
667 ) 605 )
668 # check "Add distro" button works 606 # check "Add distro" button works
669 rows = self.find_all('#distrostable tbody tr') 607 self.wait_until_visible(".add-del-layers")
670 distro_to_add = rows[0] 608 finder = lambda driver: self.find_all('#distrostable tbody tr')[0].find_element(By.XPATH, '//td[@class="add-del-layers"]')
671 add_btn = distro_to_add.find_element( 609 add_btn = self.wait_until_element_clickable(finder)
672 By.XPATH,
673 '//td[@class="add-del-layers"]//a[1]'
674 )
675 add_btn.click() 610 add_btn.click()
676 self.wait_until_visible('#change-notification', poll=2) 611 self.wait_until_visible('#change-notification')
677 change_notification = self.find('#change-notification') 612 change_notification = self.find('#change-notification')
678 self.assertTrue( 613 self.assertIn(
679 f'You have changed the distro to: {input_text}' in str(change_notification.text) 614 f'You have changed the distro to: {input_text}', str(change_notification.text)
680 ) 615 )
681 # check distro table feature(show/hide column, pagination) 616 # check distro table feature(show/hide column, pagination)
682 self._navigate_to_config_nav('distrostable', 7) 617 self._navigate_to_config_nav('distrostable', 7)
@@ -699,7 +634,7 @@ class TestProjectPage(SeleniumFunctionalTestCase):
699 ) 634 )
700 635
701 def test_single_layer_page(self): 636 def test_single_layer_page(self):
702 """ Test layer page 637 """ Test layer details page using meta-poky as an example (assumes is added to start with)
703 - Check if title is displayed 638 - Check if title is displayed
704 - Check add/remove layer button works 639 - Check add/remove layer button works
705 - Check tabs(layers, recipes, machines) are displayed 640 - Check tabs(layers, recipes, machines) are displayed
@@ -708,45 +643,62 @@ class TestProjectPage(SeleniumFunctionalTestCase):
708 - Check layer summary 643 - Check layer summary
709 - Check layer description 644 - Check layer description
710 """ 645 """
711 url = reverse("layerdetails", args=(TestProjectPage.project_id, 8)) 646 self._navigate_to_config_nav('layerstable', 6)
712 self.get(url) 647 layer_link = self.driver.find_element(By.XPATH, '//tr/td[@class="layer__name"]/a[contains(text(),"meta-poky")]')
648 layer_link.click()
713 self.wait_until_visible('.page-header') 649 self.wait_until_visible('.page-header')
714 # check title is displayed 650 # check title is displayed
715 self.assertTrue(self.find('.page-header h1').is_displayed()) 651 self.assertTrue(self.find('.page-header h1').is_displayed())
716 652
717 # check add layer button works 653 # check remove layer button works
718 remove_layer_btn = self.find('#add-remove-layer-btn') 654 finder = lambda driver: self.find('#add-remove-layer-btn')
655 remove_layer_btn = self.wait_until_element_clickable(finder)
719 remove_layer_btn.click() 656 remove_layer_btn.click()
720 self.wait_until_visible('#change-notification', poll=2) 657 self.wait_until_visible('#change-notification')
721 change_notification = self.find('#change-notification') 658 change_notification = self.find('#change-notification')
722 self.assertTrue( 659 self.assertIn(
723 f'You have removed 1 layer from your project' in str(change_notification.text) 660 f'You have removed 1 layer from your project', str(change_notification.text)
724 ) 661 )
725 # check add layer button works, 18 is the random layer id 662 finder = lambda driver: self.find('#hide-alert')
726 add_layer_btn = self.find('#add-remove-layer-btn') 663 hide_button = self.wait_until_element_clickable(finder)
664 hide_button.click()
665 # check add layer button works
666 self.wait_until_not_visible('#change-notification')
667 finder = lambda driver: self.find('#add-remove-layer-btn')
668 add_layer_btn = self.wait_until_element_clickable(finder)
727 add_layer_btn.click() 669 add_layer_btn.click()
728 self.wait_until_visible('#change-notification') 670 self.wait_until_visible('#change-notification')
729 change_notification = self.find('#change-notification') 671 change_notification = self.find('#change-notification')
730 self.assertTrue( 672 self.assertIn(
731 f'You have added 1 layer to your project' in str(change_notification.text) 673 f'You have added 1 layer to your project', str(change_notification.text)
732 ) 674 )
675 finder = lambda driver: self.find('#hide-alert')
676 hide_button = self.wait_until_element_clickable(finder)
677 hide_button.click()
678 self.wait_until_not_visible('#change-notification')
733 # check tabs(layers, recipes, machines) are displayed 679 # check tabs(layers, recipes, machines) are displayed
734 tabs = self.find_all('.nav-tabs li') 680 tabs = self.find_all('.nav-tabs li')
735 self.assertEqual(len(tabs), 3) 681 self.assertEqual(len(tabs), 3)
736 # Check first tab 682 # Check first tab
737 tabs[0].click() 683 tabs[0].click()
738 self.assertTrue( 684 self.assertIn(
739 'active' in str(self.find('#information').get_attribute('class')) 685 'active', str(self.find('#information').get_attribute('class'))
740 ) 686 )
741 # Check second tab 687 # Check second tab (recipes)
688 self.wait_until_visible('.nav-tabs')
689 # Ensure page is scrolled to the top
690 self.driver.execute_script('window.scrollTo({behavior: "instant", top: 0, left: 0})')
742 tabs[1].click() 691 tabs[1].click()
743 self.assertTrue( 692 self.assertIn(
744 'active' in str(self.find('#recipes').get_attribute('class')) 693 'active', str(self.find('#recipes').get_attribute('class'))
745 ) 694 )
746 # Check third tab 695 # Check third tab (machines)
696 self.wait_until_visible('.nav-tabs')
697 # Ensure page is scrolled to the top
698 self.driver.execute_script('window.scrollTo({behavior: "instant", top: 0, left: 0})')
747 tabs[2].click() 699 tabs[2].click()
748 self.assertTrue( 700 self.assertIn(
749 'active' in str(self.find('#machines').get_attribute('class')) 701 'active', str(self.find('#machines').get_attribute('class'))
750 ) 702 )
751 # Check left section is displayed 703 # Check left section is displayed
752 section = self.find('.well') 704 section = self.find('.well')
@@ -755,9 +707,13 @@ class TestProjectPage(SeleniumFunctionalTestCase):
755 section.find_element(By.XPATH, '//h2[1]').is_displayed() 707 section.find_element(By.XPATH, '//h2[1]').is_displayed()
756 ) 708 )
757 # Check layer summary 709 # Check layer summary
758 self.assertTrue("Summary" in section.text) 710 self.assertIn("Summary", section.text)
759 # Check layer description 711 # Check layer description
760 self.assertTrue("Description" in section.text) 712 self.assertIn("Description", section.text)
713
714@pytest.mark.django_db
715@pytest.mark.order("last")
716class TestProjectPageRecipes(TestProjectPageBase):
761 717
762 def test_single_recipe_page(self): 718 def test_single_recipe_page(self):
763 """ Test recipe page 719 """ Test recipe page
@@ -767,7 +723,12 @@ class TestProjectPage(SeleniumFunctionalTestCase):
767 - Check recipe: name, summary, description, Version, Section, 723 - Check recipe: name, summary, description, Version, Section,
768 License, Approx. packages included, Approx. size, Recipe file 724 License, Approx. packages included, Approx. size, Recipe file
769 """ 725 """
770 url = reverse("recipedetails", args=(TestProjectPage.project_id, 53428)) 726 # Use a recipe which is likely to exist in the layer index but not enabled
727 # in poky out the box - xen-image-minimal from meta-virtualization
728 self._navigate_to_project_page()
729 prj = Project.objects.get(pk=TestProjectPageBase.project_id)
730 recipe_id = prj.get_all_compatible_recipes().get(name="xen-image-minimal").pk
731 url = reverse("recipedetails", args=(TestProjectPageBase.project_id, recipe_id))
771 self.get(url) 732 self.get(url)
772 self.wait_until_visible('.page-header') 733 self.wait_until_visible('.page-header')
773 # check title is displayed 734 # check title is displayed
@@ -782,11 +743,33 @@ class TestProjectPage(SeleniumFunctionalTestCase):
782 section.find_element(By.XPATH, '//h2[1]').is_displayed() 743 section.find_element(By.XPATH, '//h2[1]').is_displayed()
783 ) 744 )
784 # Check recipe sections details info are displayed 745 # Check recipe sections details info are displayed
785 self.assertTrue("Summary" in section.text) 746 self.assertIn("Summary", section.text)
786 self.assertTrue("Description" in section.text) 747 self.assertIn("Description", section.text)
787 self.assertTrue("Version" in section.text) 748 self.assertIn("Version", section.text)
788 self.assertTrue("Section" in section.text) 749 self.assertIn("Section", section.text)
789 self.assertTrue("License" in section.text) 750 self.assertIn("License", section.text)
790 self.assertTrue("Approx. packages included" in section.text) 751 self.assertIn("Approx. packages included", section.text)
791 self.assertTrue("Approx. package size" in section.text) 752 self.assertIn("Approx. package size", section.text)
792 self.assertTrue("Recipe file" in section.text) 753 self.assertIn("Recipe file", section.text)
754
755 def test_image_recipe_editColumn(self):
756 """ Test the edit column feature in image recipe table on project page """
757 self._get_create_builds(success=10, failure=10)
758
759 url = reverse('projectimagerecipes', args=(TestProjectPageBase.project_id,))
760 self.get(url)
761 self.wait_until_present('#imagerecipestable tbody tr')
762
763 column_list = [
764 'get_description_or_summary', 'layer_version__get_vcs_reference',
765 'layer_version__layer__name', 'license', 'recipe-file', 'section',
766 'version'
767 ]
768
769 # Check that we can hide the edit column
770 self._mixin_test_table_edit_column(
771 'imagerecipestable',
772 'edit-columns-button',
773 [f'checkbox-{column}' for column in column_list]
774 )
775
diff --git a/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py b/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
index eb905ddf3f..80c53e1544 100644
--- a/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
+++ b/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
@@ -7,72 +7,27 @@
7# 7#
8 8
9import string 9import string
10import random 10import time
11import pytest 11import pytest
12from django.urls import reverse 12from django.urls import reverse
13from selenium.webdriver import Keys 13from selenium.webdriver import Keys
14from selenium.webdriver.support.select import Select 14from selenium.webdriver.support.select import Select
15from selenium.common.exceptions import ElementClickInterceptedException, NoSuchElementException, TimeoutException 15from selenium.common.exceptions import ElementClickInterceptedException, NoSuchElementException, TimeoutException
16from orm.models import Project
17from tests.functional.functional_helpers import SeleniumFunctionalTestCase 16from tests.functional.functional_helpers import SeleniumFunctionalTestCase
18from selenium.webdriver.common.by import By 17from selenium.webdriver.common.by import By
19 18
20from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled 19from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled
21 20
22 21class TestProjectConfigTabBase(SeleniumFunctionalTestCase):
23@pytest.mark.django_db
24@pytest.mark.order("last")
25class TestProjectConfigTab(SeleniumFunctionalTestCase):
26 PROJECT_NAME = 'TestProjectConfigTab' 22 PROJECT_NAME = 'TestProjectConfigTab'
27 project_id = None 23 project_id = None
28 24
29 def _create_project(self, project_name, **kwargs):
30 """ Create/Test new project using:
31 - Project Name: Any string
32 - Release: Any string
33 - Merge Toaster settings: True or False
34 """
35 release = kwargs.get('release', '3')
36 self.get(reverse('newproject'))
37 self.wait_until_visible('#new-project-name')
38 self.find("#new-project-name").send_keys(project_name)
39 select = Select(self.find("#projectversion"))
40 select.select_by_value(release)
41
42 # check merge toaster settings
43 checkbox = self.find('.checkbox-mergeattr')
44 if not checkbox.is_selected():
45 checkbox.click()
46
47 if self.PROJECT_NAME != 'TestProjectConfigTab':
48 # Reset project name if it's not the default one
49 self.PROJECT_NAME = 'TestProjectConfigTab'
50
51 self.find("#create-project-button").click()
52
53 try:
54 self.wait_until_visible('#hint-error-project-name', poll=3)
55 url = reverse('project', args=(TestProjectConfigTab.project_id, ))
56 self.get(url)
57 self.wait_until_visible('#config-nav', poll=3)
58 except TimeoutException:
59 self.wait_until_visible('#config-nav', poll=3)
60
61 def _random_string(self, length):
62 return ''.join(
63 random.choice(string.ascii_letters) for _ in range(length)
64 )
65
66 def _navigate_to_project_page(self): 25 def _navigate_to_project_page(self):
67 # Navigate to project page 26 # Navigate to project page
68 if TestProjectConfigTab.project_id is None: 27 if TestProjectConfigTabBase.project_id is None:
69 self._create_project(project_name=self._random_string(10)) 28 TestProjectConfigTabBase.project_id = self.create_new_project(self.PROJECT_NAME, '3', None, True)
70 current_url = self.driver.current_url 29 url = reverse('project', args=(TestProjectConfigTabBase.project_id,))
71 TestProjectConfigTab.project_id = get_projectId_from_url( 30 self.get(url)
72 current_url)
73 else:
74 url = reverse('project', args=(TestProjectConfigTab.project_id,))
75 self.get(url)
76 self.wait_until_visible('#config-nav') 31 self.wait_until_visible('#config-nav')
77 32
78 def _create_builds(self): 33 def _create_builds(self):
@@ -88,8 +43,8 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
88 '//div[@id="latest-builds"]/div', 43 '//div[@id="latest-builds"]/div',
89 ) 44 )
90 last_build = lastest_builds[0] 45 last_build = lastest_builds[0]
91 self.assertTrue( 46 self.assertIn(
92 'foo' in str(last_build.text) 47 'foo', str(last_build.text)
93 ) 48 )
94 last_build = lastest_builds[0] 49 last_build = lastest_builds[0]
95 try: 50 try:
@@ -114,6 +69,8 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
114 config_nav = self.find('#config-nav') 69 config_nav = self.find('#config-nav')
115 return config_nav.find_elements(By.TAG_NAME, 'li')[index] 70 return config_nav.find_elements(By.TAG_NAME, 'li')[index]
116 71
72class TestProjectConfigTab(TestProjectConfigTabBase):
73
117 def test_project_config_nav(self): 74 def test_project_config_nav(self):
118 """ Test project config tab navigation: 75 """ Test project config tab navigation:
119 - Check if the menu is displayed and contains the right elements: 76 - Check if the menu is displayed and contains the right elements:
@@ -138,48 +95,48 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
138 95
139 def check_config_nav_item(index, item_name, url): 96 def check_config_nav_item(index, item_name, url):
140 item = _get_config_nav_item(index) 97 item = _get_config_nav_item(index)
141 self.assertTrue(item_name in item.text) 98 self.assertIn(item_name, item.text)
142 self.assertTrue(item.get_attribute('class') == 'active') 99 self.assertEqual(item.get_attribute('class'), 'active')
143 self.assertTrue(url in self.driver.current_url) 100 self.assertIn(url, self.driver.current_url)
144 101
145 # check if the menu contains the right elements 102 # check if the menu contains the right elements
146 # COMPATIBLE METADATA 103 # COMPATIBLE METADATA
147 compatible_metadata = _get_config_nav_item(1) 104 compatible_metadata = _get_config_nav_item(1)
148 self.assertTrue( 105 self.assertIn(
149 "compatible metadata" in compatible_metadata.text.lower() 106 "compatible metadata", compatible_metadata.text.lower()
150 ) 107 )
151 # EXTRA CONFIGURATION 108 # EXTRA CONFIGURATION
152 extra_configuration = _get_config_nav_item(8) 109 extra_configuration = _get_config_nav_item(8)
153 self.assertTrue( 110 self.assertIn(
154 "extra configuration" in extra_configuration.text.lower() 111 "extra configuration", extra_configuration.text.lower()
155 ) 112 )
156 # Actions 113 # Actions
157 actions = _get_config_nav_item(10) 114 actions = _get_config_nav_item(10)
158 self.assertTrue("actions" in str(actions.text).lower()) 115 self.assertIn("actions", str(actions.text).lower())
159 116
160 conf_nav_list = [ 117 conf_nav_list = [
161 # config 118 # config
162 [0, 'Configuration', 119 [0, 'Configuration',
163 f"/toastergui/project/{TestProjectConfigTab.project_id}"], 120 f"/toastergui/project/{TestProjectConfigTabBase.project_id}"],
164 # custom images 121 # custom images
165 [2, 'Custom images', 122 [2, 'Custom images',
166 f"/toastergui/project/{TestProjectConfigTab.project_id}/customimages"], 123 f"/toastergui/project/{TestProjectConfigTabBase.project_id}/customimages"],
167 # image recipes 124 # image recipes
168 [3, 'Image recipes', 125 [3, 'Image recipes',
169 f"/toastergui/project/{TestProjectConfigTab.project_id}/images"], 126 f"/toastergui/project/{TestProjectConfigTabBase.project_id}/images"],
170 # software recipes 127 # software recipes
171 [4, 'Software recipes', 128 [4, 'Software recipes',
172 f"/toastergui/project/{TestProjectConfigTab.project_id}/softwarerecipes"], 129 f"/toastergui/project/{TestProjectConfigTabBase.project_id}/softwarerecipes"],
173 # machines 130 # machines
174 [5, 'Machines', 131 [5, 'Machines',
175 f"/toastergui/project/{TestProjectConfigTab.project_id}/machines"], 132 f"/toastergui/project/{TestProjectConfigTabBase.project_id}/machines"],
176 # layers 133 # layers
177 [6, 'Layers', 134 [6, 'Layers',
178 f"/toastergui/project/{TestProjectConfigTab.project_id}/layers"], 135 f"/toastergui/project/{TestProjectConfigTabBase.project_id}/layers"],
179 # distro 136 # distro
180 [7, 'Distros', 137 [7, 'Distros',
181 f"/toastergui/project/{TestProjectConfigTab.project_id}/distros"], 138 f"/toastergui/project/{TestProjectConfigTabBase.project_id}/distros"],
182 # [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTab.project_id}/configuration"], # bitbake variables 139 # [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTabBase.project_id}/configuration"], # bitbake variables
183 ] 140 ]
184 for index, item_name, url in conf_nav_list: 141 for index, item_name, url in conf_nav_list:
185 item = _get_config_nav_item(index) 142 item = _get_config_nav_item(index)
@@ -253,7 +210,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
253 def test_show_rows(row_to_show, show_row_link): 210 def test_show_rows(row_to_show, show_row_link):
254 # Check that we can show rows == row_to_show 211 # Check that we can show rows == row_to_show
255 show_row_link.select_by_value(str(row_to_show)) 212 show_row_link.select_by_value(str(row_to_show))
256 self.wait_until_visible('#imagerecipestable tbody tr', poll=3) 213 self.wait_until_visible('#imagerecipestable tbody tr')
257 # check at least some rows are visible 214 # check at least some rows are visible
258 self.assertTrue( 215 self.assertTrue(
259 len(self.find_all('#imagerecipestable tbody tr')) > 0 216 len(self.find_all('#imagerecipestable tbody tr')) > 0
@@ -299,9 +256,11 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
299 - meta-poky 256 - meta-poky
300 - meta-yocto-bsp 257 - meta-yocto-bsp
301 """ 258 """
302 # Create a new project for this test 259 project_id = self.create_new_project(self.PROJECT_NAME + "-ST", '3', None, True)
303 project_name = self._random_string(10) 260 url = reverse('project', args=(project_id,))
304 self._create_project(project_name=project_name) 261 self.get(url)
262 self.wait_until_visible('#config-nav')
263
305 # check if the menu is displayed 264 # check if the menu is displayed
306 self.wait_until_visible('#project-page') 265 self.wait_until_visible('#project-page')
307 block_l = self.driver.find_element( 266 block_l = self.driver.find_element(
@@ -313,7 +272,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
313 def check_machine_distro(self, item_name, new_item_name, block_id): 272 def check_machine_distro(self, item_name, new_item_name, block_id):
314 block = self.find(f'#{block_id}') 273 block = self.find(f'#{block_id}')
315 title = block.find_element(By.TAG_NAME, 'h3') 274 title = block.find_element(By.TAG_NAME, 'h3')
316 self.assertTrue(item_name.capitalize() in title.text) 275 self.assertIn(item_name.capitalize(), title.text)
317 edit_btn = self.find(f'#change-{item_name}-toggle') 276 edit_btn = self.find(f'#change-{item_name}-toggle')
318 edit_btn.click() 277 edit_btn.click()
319 self.wait_until_visible(f'#{item_name}-change-input') 278 self.wait_until_visible(f'#{item_name}-change-input')
@@ -324,12 +283,15 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
324 change_btn.click() 283 change_btn.click()
325 self.wait_until_visible(f'#project-{item_name}-name') 284 self.wait_until_visible(f'#project-{item_name}-name')
326 project_name = self.find(f'#project-{item_name}-name') 285 project_name = self.find(f'#project-{item_name}-name')
327 self.assertTrue(new_item_name in project_name.text) 286 self.assertIn(new_item_name, project_name.text)
328 # check change notificaiton is displayed 287 # check change notificaiton is displayed
329 change_notification = self.find('#change-notification') 288 change_notification = self.find('#change-notification')
330 self.assertTrue( 289 self.assertIn(
331 f'You have changed the {item_name} to: {new_item_name}' in change_notification.text 290 f'You have changed the {item_name} to: {new_item_name}', change_notification.text
332 ) 291 )
292 hide_button = self.find('#hide-alert')
293 hide_button.click()
294 self.wait_until_not_visible('#change-notification')
333 295
334 # Machine 296 # Machine
335 check_machine_distro(self, 'machine', 'qemux86-64', 'machine-section') 297 check_machine_distro(self, 'machine', 'qemux86-64', 'machine-section')
@@ -338,97 +300,51 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
338 300
339 # Project release 301 # Project release
340 title = project_release.find_element(By.TAG_NAME, 'h3') 302 title = project_release.find_element(By.TAG_NAME, 'h3')
341 self.assertTrue("Project release" in title.text) 303 self.assertIn("Project release", title.text)
342 self.assertTrue( 304 self.assertIn(
343 "Yocto Project master" in self.find('#project-release-title').text 305 "Yocto Project master", self.find('#project-release-title').text
344 ) 306 )
345 # Layers 307 # Layers
346 title = layers.find_element(By.TAG_NAME, 'h3') 308 title = layers.find_element(By.TAG_NAME, 'h3')
347 self.assertTrue("Layers" in title.text) 309 self.assertIn("Layers", title.text)
310 self.wait_until_clickable('#layer-add-input')
348 # check at least three layers are displayed 311 # check at least three layers are displayed
349 # openembedded-core 312 # openembedded-core
350 # meta-poky 313 # meta-poky
351 # meta-yocto-bsp 314 # meta-yocto-bsp
352 layers_list = layers.find_element(By.ID, 'layers-in-project-list') 315 layer_list_items = []
353 layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') 316 starttime = time.time()
317 while len(layer_list_items) < 3:
318 layers_list = self.driver.find_element(By.ID, 'layers-in-project-list')
319 layer_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
320 if time.time() > (starttime + 30):
321 self.fail("Layer list didn't contain at least 3 items within 30s (contained %d)" % len(layer_list_items))
322
354 # remove all layers except the first three layers 323 # remove all layers except the first three layers
355 for i in range(3, len(layers_list_items)): 324 for i in range(3, len(layer_list_items)):
356 layers_list_items[i].find_element(By.TAG_NAME, 'span').click() 325 layer_list_items[i].find_element(By.TAG_NAME, 'span').click()
326
357 # check can add a layer if exists 327 # check can add a layer if exists
358 add_layer_input = layers.find_element(By.ID, 'layer-add-input') 328 add_layer_input = layers.find_element(By.ID, 'layer-add-input')
359 add_layer_input.send_keys('meta-oe') 329 add_layer_input.send_keys('meta-oe')
360 self.wait_until_visible('#layer-container > form > div > span > div') 330 self.wait_until_visible('#layer-container > form > div > span > div')
361 dropdown_item = self.driver.find_element( 331 self.wait_until_visible('.dropdown-menu')
362 By.XPATH, 332 finder = lambda driver: driver.find_element(By.XPATH, '//*[@id="layer-container"]/form/div/span/div/div/div')
363 '//*[@id="layer-container"]/form/div/span/div' 333 dropdown_item = self.wait_until_element_clickable(finder)
364 ) 334 dropdown_item.click()
365 try: 335 self.wait_until_clickable('#add-layer-btn')
366 dropdown_item.click()
367 except ElementClickInterceptedException:
368 self.skipTest(
369 "layer-container dropdown item click intercepted. Element not properly visible.")
370 add_layer_btn = layers.find_element(By.ID, 'add-layer-btn') 336 add_layer_btn = layers.find_element(By.ID, 'add-layer-btn')
371 add_layer_btn.click() 337 add_layer_btn.click()
372 self.wait_until_visible('#layers-in-project-list') 338 self.wait_until_visible('#layers-in-project-list')
373 # check layer is added
374 layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
375 self.assertTrue(len(layers_list_items) == 4)
376 339
377 def test_most_build_recipes(self): 340 # check layer is added
378 """ Test most build recipes block contains""" 341 layer_list_items = []
379 def rebuild_from_most_build_recipes(recipe_list_items): 342 starttime = time.time()
380 checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input') 343 while len(layer_list_items) < 4:
381 checkbox.click() 344 layers_list = self.driver.find_element(By.ID, 'layers-in-project-list')
382 build_btn = self.find('#freq-build-btn') 345 layer_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
383 build_btn.click() 346 if time.time() > (starttime + 30):
384 self.wait_until_visible('#latest-builds') 347 self.fail("Layer list didn't contain at least 4 items within 30s (contained %d)" % len(layer_list_items))
385 wait_until_build(self, 'queued cloning starting parsing failed')
386 lastest_builds = self.driver.find_elements(
387 By.XPATH,
388 '//div[@id="latest-builds"]/div'
389 )
390 self.assertTrue(len(lastest_builds) >= 2)
391 last_build = lastest_builds[0]
392 try:
393 cancel_button = last_build.find_element(
394 By.XPATH,
395 '//span[@class="cancel-build-btn pull-right alert-link"]',
396 )
397 cancel_button.click()
398 except NoSuchElementException:
399 # Skip if the build is already cancelled
400 pass
401 wait_until_build_cancelled(self)
402 # Create a new project for remaining asserts
403 project_name = self._random_string(10)
404 self._create_project(project_name=project_name, release='2')
405 current_url = self.driver.current_url
406 TestProjectConfigTab.project_id = get_projectId_from_url(current_url)
407 url = current_url.split('?')[0]
408
409 # Create a new builds
410 self._create_builds()
411
412 # back to project page
413 self.driver.get(url)
414
415 self.wait_until_visible('#project-page', poll=3)
416
417 # Most built recipes
418 most_built_recipes = self.driver.find_element(
419 By.XPATH, '//*[@id="project-page"]/div[1]/div[3]')
420 title = most_built_recipes.find_element(By.TAG_NAME, 'h3')
421 self.assertTrue("Most built recipes" in title.text)
422 # check can select a recipe and build it
423 self.wait_until_visible('#freq-build-list', poll=3)
424 recipe_list = self.find('#freq-build-list')
425 recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li')
426 self.assertTrue(
427 len(recipe_list_items) > 0,
428 msg="Any recipes found in the most built recipes list",
429 )
430 rebuild_from_most_build_recipes(recipe_list_items)
431 TestProjectConfigTab.project_id = None # reset project id
432 348
433 def test_project_page_tab_importlayer(self): 349 def test_project_page_tab_importlayer(self):
434 """ Test project page tab import layer """ 350 """ Test project page tab import layer """
@@ -466,42 +382,42 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
466 layers = block_l.find_element(By.ID, 'layer-container') 382 layers = block_l.find_element(By.ID, 'layer-container')
467 layers_list = layers.find_element(By.ID, 'layers-in-project-list') 383 layers_list = layers.find_element(By.ID, 'layers-in-project-list')
468 layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li') 384 layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
469 self.assertTrue( 385 self.assertIn(
470 'meta-fake' in str(layers_list_items[-1].text) 386 'meta-fake', str(layers_list_items[-1].text)
471 ) 387 )
472 388
473 def test_project_page_custom_image_no_image(self): 389 def test_project_page_custom_image_no_image(self):
474 """ Test project page tab "New custom image" when no custom image """ 390 """ Test project page tab "New custom image" when no custom image """
475 project_name = self._random_string(10) 391 project_id = self.create_new_project(self.PROJECT_NAME + "-CustomImage", '3', None, True)
476 self._create_project(project_name=project_name) 392 url = reverse('project', args=(project_id,))
477 current_url = self.driver.current_url 393 self.get(url)
478 TestProjectConfigTab.project_id = get_projectId_from_url(current_url) 394 self.wait_until_visible('#config-nav')
395
479 # navigate to "Custom image" tab 396 # navigate to "Custom image" tab
480 custom_image_section = self._get_config_nav_item(2) 397 custom_image_section = self._get_config_nav_item(2)
481 custom_image_section.click() 398 custom_image_section.click()
482 self.wait_until_visible('#empty-state-customimagestable') 399 self.wait_until_visible('#empty-state-customimagestable')
483 400
484 # Check message when no custom image 401 # Check message when no custom image
485 self.assertTrue( 402 self.assertIn(
486 "You have not created any custom images yet." in str( 403 "You have not created any custom images yet.", str(
487 self.find('#empty-state-customimagestable').text 404 self.find('#empty-state-customimagestable').text
488 ) 405 )
489 ) 406 )
490 div_empty_msg = self.find('#empty-state-customimagestable') 407 div_empty_msg = self.find('#empty-state-customimagestable')
491 link_create_custom_image = div_empty_msg.find_element( 408 link_create_custom_image = div_empty_msg.find_element(
492 By.TAG_NAME, 'a') 409 By.TAG_NAME, 'a')
493 self.assertTrue(TestProjectConfigTab.project_id is not None) 410 self.assertTrue(TestProjectConfigTabBase.project_id is not None)
494 self.assertTrue( 411 self.assertIn(
495 f"/toastergui/project/{TestProjectConfigTab.project_id}/newcustomimage" in str( 412 f"/toastergui/project/{project_id}/newcustomimage", str(
496 link_create_custom_image.get_attribute('href') 413 link_create_custom_image.get_attribute('href')
497 ) 414 )
498 ) 415 )
499 self.assertTrue( 416 self.assertIn(
500 "Create your first custom image" in str( 417 "Create your first custom image", str(
501 link_create_custom_image.text 418 link_create_custom_image.text
502 ) 419 )
503 ) 420 )
504 TestProjectConfigTab.project_id = None # reset project id
505 421
506 def test_project_page_image_recipe(self): 422 def test_project_page_image_recipe(self):
507 """ Test project page section images 423 """ Test project page section images
@@ -526,3 +442,66 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
526 self.wait_until_visible('#imagerecipestable tbody tr') 442 self.wait_until_visible('#imagerecipestable tbody tr')
527 rows = self.find_all('#imagerecipestable tbody tr') 443 rows = self.find_all('#imagerecipestable tbody tr')
528 self.assertTrue(len(rows) > 0) 444 self.assertTrue(len(rows) > 0)
445
446@pytest.mark.django_db
447@pytest.mark.order("last")
448class TestProjectConfigTabDB(TestProjectConfigTabBase):
449
450 def test_most_build_recipes(self):
451 """ Test most build recipes block contains"""
452 def rebuild_from_most_build_recipes(recipe_list_items):
453 checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input')
454 checkbox.click()
455 build_btn = self.find('#freq-build-btn')
456 build_btn.click()
457 self.wait_until_visible('#latest-builds')
458 wait_until_build(self, 'queued cloning starting parsing failed')
459 lastest_builds = self.driver.find_elements(
460 By.XPATH,
461 '//div[@id="latest-builds"]/div'
462 )
463 self.assertTrue(len(lastest_builds) >= 2)
464 last_build = lastest_builds[0]
465 try:
466 cancel_button = last_build.find_element(
467 By.XPATH,
468 '//span[@class="cancel-build-btn pull-right alert-link"]',
469 )
470 cancel_button.click()
471 except NoSuchElementException:
472 # Skip if the build is already cancelled
473 pass
474 wait_until_build_cancelled(self)
475
476 # Create a new project for remaining asserts
477 project_id = self.create_new_project(self.PROJECT_NAME + "-MostBuilt", '2', None, True)
478 url = reverse('project', args=(project_id,))
479 self.get(url)
480 self.wait_until_visible('#config-nav')
481
482 current_url = self.driver.current_url
483 url = current_url.split('?')[0]
484
485 # Create a new builds
486 self._create_builds()
487
488 # back to project page
489 self.driver.get(url)
490
491 self.wait_until_visible('#project-page')
492
493 # Most built recipes
494 most_built_recipes = self.driver.find_element(
495 By.XPATH, '//*[@id="project-page"]/div[1]/div[3]')
496 title = most_built_recipes.find_element(By.TAG_NAME, 'h3')
497 self.assertIn("Most built recipes", title.text)
498 # check can select a recipe and build it
499 self.wait_until_visible('#freq-build-list')
500 recipe_list = self.find('#freq-build-list')
501 recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li')
502 self.assertTrue(
503 len(recipe_list_items) > 0,
504 msg="No recipes found in the most built recipes list",
505 )
506 rebuild_from_most_build_recipes(recipe_list_items)
507
diff --git a/bitbake/lib/toaster/tests/functional/utils.py b/bitbake/lib/toaster/tests/functional/utils.py
index 7269fa1805..72345aef9f 100644
--- a/bitbake/lib/toaster/tests/functional/utils.py
+++ b/bitbake/lib/toaster/tests/functional/utils.py
@@ -8,7 +8,7 @@
8 8
9 9
10from time import sleep 10from time import sleep
11from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, TimeoutException 11from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, TimeoutException, WebDriverException
12from selenium.webdriver.common.by import By 12from selenium.webdriver.common.by import By
13 13
14from orm.models import Build 14from orm.models import Build
@@ -36,7 +36,7 @@ def wait_until_build(test_instance, state):
36 if 'failed' in str(build_state).lower(): 36 if 'failed' in str(build_state).lower():
37 break 37 break
38 except NoSuchElementException: 38 except NoSuchElementException:
39 continue 39 pass
40 except TimeoutException: 40 except TimeoutException:
41 break 41 break
42 start_time += 1 42 start_time += 1
@@ -48,7 +48,6 @@ def wait_until_build_cancelled(test_instance):
48 """ 48 """
49 timeout = 30 49 timeout = 30
50 start_time = 0 50 start_time = 0
51 build = None
52 while True: 51 while True:
53 try: 52 try:
54 if start_time > timeout: 53 if start_time > timeout:
@@ -64,19 +63,17 @@ def wait_until_build_cancelled(test_instance):
64 if 'failed' in str(build_state).lower(): 63 if 'failed' in str(build_state).lower():
65 break 64 break
66 if 'cancelling' in str(build_state).lower(): 65 if 'cancelling' in str(build_state).lower():
67 # Change build state to cancelled 66 pass
68 if not build: # get build object only once
69 build = Build.objects.last()
70 build.outcome = Build.CANCELLED
71 build.save()
72 if 'cancelled' in str(build_state).lower(): 67 if 'cancelled' in str(build_state).lower():
73 break 68 break
74 except NoSuchElementException:
75 continue
76 except StaleElementReferenceException:
77 continue
78 except TimeoutException: 69 except TimeoutException:
79 break 70 break
71 except NoSuchElementException:
72 pass
73 except StaleElementReferenceException:
74 pass
75 except WebDriverException:
76 pass
80 start_time += 1 77 start_time += 1
81 sleep(1) # take a breath and try again 78 sleep(1) # take a breath and try again
82 79
diff --git a/bitbake/lib/toaster/tests/toaster-tests-requirements.txt b/bitbake/lib/toaster/tests/toaster-tests-requirements.txt
index 71cc083436..6243c00a36 100644
--- a/bitbake/lib/toaster/tests/toaster-tests-requirements.txt
+++ b/bitbake/lib/toaster/tests/toaster-tests-requirements.txt
@@ -5,3 +5,5 @@ pytest-env==1.1.0
5pytest-html==4.0.2 5pytest-html==4.0.2
6pytest-metadata==3.0.0 6pytest-metadata==3.0.0
7pytest-order==1.1.0 7pytest-order==1.1.0
8requests
9
diff --git a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py
index bd398f0012..aee9bbcd14 100644
--- a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py
+++ b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py
@@ -233,7 +233,6 @@ def filter_sizeovertotal(package_object, total_size):
233 233
234 return '{:.1%}'.format(float(size)/float(total_size)) 234 return '{:.1%}'.format(float(size)/float(total_size))
235 235
236from django.utils.safestring import mark_safe
237@register.filter 236@register.filter
238def format_vpackage_rowclass(size): 237def format_vpackage_rowclass(size):
239 if size == -1: 238 if size == -1:
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 40aed265dc..061e6436c8 100644
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -372,7 +372,6 @@ def _get_parameters_values(request, default_count, default_order):
372# set cookies for parameters. this is usefull in case parameters are set 372# set cookies for parameters. this is usefull in case parameters are set
373# manually from the GET values of the link 373# manually from the GET values of the link
374def _set_parameters_values(pagesize, orderby, request): 374def _set_parameters_values(pagesize, orderby, request):
375 from django.urls import resolve
376 current_url = resolve(request.path_info).url_name 375 current_url = resolve(request.path_info).url_name
377 request.session['%s_count' % current_url] = pagesize 376 request.session['%s_count' % current_url] = pagesize
378 request.session['%s_orderby' % current_url] =orderby 377 request.session['%s_orderby' % current_url] =orderby
@@ -699,7 +698,6 @@ class LazyEncoder(json.JSONEncoder):
699 return super(LazyEncoder, self).default(obj) 698 return super(LazyEncoder, self).default(obj)
700 699
701from toastergui.templatetags.projecttags import filtered_filesizeformat 700from toastergui.templatetags.projecttags import filtered_filesizeformat
702import os
703def _get_dir_entries(build_id, target_id, start): 701def _get_dir_entries(build_id, target_id, start):
704 node_str = { 702 node_str = {
705 Target_File.ITYPE_REGULAR : '-', 703 Target_File.ITYPE_REGULAR : '-',
diff --git a/bitbake/lib/toaster/toastermain/settings.py b/bitbake/lib/toaster/toastermain/settings.py
index e06adc5a93..d2a449627f 100644
--- a/bitbake/lib/toaster/toastermain/settings.py
+++ b/bitbake/lib/toaster/toastermain/settings.py
@@ -298,7 +298,6 @@ SOUTH_TESTS_MIGRATE = False
298 298
299# We automatically detect and install applications here if 299# We automatically detect and install applications here if
300# they have a 'models.py' or 'views.py' file 300# they have a 'models.py' or 'views.py' file
301import os
302currentdir = os.path.dirname(__file__) 301currentdir = os.path.dirname(__file__)
303for t in os.walk(os.path.dirname(currentdir)): 302for t in os.walk(os.path.dirname(currentdir)):
304 modulename = os.path.basename(t[0]) 303 modulename = os.path.basename(t[0])