blob: 56dbe2b3445952ebb16409dce5fa21a149aea0f5 [file] [log] [blame]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001#! /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 # note that keyup events don't occur until the element is clicked
189 # (in the case of <input type="text"...>, for example), so simulate
190 # user clicking the element before inserting text into it
191 field = self.click(selector)
192
193 field.send_keys(value)
194 return field
195
196 def click(self, selector):
197 """ Click on element which matches CSS selector """
198 element = self.wait_until_visible(selector)
199 element.click()
200 return element
201
202 def get_page_source(self):
203 """ Get raw HTML for the current page """
204 return self.driver.page_source