1#! /usr/bin/env python3 2# 3# BitBake Toaster Implementation 4# 5# Copyright (C) 2013-2016 Intel Corporation 6# 7# SPDX-License-Identifier: GPL-2.0-only 8# 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 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 28def create_selenium_driver(cls,browser='chrome'): 29 # 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() 48 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 57 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 133 cls.driver = create_selenium_driver(cls) 134 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 214