1#! /usr/bin/env python 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 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 29def create_selenium_driver(cls,browser='chrome'): 30 # 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() 49 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 58 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 134 cls.driver = create_selenium_driver(cls) 135 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 215