run.py (38907B)
1 # mypy: allow-untyped-defs 2 3 import argparse 4 import os 5 import platform 6 import subprocess 7 import sys 8 from shutil import copyfile, which 9 from typing import ClassVar, Tuple, Type 10 11 wpt_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) 12 sys.path.insert(0, os.path.abspath(os.path.join(wpt_root, "tools"))) 13 14 from . import browser, install, testfiles 15 from ..serve import serve 16 17 logger = None 18 19 20 class WptrunError(Exception): 21 pass 22 23 24 class WptrunnerHelpAction(argparse.Action): 25 def __init__(self, 26 option_strings, 27 dest=argparse.SUPPRESS, 28 default=argparse.SUPPRESS, 29 help=None): 30 super().__init__( 31 option_strings=option_strings, 32 dest=dest, 33 default=default, 34 nargs=0, 35 help=help) 36 37 def __call__(self, parser, namespace, values, option_string=None): 38 from wptrunner import wptcommandline 39 wptparser = wptcommandline.create_parser() 40 wptparser.usage = parser.usage 41 wptparser.print_help() 42 parser.exit() 43 44 45 def create_parser(): 46 from wptrunner import wptcommandline 47 48 parser = argparse.ArgumentParser(add_help=False, parents=[install.channel_args]) 49 parser.add_argument("product", help="Browser to run tests in") 50 parser.add_argument("--affected", help="Run affected tests since revish") 51 parser.add_argument("--yes", "-y", dest="prompt", action="store_false", 52 help="Don't prompt before installing components") 53 parser.add_argument("--install-browser", action="store_true", 54 help="Install the browser from the release channel specified by --channel " 55 "(or the nightly channel by default).") 56 parser.add_argument("--install-webdriver", action="store_true", 57 help="Install WebDriver from the release channel specified by --channel " 58 "(or the nightly channel by default).") 59 parser.add_argument("--logcat-dir", 60 help="Directory to write Android logcat files to") 61 parser._add_container_actions(wptcommandline.create_parser()) 62 return parser 63 64 65 def exit(msg=None): 66 if msg: 67 logger.critical(msg) 68 sys.exit(1) 69 else: 70 sys.exit(0) 71 72 73 def args_general(kwargs): 74 75 def set_if_none(name, value): 76 if kwargs.get(name) is None: 77 kwargs[name] = value 78 logger.info("Set %s to %s" % (name, value)) 79 80 set_if_none("tests_root", wpt_root) 81 set_if_none("metadata_root", wpt_root) 82 set_if_none("manifest_update", True) 83 set_if_none("manifest_download", True) 84 85 if kwargs["ssl_type"] in (None, "pregenerated"): 86 cert_root = os.path.join(wpt_root, "tools", "certs") 87 if kwargs["ca_cert_path"] is None: 88 kwargs["ca_cert_path"] = os.path.join(cert_root, "cacert.pem") 89 90 if kwargs["host_key_path"] is None: 91 kwargs["host_key_path"] = os.path.join(cert_root, "web-platform.test.key") 92 93 if kwargs["host_cert_path"] is None: 94 kwargs["host_cert_path"] = os.path.join(cert_root, "web-platform.test.pem") 95 elif kwargs["ssl_type"] == "openssl": 96 if not which(kwargs["openssl_binary"]): 97 if os.uname()[0] == "Windows": 98 raise WptrunError("""OpenSSL binary not found. If you need HTTPS tests, install OpenSSL from 99 100 https://slproweb.com/products/Win32OpenSSL.html 101 102 Ensuring that libraries are added to /bin and add the resulting bin directory to 103 your PATH. 104 105 Otherwise run with --ssl-type=none""") 106 else: 107 raise WptrunError("""OpenSSL not found. If you don't need HTTPS support run with --ssl-type=none, 108 otherwise install OpenSSL and ensure that it's on your $PATH.""") 109 110 111 def check_environ(product): 112 if product not in ("android_webview", "chrome", "chrome_android", "chrome_ios", 113 "edge", "firefox", "firefox_android", "headless_shell", 114 "ladybird", "servo", "wktr"): 115 config_builder = serve.build_config(os.path.join(wpt_root, "config.json")) 116 # Override the ports to avoid looking for free ports 117 config_builder.ssl = {"type": "none"} 118 config_builder.ports = {"http": [8000]} 119 120 is_windows = platform.uname()[0] == "Windows" 121 122 with config_builder as config: 123 expected_hosts = set(config.domains_set) 124 if is_windows: 125 expected_hosts.update(config.not_domains_set) 126 127 missing_hosts = set(expected_hosts) 128 if is_windows: 129 hosts_path = r"%s\System32\drivers\etc\hosts" % os.environ.get( 130 "SystemRoot", r"C:\Windows") 131 else: 132 hosts_path = "/etc/hosts" 133 134 if os.path.abspath(os.curdir) == wpt_root: 135 wpt_path = "wpt" 136 else: 137 wpt_path = os.path.join(wpt_root, "wpt") 138 139 with open(hosts_path) as f: 140 for line in f: 141 line = line.split("#", 1)[0].strip() 142 parts = line.split() 143 hosts = parts[1:] 144 for host in hosts: 145 missing_hosts.discard(host) 146 if missing_hosts: 147 if is_windows: 148 message = """Missing hosts file configuration. Run 149 150 python %s make-hosts-file | Out-File %s -Encoding ascii -Append 151 152 in PowerShell with Administrator privileges.""" % (wpt_path, hosts_path) 153 else: 154 message = """Missing hosts file configuration. Run 155 156 %s make-hosts-file | sudo tee -a %s""" % ("./wpt" if wpt_path == "wpt" else wpt_path, 157 hosts_path) 158 raise WptrunError(message) 159 160 161 class AndroidLogcat: 162 def __init__(self, adb_path, base_path=None): 163 self.adb_path = adb_path 164 self.base_path = base_path if base_path is not None else os.curdir 165 self.procs = {} 166 167 def start(self, device_serial): 168 """ 169 Start recording logcat. Writes logcat to the upload directory. 170 """ 171 # Start logcat for the device. The adb process runs until the 172 # corresponding device is stopped. Output is written directly to 173 # the blobber upload directory so that it is uploaded automatically 174 # at the end of the job. 175 if device_serial in self.procs: 176 logger.warning(f"Logcat for {device_serial} already started") 177 return 178 179 logcat_path = os.path.join(self.base_path, f"logcat-{device_serial}.log") 180 out_file = open(logcat_path, "w") 181 cmd = [ 182 self.adb_path, 183 "-s", 184 device_serial, 185 "logcat", 186 "-v", 187 "threadtime", 188 "Trace:S", 189 "StrictMode:S", 190 "ExchangeService:S", 191 ] 192 logger.debug(" ".join(cmd)) 193 proc = subprocess.Popen( 194 cmd, stdout=out_file, stdin=subprocess.PIPE 195 ) 196 logger.info(f"Started logcat for device {device_serial} pid {proc.pid}") 197 self.procs[device_serial] = (proc, out_file) 198 199 def stop(self, device_serial=None): 200 """ 201 Stop logcat process started by logcat_start. 202 """ 203 if device_serial is None: 204 for key in list(self.procs.keys()): 205 self.stop(key) 206 return 207 208 proc, out_file = self.procs.get(device_serial, (None, None)) 209 if proc is not None: 210 try: 211 proc.kill() 212 out_file.close() 213 finally: 214 del self.procs[device_serial] 215 216 217 class BrowserSetup: 218 name: ClassVar[str] 219 browser_cls: ClassVar[Type[browser.Browser]] 220 221 def __init__(self, venv, prompt=True): 222 self.browser = self.browser_cls(logger) 223 self.venv = venv 224 self.prompt = prompt 225 226 def prompt_install(self, component): 227 if not self.prompt: 228 return True 229 while True: 230 resp = input("Download and install %s [Y/n]? " % component).strip().lower() 231 if not resp or resp == "y": 232 return True 233 elif resp == "n": 234 return False 235 236 def install(self, channel=None): 237 if self.prompt_install(self.name): 238 return self.browser.install(self.venv.path, channel) 239 240 def requirements(self): 241 if self.browser.requirements: 242 return [os.path.join(wpt_root, "tools", "wptrunner", self.browser.requirements)] 243 return [] 244 245 def setup(self, kwargs): 246 self.setup_kwargs(kwargs) 247 248 def teardown(self): 249 pass 250 251 252 def safe_unsetenv(env_var): 253 """Safely remove an environment variable. 254 255 Python3 does not support os.unsetenv in Windows for python<3.9, so we better 256 remove the variable directly from os.environ. 257 """ 258 try: 259 del os.environ[env_var] 260 except KeyError: 261 pass 262 263 264 class Firefox(BrowserSetup): 265 name = "firefox" 266 browser_cls = browser.Firefox 267 268 def setup_kwargs(self, kwargs): 269 if kwargs["binary"] is None: 270 if kwargs["browser_channel"] is None: 271 kwargs["browser_channel"] = "nightly" 272 logger.info("No browser channel specified. Running nightly instead.") 273 274 binary = self.browser.find_binary(self.venv.path, 275 kwargs["browser_channel"]) 276 if binary is None: 277 raise WptrunError("""Firefox binary not found on $PATH. 278 279 Install Firefox or use --binary to set the binary path""") 280 kwargs["binary"] = binary 281 282 if kwargs["certutil_binary"] is None and kwargs["ssl_type"] != "none": 283 certutil = self.browser.find_certutil() 284 285 if certutil is None: 286 # Can't download this for now because it's missing the libnss3 library 287 logger.info("""Can't find certutil, certificates will not be checked. 288 Consider installing certutil via your OS package manager or directly.""") 289 else: 290 logger.info("Using certutil %s" % certutil) 291 292 kwargs["certutil_binary"] = certutil 293 294 if kwargs["webdriver_binary"] is None and "wdspec" in kwargs["test_types"]: 295 webdriver_binary = None 296 if not kwargs["install_webdriver"]: 297 webdriver_binary = self.browser.find_webdriver() 298 299 if webdriver_binary is None: 300 install = self.prompt_install("geckodriver") 301 302 if install: 303 logger.info("Downloading geckodriver") 304 webdriver_binary = self.browser.install_webdriver( 305 dest=self.venv.bin_path, 306 channel=kwargs["browser_channel"], 307 browser_binary=kwargs["binary"]) 308 else: 309 logger.info("Using webdriver binary %s" % webdriver_binary) 310 311 if webdriver_binary: 312 kwargs["webdriver_binary"] = webdriver_binary 313 else: 314 logger.info("Unable to find or install geckodriver, skipping wdspec tests") 315 kwargs["test_types"].remove("wdspec") 316 317 if kwargs["prefs_root"] is None: 318 prefs_root = self.browser.install_prefs(kwargs["binary"], 319 self.venv.path, 320 channel=kwargs["browser_channel"]) 321 kwargs["prefs_root"] = prefs_root 322 323 if kwargs["headless"] is None and not kwargs["debug_test"]: 324 kwargs["headless"] = True 325 logger.info("Running in headless mode, pass --no-headless to disable") 326 327 if kwargs["browser_channel"] == "nightly" and kwargs["enable_webtransport_h3"] is None: 328 kwargs["enable_webtransport_h3"] = True 329 330 # Turn off Firefox WebRTC ICE logging on WPT (turned on by mozrunner) 331 safe_unsetenv('R_LOG_LEVEL') 332 safe_unsetenv('R_LOG_DESTINATION') 333 safe_unsetenv('R_LOG_VERBOSE') 334 335 # Allow WebRTC tests to call getUserMedia. 336 kwargs["extra_prefs"].append("media.navigator.streams.fake=true") 337 338 kwargs["enable_webtransport_h3"] = True 339 340 class FirefoxAndroid(BrowserSetup): 341 name = "firefox_android" 342 browser_cls = browser.FirefoxAndroid 343 344 def setup_kwargs(self, kwargs): 345 from . import android 346 import mozdevice 347 348 # We don't support multiple channels for android yet 349 if kwargs["browser_channel"] is None: 350 kwargs["browser_channel"] = "nightly" 351 352 if kwargs["prefs_root"] is None: 353 prefs_root = self.browser.install_prefs(kwargs["binary"], 354 self.venv.path, 355 channel=kwargs["browser_channel"]) 356 kwargs["prefs_root"] = prefs_root 357 358 if kwargs["package_name"] is None: 359 kwargs["package_name"] = "org.mozilla.geckoview.test_runner" 360 app = kwargs["package_name"] 361 362 if not kwargs["device_serial"]: 363 kwargs["device_serial"] = ["emulator-5554"] 364 365 if kwargs["webdriver_binary"] is None and "wdspec" in kwargs["test_types"]: 366 webdriver_binary = None 367 if not kwargs["install_webdriver"]: 368 webdriver_binary = self.browser.find_webdriver() 369 370 if webdriver_binary is None: 371 install = self.prompt_install("geckodriver") 372 373 if install: 374 logger.info("Downloading geckodriver") 375 webdriver_binary = self.browser.install_webdriver( 376 dest=self.venv.bin_path, 377 channel=kwargs["browser_channel"], 378 browser_binary=kwargs["binary"]) 379 else: 380 logger.info("Using webdriver binary %s" % webdriver_binary) 381 382 if webdriver_binary: 383 kwargs["webdriver_binary"] = webdriver_binary 384 else: 385 logger.info("Unable to find or install geckodriver, skipping wdspec tests") 386 kwargs["test_types"].remove("wdspec") 387 388 if kwargs["adb_binary"] is None: 389 if "ADB_PATH" not in os.environ: 390 adb_path = os.path.join(android.get_paths(None)["sdk"], 391 "platform-tools", 392 "adb") 393 os.environ["ADB_PATH"] = adb_path 394 kwargs["adb_binary"] = os.environ["ADB_PATH"] 395 396 self._logcat = AndroidLogcat(kwargs["adb_binary"], base_path=kwargs["logcat_dir"]) 397 398 for device_serial in kwargs["device_serial"]: 399 if device_serial.startswith("emulator-"): 400 # We're running on an emulator so ensure that's set up 401 android.start(logger, 402 reinstall=False, 403 device_serial=device_serial, 404 prompt=kwargs["prompt"]) 405 406 for device_serial in kwargs["device_serial"]: 407 device = mozdevice.ADBDeviceFactory(adb=kwargs["adb_binary"], 408 device=device_serial) 409 self._logcat.start(device_serial) 410 max_retries = 5 411 last_exception = None 412 if self.browser.apk_path: 413 device.uninstall_app(app) 414 for i in range(max_retries + 1): 415 logger.info(f"Installing {app} on {device_serial} " 416 f"attempt {i + 1}/{max_retries + 1}") 417 try: 418 # Temporarily replace mozdevice function with custom code 419 # that passes in the `--no-incremental` option 420 cmd = ["install", "--no-incremental", self.browser.apk_path] 421 logger.debug(" ".join(cmd)) 422 data = device.command_output(cmd, timeout=120) 423 if data.find("Success") == -1: 424 raise mozdevice.ADBError(f"Install failed for {self.browser.apk_path}." 425 f" Got: {data}") 426 except Exception as e: 427 last_exception = e 428 else: 429 break 430 else: 431 assert last_exception is not None 432 raise WptrunError(f"Failed to install {app} on device {device_serial} " 433 f"after {max_retries} retries") from last_exception 434 elif not device.is_app_installed(app): 435 raise WptrunError(f"app {app} not installed on device {device_serial}") 436 437 kwargs["enable_webtransport_h3"] = True 438 439 def teardown(self): 440 from . import android 441 442 if hasattr(self, "_logcat"): 443 emulator_log = os.path.join(android.get_paths(None)["sdk"], 444 ".android", 445 "emulator.log") 446 if os.path.exists(emulator_log): 447 dest_path = os.path.join(self._logcat.base_path, "emulator.log") 448 copyfile(emulator_log, dest_path) 449 450 self._logcat.stop() 451 452 453 class Chrome(BrowserSetup): 454 name = "chrome" 455 browser_cls: ClassVar[Type[browser.ChromeChromiumBase]] = browser.Chrome 456 experimental_channels: ClassVar[Tuple[str, ...]] = ("dev", "canary") 457 458 def setup_kwargs(self, kwargs): 459 browser_channel = kwargs["browser_channel"] 460 if kwargs["binary"] is None: 461 binary = self.browser.find_binary(venv_path=self.venv.path, channel=browser_channel) 462 if binary: 463 kwargs["binary"] = binary 464 else: 465 raise WptrunError(f"Unable to locate {self.name.capitalize()} binary") 466 467 if kwargs["mojojs_path"]: 468 kwargs["enable_mojojs"] = True 469 logger.info("--mojojs-path is provided, enabling MojoJS") 470 else: 471 path = self.browser.install_mojojs(dest=self.venv.path, 472 browser_binary=kwargs["binary"]) 473 if path: 474 kwargs["mojojs_path"] = path 475 kwargs["enable_mojojs"] = True 476 logger.info(f"MojoJS enabled automatically (mojojs_path: {path})") 477 else: 478 kwargs["enable_mojojs"] = False 479 logger.info("MojoJS is disabled for this run.") 480 481 if kwargs["webdriver_binary"] is None: 482 webdriver_binary = None 483 if not kwargs["install_webdriver"]: 484 webdriver_binary = self.browser.find_webdriver(self.venv.bin_path) 485 if webdriver_binary and not self.browser.webdriver_supports_browser( 486 webdriver_binary, kwargs["binary"], browser_channel): 487 webdriver_binary = None 488 489 if webdriver_binary is None: 490 install = self.prompt_install("chromedriver") 491 492 if install: 493 webdriver_binary = self.browser.install_webdriver( 494 dest=self.venv.bin_path, 495 channel=browser_channel, 496 browser_binary=kwargs["binary"], 497 ) 498 else: 499 logger.info("Using webdriver binary %s" % webdriver_binary) 500 501 if webdriver_binary: 502 kwargs["webdriver_binary"] = webdriver_binary 503 else: 504 raise WptrunError("Unable to locate or install matching ChromeDriver binary") 505 if kwargs["headless"] is None and not kwargs["debug_test"]: 506 kwargs["headless"] = True 507 logger.info("Running in headless mode, pass --no-headless to disable") 508 if browser_channel in self.experimental_channels: 509 # HACK(Hexcles): work around https://github.com/web-platform-tests/wpt/issues/16448 510 kwargs["webdriver_args"].append("--disable-build-check") 511 if kwargs["enable_experimental"] is None: 512 logger.info( 513 "Automatically turning on experimental features for Chrome Dev/Canary or Chromium trunk") 514 kwargs["enable_experimental"] = True 515 if kwargs["enable_webtransport_h3"] is None: 516 # To start the WebTransport over HTTP/3 test server. 517 kwargs["enable_webtransport_h3"] = True 518 elif browser_channel is not None: 519 # browser_channel is not set when running WPT in chromium 520 kwargs["enable_experimental"] = False 521 if os.getenv("TASKCLUSTER_ROOT_URL"): 522 # We are on Taskcluster, where our Docker container does not have 523 # enough capabilities to run Chrome with sandboxing. (gh-20133) 524 kwargs["binary_args"].append("--no-sandbox") 525 526 527 class HeadlessShell(BrowserSetup): 528 name = "headless_shell" 529 browser_cls = browser.HeadlessShell 530 experimental_channels = ("dev", "canary", "nightly") 531 532 def setup_kwargs(self, kwargs): 533 browser_channel = kwargs["browser_channel"] 534 if kwargs["binary"] is None: 535 binary = self.browser.find_binary(venv_path=self.venv.path, channel=browser_channel) 536 if binary: 537 kwargs["binary"] = binary 538 else: 539 raise WptrunError(f"Unable to locate {self.name!r} binary") 540 541 if kwargs["mojojs_path"]: 542 kwargs["enable_mojojs"] = True 543 logger.info("--mojojs-path is provided, enabling MojoJS") 544 elif kwargs["enable_mojojs"]: 545 logger.warning(f"Cannot install MojoJS for {self.name}, " 546 "which does not return version information. " 547 "Provide '--mojojs-path' explicitly instead.") 548 logger.warning("MojoJS is disabled for this run.") 549 550 # Never pause after test, since headless shell is not interactive. 551 kwargs["pause_after_test"] = False 552 # Don't add a `--headless` switch. 553 kwargs["headless"] = False 554 555 if kwargs["enable_webtransport_h3"] is None: 556 kwargs["enable_webtransport_h3"] = True 557 558 559 class Chromium(Chrome): 560 name = "chromium" 561 browser_cls: ClassVar[Type[browser.ChromeChromiumBase]] = browser.Chromium 562 experimental_channels = ("nightly",) 563 564 565 class ChromeAndroidBase(BrowserSetup): 566 experimental_channels = ("dev", "canary") 567 568 def setup_kwargs(self, kwargs): 569 if kwargs.get("device_serial"): 570 self.browser.device_serial = kwargs["device_serial"] 571 if kwargs.get("adb_binary"): 572 self.browser.adb_binary = kwargs["adb_binary"] 573 browser_channel = kwargs["browser_channel"] 574 if kwargs["package_name"] is None: 575 kwargs["package_name"] = self.browser.find_binary( 576 channel=browser_channel) 577 if not kwargs["device_serial"]: 578 kwargs["device_serial"] = ["emulator-5554"] 579 if kwargs["webdriver_binary"] is None: 580 webdriver_binary = None 581 if not kwargs["install_webdriver"]: 582 webdriver_binary = self.browser.find_webdriver() 583 584 if webdriver_binary is None: 585 install = self.prompt_install("chromedriver") 586 587 if install: 588 logger.info("Downloading chromedriver") 589 webdriver_binary = self.browser.install_webdriver( 590 dest=self.venv.bin_path, 591 channel=browser_channel, 592 browser_binary=kwargs["package_name"], 593 ) 594 else: 595 logger.info("Using webdriver binary %s" % webdriver_binary) 596 597 if webdriver_binary: 598 kwargs["webdriver_binary"] = webdriver_binary 599 else: 600 raise WptrunError("Unable to locate or install chromedriver binary") 601 602 603 class ChromeAndroid(ChromeAndroidBase): 604 name = "chrome_android" 605 browser_cls = browser.ChromeAndroid 606 607 def setup_kwargs(self, kwargs): 608 super().setup_kwargs(kwargs) 609 if kwargs["browser_channel"] in self.experimental_channels: 610 # HACK(Hexcles): work around https://github.com/web-platform-tests/wpt/issues/16448 611 kwargs["webdriver_args"].append("--disable-build-check") 612 if kwargs["enable_experimental"] is None: 613 logger.info("Automatically turning on experimental features for Chrome Dev/Canary") 614 kwargs["enable_experimental"] = True 615 616 617 class ChromeiOS(BrowserSetup): 618 name = "chrome_ios" 619 browser_cls = browser.ChromeiOS 620 621 def setup_kwargs(self, kwargs): 622 if kwargs["webdriver_binary"] is None: 623 raise WptrunError("Unable to locate or install chromedriver binary") 624 625 626 class AndroidWebview(ChromeAndroidBase): 627 name = "android_webview" 628 browser_cls = browser.AndroidWebview 629 630 def setup_kwargs(self, kwargs): 631 if kwargs["mojojs_path"]: 632 kwargs["enable_mojojs"] = True 633 logger.info("--mojojs-path is provided, enabling MojoJS") 634 635 636 class Opera(BrowserSetup): 637 name = "opera" 638 browser_cls = browser.Opera 639 640 def setup_kwargs(self, kwargs): 641 if kwargs["webdriver_binary"] is None: 642 webdriver_binary = None 643 if not kwargs["install_webdriver"]: 644 webdriver_binary = self.browser.find_webdriver() 645 646 if webdriver_binary is None: 647 install = self.prompt_install("operadriver") 648 649 if install: 650 logger.info("Downloading operadriver") 651 webdriver_binary = self.browser.install_webdriver( 652 dest=self.venv.bin_path, 653 channel=kwargs["browser_channel"]) 654 else: 655 logger.info("Using webdriver binary %s" % webdriver_binary) 656 657 if webdriver_binary: 658 kwargs["webdriver_binary"] = webdriver_binary 659 else: 660 raise WptrunError("Unable to locate or install operadriver binary") 661 662 663 class Edge(BrowserSetup): 664 name = "MicrosoftEdge" 665 browser_cls = browser.Edge 666 experimental_channels: ClassVar[Tuple[str, ...]] = ("dev", "canary") 667 668 def setup_kwargs(self, kwargs): 669 browser_channel = kwargs["browser_channel"] 670 if kwargs["binary"] is None: 671 binary = self.browser.find_binary(venv_path=self.venv.path, channel=browser_channel) 672 if binary: 673 kwargs["binary"] = binary 674 else: 675 raise WptrunError(f"Unable to locate {self.name.capitalize()} binary") 676 677 if kwargs["mojojs_path"]: 678 kwargs["enable_mojojs"] = True 679 logger.info("--mojojs-path is provided, enabling MojoJS") 680 else: 681 path = self.browser.install_mojojs(dest=self.venv.path, 682 browser_binary=kwargs["binary"]) 683 if path: 684 kwargs["mojojs_path"] = path 685 kwargs["enable_mojojs"] = True 686 logger.info(f"MojoJS enabled automatically (mojojs_path: {path})") 687 else: 688 kwargs["enable_mojojs"] = False 689 logger.info("MojoJS is disabled for this run.") 690 691 if kwargs["webdriver_binary"] is None: 692 webdriver_binary = None 693 if not kwargs["install_webdriver"]: 694 webdriver_binary = self.browser.find_webdriver(self.venv.bin_path) 695 if webdriver_binary and not self.browser.webdriver_supports_browser( 696 webdriver_binary, kwargs["binary"], browser_channel): 697 webdriver_binary = None 698 699 if webdriver_binary is None: 700 install = self.prompt_install("msedgedriver") 701 702 if install: 703 webdriver_binary = self.browser.install_webdriver( 704 dest=self.venv.bin_path, 705 channel=browser_channel, 706 browser_binary=kwargs["binary"], 707 ) 708 else: 709 logger.info("Using webdriver binary %s" % webdriver_binary) 710 711 if webdriver_binary: 712 kwargs["webdriver_binary"] = webdriver_binary 713 else: 714 raise WptrunError("Unable to locate or install matching msedgedriver binary") 715 if browser_channel in self.experimental_channels: 716 # HACK(Hexcles): work around https://github.com/web-platform-tests/wpt/issues/16448 717 kwargs["webdriver_args"].append("--disable-build-check") 718 if kwargs["enable_experimental"] is None: 719 logger.info( 720 "Automatically turning on experimental features for Microsoft Edge Dev/Canary") 721 kwargs["enable_experimental"] = True 722 if kwargs["enable_webtransport_h3"] is None: 723 # To start the WebTransport over HTTP/3 test server. 724 kwargs["enable_webtransport_h3"] = True 725 if os.getenv("TASKCLUSTER_ROOT_URL"): 726 # We are on Taskcluster, where our Docker container does not have 727 # enough capabilities to run Microsoft Edge with sandboxing. (gh-20133) 728 kwargs["binary_args"].append("--no-sandbox") 729 730 731 class Safari(BrowserSetup): 732 name = "safari" 733 browser_cls = browser.Safari 734 735 def install(self, channel=None): 736 raise NotImplementedError 737 738 def setup_kwargs(self, kwargs): 739 if kwargs["webdriver_binary"] is None: 740 webdriver_binary = self.browser.find_webdriver(channel=kwargs["browser_channel"]) 741 742 if webdriver_binary is None: 743 raise WptrunError("Unable to locate safaridriver binary") 744 745 kwargs["webdriver_binary"] = webdriver_binary 746 747 748 class Sauce(BrowserSetup): 749 name = "sauce" 750 browser_cls = browser.Sauce 751 752 def install(self, channel=None): 753 raise NotImplementedError 754 755 def setup_kwargs(self, kwargs): 756 if kwargs["sauce_browser"] is None: 757 raise WptrunError("Missing required argument --sauce-browser") 758 if kwargs["sauce_version"] is None: 759 raise WptrunError("Missing required argument --sauce-version") 760 kwargs["test_types"] = ["testharness", "reftest"] 761 762 763 class Servo(BrowserSetup): 764 name = "servo" 765 browser_cls = browser.Servo 766 767 def install(self, channel=None): 768 if self.prompt_install(self.name): 769 return self.browser.install(self.venv.path) 770 771 def setup_kwargs(self, kwargs): 772 if kwargs["binary"] is None: 773 binary = self.browser.find_binary(self.venv.path, None) 774 775 if binary is None: 776 raise WptrunError("Unable to find servo binary in PATH") 777 kwargs["binary"] = binary 778 779 780 class ServoWebDriver(Servo): 781 name = "servodriver" 782 browser_cls = browser.ServoWebDriver 783 784 785 class WebKit(BrowserSetup): 786 name = "webkit" 787 browser_cls = browser.WebKit 788 789 def install(self, channel=None): 790 raise NotImplementedError 791 792 def setup_kwargs(self, kwargs): 793 pass 794 795 class Ladybird(BrowserSetup): 796 name = "ladybird" 797 browser_cls = browser.Ladybird 798 799 def install(self, channel=None): 800 raise NotImplementedError 801 802 def setup_kwargs(self, kwargs): 803 pass 804 805 class WebKitTestRunner(BrowserSetup): 806 name = "wktr" 807 browser_cls = browser.WebKitTestRunner 808 809 def install(self, channel=None): 810 if self.prompt_install(self.name): 811 return self.browser.install(self.venv.path, channel=channel) 812 813 def setup_kwargs(self, kwargs): 814 if kwargs["binary"] is None: 815 binary = self.browser.find_binary(self.venv.path, channel=kwargs["browser_channel"]) 816 817 if binary is None: 818 raise WptrunError("Unable to find binary in PATH") 819 kwargs["binary"] = binary 820 821 822 class WebKitGlibBaseMiniBrowser(BrowserSetup): 823 """ Base class for WebKitGTKMiniBrowser and WPEWebKitMiniBrowser """ 824 825 def install(self, channel=None): 826 if self.prompt_install(self.name): 827 return self.browser.install(self.venv.path, channel, self.prompt) 828 829 def setup_kwargs(self, kwargs): 830 if kwargs["binary"] is None: 831 binary = self.browser.find_binary( 832 venv_path=self.venv.path, channel=kwargs["browser_channel"]) 833 834 if binary is None: 835 raise WptrunError("Unable to find MiniBrowser binary") 836 kwargs["binary"] = binary 837 838 if kwargs["webdriver_binary"] is None: 839 webdriver_binary = self.browser.find_webdriver( 840 venv_path=self.venv.path, channel=kwargs["browser_channel"]) 841 842 if webdriver_binary is None: 843 raise WptrunError('Unable to find "%s" binary in PATH' % self.browser_cls.WEBDRIVER_BINARY_NAME) 844 kwargs["webdriver_binary"] = webdriver_binary 845 846 847 class WebKitGTKMiniBrowser(WebKitGlibBaseMiniBrowser): 848 name = "webkitgtk_minibrowser" 849 browser_cls = browser.WebKitGTKMiniBrowser 850 851 852 class WPEWebKitMiniBrowser(WebKitGlibBaseMiniBrowser): 853 name = "wpewebkit_minibrowser" 854 browser_cls = browser.WPEWebKitMiniBrowser 855 856 def setup_kwargs(self, kwargs): 857 if kwargs["headless"]: 858 kwargs["binary_args"].append("--headless") 859 super().setup_kwargs(kwargs) 860 861 862 class Epiphany(BrowserSetup): 863 name = "epiphany" 864 browser_cls = browser.Epiphany 865 866 def install(self, channel=None): 867 raise NotImplementedError 868 869 def setup_kwargs(self, kwargs): 870 if kwargs["binary"] is None: 871 binary = self.browser.find_binary() 872 873 if binary is None: 874 raise WptrunError("Unable to find epiphany in PATH") 875 kwargs["binary"] = binary 876 877 if kwargs["webdriver_binary"] is None: 878 webdriver_binary = self.browser.find_webdriver() 879 880 if webdriver_binary is None: 881 raise WptrunError("Unable to find WebKitWebDriver in PATH") 882 kwargs["webdriver_binary"] = webdriver_binary 883 884 885 product_setup = { 886 "android_webview": AndroidWebview, 887 "firefox": Firefox, 888 "firefox_android": FirefoxAndroid, 889 "chrome": Chrome, 890 "chrome_android": ChromeAndroid, 891 "chrome_ios": ChromeiOS, 892 "chromium": Chromium, 893 "edge": Edge, 894 "headless_shell": HeadlessShell, 895 "safari": Safari, 896 "servo": Servo, 897 "servodriver": ServoWebDriver, 898 "sauce": Sauce, 899 "opera": Opera, 900 "webkit": WebKit, 901 "wktr": WebKitTestRunner, 902 "webkitgtk_minibrowser": WebKitGTKMiniBrowser, 903 "wpewebkit_minibrowser": WPEWebKitMiniBrowser, 904 "epiphany": Epiphany, 905 "ladybird": Ladybird, 906 } 907 908 909 def setup_logging(kwargs, default_config=None, formatter_defaults=None): 910 import mozlog 911 from wptrunner import wptrunner 912 913 global logger 914 915 # Use the grouped formatter by default where mozlog 3.9+ is installed 916 if default_config is None: 917 if hasattr(mozlog.formatters, "GroupingFormatter"): 918 default_formatter = "grouped" 919 else: 920 default_formatter = "mach" 921 default_config = {default_formatter: sys.stdout} 922 wptrunner.setup_logging(kwargs, default_config, formatter_defaults=formatter_defaults) 923 logger = wptrunner.logger 924 return logger 925 926 927 def setup_wptrunner(venv, **kwargs): 928 from wptrunner import wptcommandline 929 930 kwargs = kwargs.copy() 931 932 kwargs["product"] = kwargs["product"].replace("-", "_") 933 934 check_environ(kwargs["product"]) 935 args_general(kwargs) 936 937 if kwargs["product"] not in product_setup: 938 if kwargs["product"] == "edgechromium": 939 raise WptrunError("edgechromium has been renamed to edge.") 940 941 raise WptrunError("Unsupported product %s" % kwargs["product"]) 942 943 setup_cls = product_setup[kwargs["product"]](venv, kwargs["prompt"]) 944 if not venv.skip_virtualenv_setup: 945 requirements = [os.path.join(wpt_root, "tools", "wptrunner", "requirements.txt")] 946 requirements.extend(setup_cls.requirements()) 947 venv.install_requirements(*requirements) 948 949 affected_revish = kwargs.get("affected") 950 if affected_revish is not None: 951 files_changed, _ = testfiles.files_changed( 952 affected_revish, include_uncommitted=True, include_new=True) 953 # TODO: Perhaps use wptrunner.testloader.ManifestLoader here 954 # and remove the manifest-related code from testfiles. 955 # https://github.com/web-platform-tests/wpt/issues/14421 956 tests_changed, tests_affected = testfiles.affected_testfiles( 957 files_changed, manifest_path=kwargs.get("manifest_path"), manifest_update=kwargs["manifest_update"]) 958 test_list = tests_changed | tests_affected 959 logger.info("Identified %s affected tests" % len(test_list)) 960 test_list = [os.path.relpath(item, wpt_root) for item in test_list] 961 kwargs["test_list"] += test_list 962 kwargs["default_exclude"] = True 963 964 if kwargs["install_browser"] and not kwargs["channel"]: 965 logger.info("--install-browser is given but --channel is not set, default to nightly channel") 966 kwargs["channel"] = "nightly" 967 968 if kwargs["channel"]: 969 channel = install.get_channel(kwargs["product"], kwargs["channel"]) 970 if channel is not None: 971 if channel != kwargs["channel"]: 972 logger.info("Interpreting channel '%s' as '%s'" % (kwargs["channel"], 973 channel)) 974 kwargs["browser_channel"] = channel 975 else: 976 logger.info("Valid channels for %s not known; using argument unmodified" % 977 kwargs["product"]) 978 kwargs["browser_channel"] = kwargs["channel"] 979 980 if kwargs["install_browser"]: 981 logger.info("Installing browser") 982 kwargs["binary"] = setup_cls.install(channel=kwargs["browser_channel"]) 983 984 setup_cls.setup(kwargs) 985 986 # Remove kwargs we handle here 987 wptrunner_kwargs = kwargs.copy() 988 for kwarg in ["affected", 989 "install_browser", 990 "install_webdriver", 991 "channel", 992 "prompt", 993 "logcat_dir"]: 994 del wptrunner_kwargs[kwarg] 995 996 wptcommandline.check_args(wptrunner_kwargs) 997 998 # Only update browser_version if it was not given as a command line 999 # argument, so that it can be overridden on the command line. 1000 if not wptrunner_kwargs["browser_version"]: 1001 wptrunner_kwargs["browser_version"] = setup_cls.browser.version( 1002 binary=wptrunner_kwargs.get("binary") or wptrunner_kwargs.get("package_name"), 1003 webdriver_binary=wptrunner_kwargs.get("webdriver_binary"), 1004 ) 1005 1006 return setup_cls, wptrunner_kwargs 1007 1008 1009 def run(venv, **kwargs): 1010 setup_logging(kwargs) 1011 1012 setup_cls, wptrunner_kwargs = setup_wptrunner(venv, **kwargs) 1013 1014 try: 1015 rv = run_single(venv, **wptrunner_kwargs) 1016 finally: 1017 setup_cls.teardown() 1018 1019 return rv 1020 1021 1022 def run_single(venv, **kwargs): 1023 from wptrunner import wptrunner 1024 return wptrunner.start(**kwargs)