blob: 156d639b1e4abdca7922aaf2f27c4e8e5a902d66 [file] [log] [blame]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001#! /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
33import unittest
34
35from django.contrib.staticfiles.testing import StaticLiveServerTestCase
36from selenium import webdriver
37from selenium.webdriver.support.ui import WebDriverWait
38from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
39from selenium.common.exceptions import NoSuchElementException, \
40 StaleElementReferenceException, TimeoutException
41
Brad Bishop6e60e8b2018-02-01 10:27:11 -050042def create_selenium_driver(cls,browser='chrome'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060043 # set default browser string based on env (if available)
44 env_browser = os.environ.get('TOASTER_TESTS_BROWSER')
45 if env_browser:
46 browser = env_browser
47
48 if browser == 'chrome':
49 return webdriver.Chrome(
50 service_args=["--verbose", "--log-path=selenium.log"]
51 )
52 elif browser == 'firefox':
53 return webdriver.Firefox()
54 elif browser == 'marionette':
55 capabilities = DesiredCapabilities.FIREFOX
56 capabilities['marionette'] = True
57 return webdriver.Firefox(capabilities=capabilities)
58 elif browser == 'ie':
59 return webdriver.Ie()
60 elif browser == 'phantomjs':
61 return webdriver.PhantomJS()
Brad Bishop6e60e8b2018-02-01 10:27:11 -050062 elif browser == 'remote':
63 # if we were to add yet another env variable like TOASTER_REMOTE_BROWSER
64 # we could let people pick firefox or chrome, left for later
65 remote_hub= os.environ.get('TOASTER_REMOTE_HUB')
66 driver = webdriver.Remote(remote_hub,
67 webdriver.DesiredCapabilities.FIREFOX.copy())
68
69 driver.get("http://%s:%s"%(cls.server_thread.host,cls.server_thread.port))
70 return driver
Patrick Williamsc0f7c042017-02-23 20:41:17 -060071 else:
72 msg = 'Selenium driver for browser %s is not available' % browser
73 raise RuntimeError(msg)
74
75class Wait(WebDriverWait):
76 """
77 Subclass of WebDriverWait with predetermined timeout and poll
78 frequency. Also deals with a wider variety of exceptions.
79 """
80 _TIMEOUT = 10
81 _POLL_FREQUENCY = 0.5
82
83 def __init__(self, driver):
84 super(Wait, self).__init__(driver, self._TIMEOUT, self._POLL_FREQUENCY)
85
86 def until(self, method, message=''):
87 """
88 Calls the method provided with the driver as an argument until the
89 return value is not False.
90 """
91
92 end_time = time.time() + self._timeout
93 while True:
94 try:
95 value = method(self._driver)
96 if value:
97 return value
98 except NoSuchElementException:
99 pass
100 except StaleElementReferenceException:
101 pass
102
103 time.sleep(self._poll)
104 if time.time() > end_time:
105 break
106
107 raise TimeoutException(message)
108
109 def until_not(self, method, message=''):
110 """
111 Calls the method provided with the driver as an argument until the
112 return value is False.
113 """
114
115 end_time = time.time() + self._timeout
116 while True:
117 try:
118 value = method(self._driver)
119 if not value:
120 return value
121 except NoSuchElementException:
122 return True
123 except StaleElementReferenceException:
124 pass
125
126 time.sleep(self._poll)
127 if time.time() > end_time:
128 break
129
130 raise TimeoutException(message)
131
132class SeleniumTestCaseBase(unittest.TestCase):
133 """
134 NB StaticLiveServerTestCase is used as the base test case so that
135 static files are served correctly in a Selenium test run context; see
136 https://docs.djangoproject.com/en/1.9/ref/contrib/staticfiles/#specialized-test-case-to-support-live-testing
137 """
138
139 @classmethod
140 def setUpClass(cls):
141 """ Create a webdriver driver at the class level """
142
143 super(SeleniumTestCaseBase, cls).setUpClass()
144
145 # instantiate the Selenium webdriver once for all the test methods
146 # in this test case
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500147 cls.driver = create_selenium_driver(cls)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600148 cls.driver.maximize_window()
149
150 @classmethod
151 def tearDownClass(cls):
152 """ Clean up webdriver driver """
153
154 cls.driver.quit()
155 super(SeleniumTestCaseBase, cls).tearDownClass()
156
157 def get(self, url):
158 """
159 Selenium requires absolute URLs, so convert Django URLs returned
160 by resolve() or similar to absolute ones and get using the
161 webdriver instance.
162
163 url: a relative URL
164 """
165 abs_url = '%s%s' % (self.live_server_url, url)
166 self.driver.get(abs_url)
167
168 def find(self, selector):
169 """ Find single element by CSS selector """
170 return self.driver.find_element_by_css_selector(selector)
171
172 def find_all(self, selector):
173 """ Find all elements matching CSS selector """
174 return self.driver.find_elements_by_css_selector(selector)
175
176 def element_exists(self, selector):
177 """
178 Return True if one element matching selector exists,
179 False otherwise
180 """
181 return len(self.find_all(selector)) == 1
182
183 def focused_element(self):
184 """ Return the element which currently has focus on the page """
185 return self.driver.switch_to.active_element
186
187 def wait_until_present(self, selector):
188 """ Wait until element matching CSS selector is on the page """
189 is_present = lambda driver: self.find(selector)
190 msg = 'An element matching "%s" should be on the page' % selector
191 element = Wait(self.driver).until(is_present, msg)
192 return element
193
194 def wait_until_visible(self, selector):
195 """ Wait until element matching CSS selector is visible on the page """
196 is_visible = lambda driver: self.find(selector).is_displayed()
197 msg = 'An element matching "%s" should be visible' % selector
198 Wait(self.driver).until(is_visible, msg)
199 return self.find(selector)
200
201 def wait_until_focused(self, selector):
202 """ Wait until element matching CSS selector has focus """
203 is_focused = \
204 lambda driver: self.find(selector) == self.focused_element()
205 msg = 'An element matching "%s" should be focused' % selector
206 Wait(self.driver).until(is_focused, msg)
207 return self.find(selector)
208
209 def enter_text(self, selector, value):
210 """ Insert text into element matching selector """
211 # note that keyup events don't occur until the element is clicked
212 # (in the case of <input type="text"...>, for example), so simulate
213 # user clicking the element before inserting text into it
214 field = self.click(selector)
215
216 field.send_keys(value)
217 return field
218
219 def click(self, selector):
220 """ Click on element which matches CSS selector """
221 element = self.wait_until_visible(selector)
222 element.click()
223 return element
224
225 def get_page_source(self):
226 """ Get raw HTML for the current page """
227 return self.driver.page_source