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