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