1# Copyright 2023 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from __future__ import annotations 6 7import logging 8from typing import TYPE_CHECKING, Optional 9 10from crossbench import compat 11from crossbench import path as pth 12from crossbench.browsers.attributes import BrowserAttributes 13from crossbench.browsers.browser import Browser 14 15if TYPE_CHECKING: 16 from crossbench import plt 17 from crossbench.browsers.settings import Settings 18 19 20SAFARIDRIVER_PATH = pth.AnyPosixPath("/usr/bin/safaridriver") 21 22 23def find_safaridriver(bin_path: pth.AnyPath, 24 platform: plt.Platform) -> pth.AnyPath: 25 assert platform.is_file(bin_path), f"Invalid binary path: {bin_path}" 26 driver_path = bin_path.parent / "safaridriver" 27 if platform.exists(driver_path): 28 return driver_path 29 # The system-default Safari version doesn't come with the driver 30 assert compat.is_relative_to(bin_path, Safari.default_path(platform)), ( 31 f"Expected default Safari.app binary but got {bin_path}") 32 return SAFARIDRIVER_PATH 33 34 35class Safari(Browser): 36 37 @classmethod 38 def default_path(cls, platform: plt.Platform) -> pth.AnyPath: 39 return platform.path("/Applications/Safari.app") 40 41 @classmethod 42 def technology_preview_path(cls, platform: plt.Platform) -> pth.AnyPath: 43 return platform.path("/Applications/Safari Technology Preview.app") 44 45 def __init__(self, 46 label: str, 47 path: pth.AnyPath, 48 settings: Optional[Settings] = None): 49 super().__init__(label, path, settings=settings) 50 assert self.platform.is_macos, "Safari only works on MacOS" 51 self.bundle_name: str = "" 52 53 def _setup_path(self, path: Optional[pth.AnyPath] = None) -> None: 54 super()._setup_path(path) 55 assert self.path 56 self.bundle_name = self.path.stem.replace(" ", "") 57 58 def _setup_cache_dir(self, settings: Settings) -> None: 59 assert settings.cache_dir is None, ( 60 "Cannot set custom cache dir for Safari") 61 assert self.bundle_name, "Missing bundle_name" 62 self.cache_dir = self.platform.home() / ( 63 f"Library/Containers/com.apple.{self.bundle_name}/Data/Library/Caches") 64 65 @property 66 def type_name(self) -> str: 67 return "safari" 68 69 @property 70 def attributes(self) -> BrowserAttributes: 71 return BrowserAttributes.SAFARI 72 73 def clear_cache(self) -> None: 74 self._clear_cache() 75 76 def _clear_cache(self) -> None: 77 logging.info("CLEAR CACHE: %s", self) 78 self.platform.exec_apple_script(f""" 79 tell application "{self.app_path}" to activate 80 tell application "System Events" 81 keystroke "e" using {{command down, option down}} 82 end tell""") 83 84 def _extract_version(self) -> str: 85 # Use the shipped safaridriver to get the more detailed version 86 # TODO: support remote platform 87 driver_version = self.platform.app_version( 88 find_safaridriver(self.path, self.platform)) 89 # Input: "Included with Safari 16.6 (18615.3.6.11.1)" 90 # Output: " (18615.3.6.11.1)" 91 driver_version = " (" + driver_version.split(" (", maxsplit=1)[1] 92 assert self.path 93 app_path = self.path.parents[2] 94 return self.platform.app_version(app_path) + driver_version 95