summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorElliot Smith <elliot.smith@intel.com>2016-03-31 19:55:43 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2016-04-01 07:14:58 +0100
commitf859a3d40e91e64d4b5292cefd880075a27ddaf9 (patch)
tree31b65d409869857c513a1286459fa6e7d45812c2
parent965c72c38b966ae78a012040159375cec6106eac (diff)
downloadpoky-f859a3d40e91e64d4b5292cefd880075a27ddaf9.tar.gz
bitbake: toaster: tests Migrate to Selenium for UI tests
Create a new folder for Selenium tests. Add a new base Selenium testcase class and a helper which instantiates a webdriver for a given browser. Add a sample Selenium test case which can be used as a template for creating new tests. (Bitbake rev: b7a377aa2ab36390d619e2a0436ccb4b8d186c23) Signed-off-by: Elliot Smith <elliot.smith@intel.com> Signed-off-by: Michael Wood <michael.g.wood@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--bitbake/lib/toaster/tests/__init__.py0
-rw-r--r--bitbake/lib/toaster/tests/browser/README41
-rw-r--r--bitbake/lib/toaster/tests/browser/__init__.py0
-rw-r--r--bitbake/lib/toaster/tests/browser/selenium_helpers.py200
-rw-r--r--bitbake/lib/toaster/tests/browser/test_sample.py41
-rw-r--r--bitbake/lib/toaster/tests/toaster-tests-requirements.txt1
6 files changed, 283 insertions, 0 deletions
diff --git a/bitbake/lib/toaster/tests/__init__.py b/bitbake/lib/toaster/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/bitbake/lib/toaster/tests/__init__.py
diff --git a/bitbake/lib/toaster/tests/browser/README b/bitbake/lib/toaster/tests/browser/README
new file mode 100644
index 0000000000..63e8169c16
--- /dev/null
+++ b/bitbake/lib/toaster/tests/browser/README
@@ -0,0 +1,41 @@
1# Running Toaster's browser-based test suite
2
3These tests require Selenium to be installed in your Python environment.
4
5The simplest way to install this is via pip:
6
7 pip install selenium
8
9Alternatively, if you used pip to install the libraries required by Toaster,
10selenium will already be installed.
11
12To run tests against Chrome:
13
14* Download chromedriver for your host OS from
15 https://code.google.com/p/chromedriver/downloads/list
16* On *nix systems, put chromedriver on PATH
17* On Windows, put chromedriver.exe in the same directory as chrome.exe
18
19To run tests against PhantomJS (headless):
20
21* Download and install PhantomJS:
22 http://phantomjs.org/download.html
23* On *nix systems, put phantomjs on PATH
24* Not tested on Windows
25
26Firefox should work without requiring additional software to be installed.
27
28The test case will instantiate a Selenium driver set by the
29TOASTER_TESTS_BROWSER environment variable, or Chrome if this is not specified.
30
31Available drivers:
32
33* chrome (default)
34* firefox
35* ie
36* phantomjs
37
38e.g. to run the test suite with phantomjs where you have phantomjs installed
39in /home/me/apps/phantomjs:
40
41PATH=/home/me/apps/phantomjs/bin:$PATH TOASTER_TESTS_BROWSER=phantomjs manage.py test tests.browser
diff --git a/bitbake/lib/toaster/tests/browser/__init__.py b/bitbake/lib/toaster/tests/browser/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/bitbake/lib/toaster/tests/browser/__init__.py
diff --git a/bitbake/lib/toaster/tests/browser/selenium_helpers.py b/bitbake/lib/toaster/tests/browser/selenium_helpers.py
new file mode 100644
index 0000000000..d3ab3ca72e
--- /dev/null
+++ b/bitbake/lib/toaster/tests/browser/selenium_helpers.py
@@ -0,0 +1,200 @@
1#! /usr/bin/env python
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# BitBake Toaster Implementation
6#
7# Copyright (C) 2013-2016 Intel Corporation
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21#
22# The Wait class and some of SeleniumDriverHelper and SeleniumTestCase are
23# modified from Patchwork, released under the same licence terms as Toaster:
24# https://github.com/dlespiau/patchwork/blob/master/patchwork/tests.browser.py
25
26"""
27Helper methods for creating Toaster Selenium tests which run within
28the context of Django unit tests.
29"""
30
31import os
32import time
33
34from django.contrib.staticfiles.testing import StaticLiveServerTestCase
35from selenium import webdriver
36from selenium.webdriver.support.ui import WebDriverWait
37from selenium.common.exceptions import NoSuchElementException, \
38 StaleElementReferenceException, TimeoutException
39
40def create_selenium_driver(browser='chrome'):
41 # set default browser string based on env (if available)
42 env_browser = os.environ.get('TOASTER_TESTS_BROWSER')
43 if env_browser:
44 browser = env_browser
45
46 if browser == 'chrome':
47 return webdriver.Chrome(
48 service_args=["--verbose", "--log-path=selenium.log"]
49 )
50 elif browser == 'firefox':
51 return webdriver.Firefox()
52 elif browser == 'ie':
53 return webdriver.Ie()
54 elif browser == 'phantomjs':
55 return webdriver.PhantomJS()
56 else:
57 msg = 'Selenium driver for browser %s is not available' % browser
58 raise RuntimeError(msg)
59
60class Wait(WebDriverWait):
61 """
62 Subclass of WebDriverWait with predetermined timeout and poll
63 frequency. Also deals with a wider variety of exceptions.
64 """
65 _TIMEOUT = 10
66 _POLL_FREQUENCY = 0.5
67
68 def __init__(self, driver):
69 super(Wait, self).__init__(driver, self._TIMEOUT, self._POLL_FREQUENCY)
70
71 def until(self, method, message=''):
72 """
73 Calls the method provided with the driver as an argument until the
74 return value is not False.
75 """
76
77 end_time = time.time() + self._timeout
78 while True:
79 try:
80 value = method(self._driver)
81 if value:
82 return value
83 except NoSuchElementException:
84 pass
85 except StaleElementReferenceException:
86 pass
87
88 time.sleep(self._poll)
89 if time.time() > end_time:
90 break
91
92 raise TimeoutException(message)
93
94 def until_not(self, method, message=''):
95 """
96 Calls the method provided with the driver as an argument until the
97 return value is False.
98 """
99
100 end_time = time.time() + self._timeout
101 while True:
102 try:
103 value = method(self._driver)
104 if not value:
105 return value
106 except NoSuchElementException:
107 return True
108 except StaleElementReferenceException:
109 pass
110
111 time.sleep(self._poll)
112 if time.time() > end_time:
113 break
114
115 raise TimeoutException(message)
116
117class SeleniumTestCase(StaticLiveServerTestCase):
118 """
119 NB StaticLiveServerTestCase is used as the base test case so that
120 static files are served correctly in a Selenium test run context; see
121 https://docs.djangoproject.com/en/1.9/ref/contrib/staticfiles/#specialized-test-case-to-support-live-testing
122 """
123
124 @classmethod
125 def setUpClass(cls):
126 """ Create a webdriver driver at the class level """
127
128 super(SeleniumTestCase, cls).setUpClass()
129
130 # instantiate the Selenium webdriver once for all the test methods
131 # in this test case
132 cls.driver = create_selenium_driver()
133
134 @classmethod
135 def tearDownClass(cls):
136 """ Clean up webdriver driver """
137
138 cls.driver.quit()
139 super(SeleniumTestCase, cls).tearDownClass()
140
141 def get(self, url):
142 """
143 Selenium requires absolute URLs, so convert Django URLs returned
144 by resolve() or similar to absolute ones and get using the
145 webdriver instance.
146
147 url: a relative URL
148 """
149 abs_url = '%s%s' % (self.live_server_url, url)
150 self.driver.get(abs_url)
151
152 def find(self, selector):
153 """ Find single element by CSS selector """
154 return self.driver.find_element_by_css_selector(selector)
155
156 def find_all(self, selector):
157 """ Find all elements matching CSS selector """
158 return self.driver.find_elements_by_css_selector(selector)
159
160 def focused_element(self):
161 """ Return the element which currently has focus on the page """
162 return self.driver.switch_to.active_element
163
164 def wait_until_present(self, selector):
165 """ Wait until element matching CSS selector is on the page """
166 is_present = lambda driver: self.find(selector)
167 msg = 'An element matching "%s" should be on the page' % selector
168 element = Wait(self.driver).until(is_present, msg)
169 return element
170
171 def wait_until_visible(self, selector):
172 """ Wait until element matching CSS selector is visible on the page """
173 is_visible = lambda driver: self.find(selector).is_displayed()
174 msg = 'An element matching "%s" should be visible' % selector
175 Wait(self.driver).until(is_visible, msg)
176 return self.find(selector)
177
178 def wait_until_focused(self, selector):
179 """ Wait until element matching CSS selector has focus """
180 is_focused = \
181 lambda driver: self.find(selector) == self.focused_element()
182 msg = 'An element matching "%s" should be focused' % selector
183 Wait(self.driver).until(is_focused, msg)
184 return self.find(selector)
185
186 def enter_text(self, selector, value):
187 """ Insert text into element matching selector """
188 field = self.wait_until_present(selector)
189 field.send_keys(value)
190 return field
191
192 def click(self, selector):
193 """ Click on element which matches CSS selector """
194 element = self.wait_until_visible(selector)
195 element.click()
196 return element
197
198 def get_page_source(self):
199 """ Get raw HTML for the current page """
200 return self.driver.page_source
diff --git a/bitbake/lib/toaster/tests/browser/test_sample.py b/bitbake/lib/toaster/tests/browser/test_sample.py
new file mode 100644
index 0000000000..7bb8b97e8c
--- /dev/null
+++ b/bitbake/lib/toaster/tests/browser/test_sample.py
@@ -0,0 +1,41 @@
1#! /usr/bin/env python
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# BitBake Toaster Implementation
6#
7# Copyright (C) 2013-2016 Intel Corporation
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22"""
23A small example test demonstrating the basics of writing a test with
24Toaster's SeleniumTestCase; this just fetches the Toaster home page
25and checks it has the word "Toaster" in the brand link
26
27New test files should follow this structure, should be named "test_*.py",
28and should be in the same directory as this sample.
29"""
30
31from django.core.urlresolvers import reverse
32from tests.browser.selenium_helpers import SeleniumTestCase
33
34class TestSample(SeleniumTestCase):
35 """ Test landing page shows the Toaster brand """
36
37 def test_landing_page_has_brand(self):
38 url = reverse('landing')
39 self.get(url)
40 brand_link = self.find('span.brand a')
41 self.assertEqual(brand_link.text.strip(), 'Toaster')
diff --git a/bitbake/lib/toaster/tests/toaster-tests-requirements.txt b/bitbake/lib/toaster/tests/toaster-tests-requirements.txt
new file mode 100644
index 0000000000..4f9fcc46d2
--- /dev/null
+++ b/bitbake/lib/toaster/tests/toaster-tests-requirements.txt
@@ -0,0 +1 @@
selenium==2.49.2