| # mypy: allow-untyped-defs |
| |
| import os |
| import plistlib |
| from distutils.spawn import find_executable |
| from distutils.version import LooseVersion |
| |
| import psutil |
| |
| from .base import WebDriverBrowser, require_arg |
| from .base import get_timeout_multiplier # noqa: F401 |
| from ..executors import executor_kwargs as base_executor_kwargs |
| from ..executors.base import WdspecExecutor # noqa: F401 |
| from ..executors.executorwebdriver import (WebDriverTestharnessExecutor, # noqa: F401 |
| WebDriverRefTestExecutor, # noqa: F401 |
| WebDriverCrashtestExecutor) # noqa: F401 |
| |
| |
| __wptrunner__ = {"product": "safari", |
| "check_args": "check_args", |
| "browser": "SafariBrowser", |
| "executor": {"testharness": "WebDriverTestharnessExecutor", |
| "reftest": "WebDriverRefTestExecutor", |
| "wdspec": "WdspecExecutor", |
| "crashtest": "WebDriverCrashtestExecutor"}, |
| "browser_kwargs": "browser_kwargs", |
| "executor_kwargs": "executor_kwargs", |
| "env_extras": "env_extras", |
| "env_options": "env_options", |
| "run_info_extras": "run_info_extras", |
| "timeout_multiplier": "get_timeout_multiplier"} |
| |
| |
| def check_args(**kwargs): |
| require_arg(kwargs, "webdriver_binary") |
| |
| |
| def browser_kwargs(logger, test_type, run_info_data, config, **kwargs): |
| return {"webdriver_binary": kwargs["webdriver_binary"], |
| "webdriver_args": kwargs.get("webdriver_args"), |
| "kill_safari": kwargs.get("kill_safari", False)} |
| |
| |
| def executor_kwargs(logger, test_type, test_environment, run_info_data, **kwargs): |
| executor_kwargs = base_executor_kwargs(test_type, test_environment, run_info_data, **kwargs) |
| executor_kwargs["close_after_done"] = True |
| executor_kwargs["capabilities"] = {} |
| if test_type == "testharness": |
| executor_kwargs["capabilities"]["pageLoadStrategy"] = "eager" |
| if kwargs["binary"] is not None: |
| raise ValueError("Safari doesn't support setting executable location") |
| |
| V = LooseVersion |
| browser_bundle_version = run_info_data["browser_bundle_version"] |
| if browser_bundle_version is not None and V(browser_bundle_version[2:]) >= V("613.1.7.1"): |
| logger.debug("using acceptInsecureCerts=True") |
| executor_kwargs["capabilities"]["acceptInsecureCerts"] = True |
| else: |
| logger.warning("not using acceptInsecureCerts, Safari will require certificates to be trusted") |
| |
| return executor_kwargs |
| |
| |
| def env_extras(**kwargs): |
| return [] |
| |
| |
| def env_options(): |
| return {} |
| |
| |
| def run_info_extras(**kwargs): |
| webdriver_binary = kwargs["webdriver_binary"] |
| rv = {} |
| |
| safari_bundle, safari_info = get_safari_info(webdriver_binary) |
| |
| if safari_info is not None: |
| assert safari_bundle is not None # if safari_info is not None, this can't be |
| _, webkit_info = get_webkit_info(safari_bundle) |
| if webkit_info is None: |
| webkit_info = {} |
| else: |
| safari_info = {} |
| webkit_info = {} |
| |
| rv["browser_marketing_version"] = safari_info.get("CFBundleShortVersionString") |
| rv["browser_bundle_version"] = safari_info.get("CFBundleVersion") |
| rv["browser_webkit_bundle_version"] = webkit_info.get("CFBundleVersion") |
| |
| with open("/System/Library/CoreServices/SystemVersion.plist", "rb") as fp: |
| system_version = plistlib.load(fp) |
| |
| rv["os_build"] = system_version["ProductBuildVersion"] |
| |
| return rv |
| |
| |
| def get_safari_info(wd_path): |
| bundle_paths = [ |
| os.path.join(os.path.dirname(wd_path), "..", ".."), # bundled Safari (e.g. STP) |
| os.path.join(os.path.dirname(wd_path), "Safari.app"), # local Safari build |
| "/Applications/Safari.app", # system Safari |
| ] |
| |
| for bundle_path in bundle_paths: |
| info_path = os.path.join(bundle_path, "Contents", "Info.plist") |
| if not os.path.isfile(info_path): |
| continue |
| |
| with open(info_path, "rb") as fp: |
| info = plistlib.load(fp) |
| |
| # check we have a Safari family bundle |
| ident = info.get("CFBundleIdentifier") |
| if not isinstance(ident, str) or not ident.startswith("com.apple.Safari"): |
| continue |
| |
| return (bundle_path, info) |
| |
| return (None, None) |
| |
| |
| def get_webkit_info(safari_bundle_path): |
| framework_paths = [ |
| os.path.join(os.path.dirname(safari_bundle_path), "Contents", "Frameworks"), # bundled Safari (e.g. STP) |
| os.path.join(os.path.dirname(safari_bundle_path), ".."), # local Safari build |
| "/System/Library/PrivateFrameworks", |
| "/Library/Frameworks", |
| "/System/Library/Frameworks", |
| ] |
| |
| for framework_path in framework_paths: |
| info_path = os.path.join(framework_path, "WebKit.framework", "Versions", "Current", "Resources", "Info.plist") |
| if not os.path.isfile(info_path): |
| continue |
| |
| with open(info_path, "rb") as fp: |
| info = plistlib.load(fp) |
| return (framework_path, info) |
| |
| return (None, None) |
| |
| |
| class SafariBrowser(WebDriverBrowser): |
| """Safari is backed by safaridriver, which is supplied through |
| ``wptrunner.webdriver.SafariDriverServer``. |
| """ |
| def __init__(self, logger, binary=None, webdriver_binary=None, webdriver_args=None, |
| port=None, env=None, kill_safari=False, **kwargs): |
| """Creates a new representation of Safari. The `webdriver_binary` |
| argument gives the WebDriver binary to use for testing. (The browser |
| binary location cannot be specified, as Safari and SafariDriver are |
| coupled.) If `kill_safari` is True, then `Browser.stop` will stop Safari.""" |
| super().__init__(logger, |
| binary, |
| webdriver_binary, |
| webdriver_args=webdriver_args, |
| port=None, |
| supports_pac=False, |
| env=env) |
| |
| if "/" not in webdriver_binary: |
| wd_path = find_executable(webdriver_binary) |
| else: |
| wd_path = webdriver_binary |
| self.safari_path = self._find_safari_executable(wd_path) |
| |
| logger.debug("WebDriver executable path: %s" % wd_path) |
| logger.debug("Safari executable path: %s" % self.safari_path) |
| |
| self.kill_safari = kill_safari |
| |
| def _find_safari_executable(self, wd_path): |
| bundle_path, info = get_safari_info(wd_path) |
| |
| exe = info.get("CFBundleExecutable") |
| if not isinstance(exe, str): |
| return None |
| |
| exe_path = os.path.join(bundle_path, "Contents", "MacOS", exe) |
| if not os.path.isfile(exe_path): |
| return None |
| |
| return exe_path |
| |
| def make_command(self): |
| return [self.webdriver_binary, f"--port={self.port}"] + self.webdriver_args |
| |
| def stop(self, force=False): |
| super().stop(force) |
| |
| if self.kill_safari: |
| self.logger.debug("Going to stop Safari") |
| for proc in psutil.process_iter(attrs=["exe"]): |
| if proc.info["exe"] is None: |
| continue |
| |
| try: |
| if not os.path.samefile(proc.info["exe"], self.safari_path): |
| continue |
| except OSError: |
| continue |
| |
| self.logger.debug("Stopping Safari %s" % proc.pid) |
| try: |
| proc.terminate() |
| try: |
| proc.wait(10) |
| except psutil.TimeoutExpired: |
| proc.kill() |
| proc.wait(10) |
| except psutil.NoSuchProcess: |
| pass |