1import json 2import os 3import socket 4import traceback 5 6from .base import (Protocol, 7 BaseProtocolPart, 8 RefTestExecutor, 9 RefTestImplementation, 10 TestharnessExecutor, 11 TimedRunner, 12 strip_server) 13from ..webdriver_server import wait_for_service 14 15webdriver = None 16ServoCommandExtensions = None 17 18here = os.path.dirname(__file__) 19 20 21def do_delayed_imports(): 22 global webdriver 23 import webdriver 24 25 global ServoCommandExtensions 26 27 class ServoCommandExtensions(object): 28 def __init__(self, session): 29 self.session = session 30 31 @webdriver.client.command 32 def get_prefs(self, *prefs): 33 body = {"prefs": list(prefs)} 34 return self.session.send_session_command("POST", "servo/prefs/get", body) 35 36 @webdriver.client.command 37 def set_prefs(self, prefs): 38 body = {"prefs": prefs} 39 return self.session.send_session_command("POST", "servo/prefs/set", body) 40 41 @webdriver.client.command 42 def reset_prefs(self, *prefs): 43 body = {"prefs": list(prefs)} 44 return self.session.send_session_command("POST", "servo/prefs/reset", body) 45 46 def change_prefs(self, old_prefs, new_prefs): 47 # Servo interprets reset with an empty list as reset everything 48 if old_prefs: 49 self.reset_prefs(*old_prefs.keys()) 50 self.set_prefs({k: parse_pref_value(v) for k, v in new_prefs.items()}) 51 52 53# See parse_pref_from_command_line() in components/config/opts.rs 54def parse_pref_value(value): 55 if value == "true": 56 return True 57 if value == "false": 58 return False 59 try: 60 return float(value) 61 except ValueError: 62 return value 63 64 65class ServoBaseProtocolPart(BaseProtocolPart): 66 def execute_script(self, script, asynchronous=False): 67 pass 68 69 def set_timeout(self, timeout): 70 pass 71 72 def wait(self): 73 pass 74 75 def set_window(self, handle): 76 pass 77 78 def window_handles(self): 79 return [] 80 81 def load(self, url): 82 pass 83 84 85class ServoWebDriverProtocol(Protocol): 86 implements = [ServoBaseProtocolPart] 87 88 def __init__(self, executor, browser, capabilities, **kwargs): 89 do_delayed_imports() 90 Protocol.__init__(self, executor, browser) 91 self.capabilities = capabilities 92 self.host = browser.webdriver_host 93 self.port = browser.webdriver_port 94 self.init_timeout = browser.init_timeout 95 self.session = None 96 97 def connect(self): 98 """Connect to browser via WebDriver.""" 99 wait_for_service((self.host, self.port), timeout=self.init_timeout) 100 101 self.session = webdriver.Session(self.host, self.port, extension=ServoCommandExtensions) 102 self.session.start() 103 104 def after_connect(self): 105 pass 106 107 def teardown(self): 108 self.logger.debug("Hanging up on WebDriver session") 109 try: 110 self.session.end() 111 except Exception: 112 pass 113 114 def is_alive(self): 115 try: 116 # Get a simple property over the connection 117 self.session.window_handle 118 # TODO what exception? 119 except Exception: 120 return False 121 return True 122 123 def wait(self): 124 while True: 125 try: 126 self.session.execute_async_script("") 127 except webdriver.TimeoutException: 128 pass 129 except (socket.timeout, IOError): 130 break 131 except Exception: 132 self.logger.error(traceback.format_exc()) 133 break 134 135 136class ServoWebDriverRun(TimedRunner): 137 def set_timeout(self): 138 pass 139 140 def run_func(self): 141 try: 142 self.result = True, self.func(self.protocol.session, self.url, self.timeout) 143 except webdriver.TimeoutException: 144 self.result = False, ("EXTERNAL-TIMEOUT", None) 145 except (socket.timeout, IOError): 146 self.result = False, ("CRASH", None) 147 except Exception as e: 148 message = getattr(e, "message", "") 149 if message: 150 message += "\n" 151 message += traceback.format_exc() 152 self.result = False, ("INTERNAL-ERROR", e) 153 finally: 154 self.result_flag.set() 155 156 157class ServoWebDriverTestharnessExecutor(TestharnessExecutor): 158 supports_testdriver = True 159 160 def __init__(self, logger, browser, server_config, timeout_multiplier=1, 161 close_after_done=True, capabilities=None, debug_info=None, 162 **kwargs): 163 TestharnessExecutor.__init__(self, logger, browser, server_config, timeout_multiplier=1, 164 debug_info=None) 165 self.protocol = ServoWebDriverProtocol(self, browser, capabilities=capabilities) 166 with open(os.path.join(here, "testharness_servodriver.js")) as f: 167 self.script = f.read() 168 self.timeout = None 169 170 def on_protocol_change(self, new_protocol): 171 pass 172 173 def is_alive(self): 174 return self.protocol.is_alive() 175 176 def do_test(self, test): 177 url = self.test_url(test) 178 179 timeout = test.timeout * self.timeout_multiplier + self.extra_timeout 180 181 if timeout != self.timeout: 182 try: 183 self.protocol.session.timeouts.script = timeout 184 self.timeout = timeout 185 except IOError: 186 msg = "Lost WebDriver connection" 187 self.logger.error(msg) 188 return ("INTERNAL-ERROR", msg) 189 190 success, data = ServoWebDriverRun(self.logger, 191 self.do_testharness, 192 self.protocol, 193 url, 194 timeout, 195 self.extra_timeout).run() 196 197 if success: 198 return self.convert_result(test, data) 199 200 return (test.result_cls(*data), []) 201 202 def do_testharness(self, session, url, timeout): 203 session.url = url 204 result = json.loads( 205 session.execute_async_script( 206 self.script % {"abs_url": url, 207 "url": strip_server(url), 208 "timeout_multiplier": self.timeout_multiplier, 209 "timeout": timeout * 1000})) 210 # Prevent leaking every page in history until Servo develops a more sane 211 # page cache 212 session.back() 213 return result 214 215 def on_environment_change(self, new_environment): 216 self.protocol.session.extension.change_prefs( 217 self.last_environment.get("prefs", {}), 218 new_environment.get("prefs", {}) 219 ) 220 221 222class TimeoutError(Exception): 223 pass 224 225 226class ServoWebDriverRefTestExecutor(RefTestExecutor): 227 def __init__(self, logger, browser, server_config, timeout_multiplier=1, 228 screenshot_cache=None, capabilities=None, debug_info=None, 229 **kwargs): 230 """Selenium WebDriver-based executor for reftests""" 231 RefTestExecutor.__init__(self, 232 logger, 233 browser, 234 server_config, 235 screenshot_cache=screenshot_cache, 236 timeout_multiplier=timeout_multiplier, 237 debug_info=debug_info) 238 self.protocol = ServoWebDriverProtocol(self, browser, 239 capabilities=capabilities) 240 self.implementation = RefTestImplementation(self) 241 self.timeout = None 242 with open(os.path.join(here, "test-wait.js")) as f: 243 self.wait_script = f.read() % {"classname": "reftest-wait"} 244 245 def reset(self): 246 self.implementation.reset() 247 248 def is_alive(self): 249 return self.protocol.is_alive() 250 251 def do_test(self, test): 252 try: 253 result = self.implementation.run_test(test) 254 return self.convert_result(test, result) 255 except IOError: 256 return test.result_cls("CRASH", None), [] 257 except TimeoutError: 258 return test.result_cls("TIMEOUT", None), [] 259 except Exception as e: 260 message = getattr(e, "message", "") 261 if message: 262 message += "\n" 263 message += traceback.format_exc() 264 return test.result_cls("INTERNAL-ERROR", message), [] 265 266 def screenshot(self, test, viewport_size, dpi, page_ranges): 267 # https://github.com/web-platform-tests/wpt/issues/7135 268 assert viewport_size is None 269 assert dpi is None 270 271 timeout = (test.timeout * self.timeout_multiplier + self.extra_timeout 272 if self.debug_info is None else None) 273 274 if self.timeout != timeout: 275 try: 276 self.protocol.session.timeouts.script = timeout 277 self.timeout = timeout 278 except IOError: 279 msg = "Lost webdriver connection" 280 self.logger.error(msg) 281 return ("INTERNAL-ERROR", msg) 282 283 return ServoWebDriverRun(self.logger, 284 self._screenshot, 285 self.protocol, 286 self.test_url(test), 287 timeout, 288 self.extra_timeout).run() 289 290 def _screenshot(self, session, url, timeout): 291 session.url = url 292 session.execute_async_script(self.wait_script) 293 return session.screenshot() 294 295 def on_environment_change(self, new_environment): 296 self.protocol.session.extension.change_prefs( 297 self.last_environment.get("prefs", {}), 298 new_environment.get("prefs", {}) 299 ) 300