1# Copyright 2024 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 argparse 8import unittest 9 10import hjson 11from immutabledict import immutabledict 12 13from crossbench import path as pth 14from crossbench import plt 15from crossbench.browsers.chrome.chrome import Chrome 16from crossbench.cli.config.browser import BrowserConfig 17from crossbench.cli.config.driver import BrowserDriverType, DriverConfig 18from crossbench.cli.config.network import NetworkConfig, NetworkSpeedPreset 19from crossbench.exception import MultiException 20from crossbench.types import JsonDict 21from tests import test_helper 22from tests.crossbench import mock_browser 23from tests.crossbench.cli.config.base import (ADB_DEVICES_OUTPUT, 24 ADB_DEVICES_SINGLE_OUTPUT, 25 XCTRACE_DEVICES_OUTPUT, 26 XCTRACE_DEVICES_SINGLE_OUTPUT, 27 BaseConfigTestCase) 28 29 30class BrowserConfigTestCase(BaseConfigTestCase): 31 32 def test_equal(self): 33 path = Chrome.stable_path(self.platform) 34 self.assertEqual( 35 BrowserConfig(path, DriverConfig(BrowserDriverType.default())), 36 BrowserConfig(path, DriverConfig(BrowserDriverType.default()))) 37 self.assertNotEqual( 38 BrowserConfig(path, DriverConfig(BrowserDriverType.default())), 39 BrowserConfig( 40 path, 41 DriverConfig( 42 BrowserDriverType.default(), settings=immutabledict(custom=1)))) 43 self.assertNotEqual( 44 BrowserConfig(path, DriverConfig(BrowserDriverType.default())), 45 BrowserConfig( 46 pth.AnyPath("foo"), DriverConfig(BrowserDriverType.default()))) 47 48 def test_hashable(self): 49 _ = hash(BrowserConfig.default()) 50 _ = hash( 51 BrowserConfig( 52 pth.AnyPath("foo"), 53 DriverConfig( 54 BrowserDriverType.default(), settings=immutabledict(custom=1)))) 55 56 def test_parse_name_or_path(self): 57 path = Chrome.stable_path(self.platform) 58 self.assertEqual( 59 BrowserConfig.parse("chrome"), 60 BrowserConfig(path, DriverConfig(BrowserDriverType.default()))) 61 self.assertEqual( 62 BrowserConfig.parse(str(path)), 63 BrowserConfig(path, DriverConfig(BrowserDriverType.default()))) 64 65 def test_parse_invalid_name(self): 66 with self.assertRaises(argparse.ArgumentTypeError): 67 BrowserConfig.parse("") 68 with self.assertRaises(argparse.ArgumentTypeError) as cm: 69 BrowserConfig.parse("a-random-name") 70 self.assertIn("a-random-name", str(cm.exception)) 71 72 def test_parse_invalid_path(self): 73 path = pth.AnyPath("foo/bar") 74 with self.assertRaises(argparse.ArgumentTypeError) as cm: 75 BrowserConfig.parse(str(path)) 76 self.assertIn(str(path), str(cm.exception)) 77 with self.assertRaises(argparse.ArgumentTypeError) as cm: 78 BrowserConfig.parse("selenium/bar") 79 self.assertIn("selenium", str(cm.exception)) 80 self.assertIn("bar", str(cm.exception)) 81 82 def test_parse_invalid_windows_path(self): 83 with self.assertRaises(argparse.ArgumentTypeError) as cm: 84 BrowserConfig.parse("selenium\\bar") 85 self.assertIn("selenium\\\\bar", str(cm.exception)) 86 with self.assertRaises(argparse.ArgumentTypeError) as cm: 87 BrowserConfig.parse("C:\\selenium\\bar") 88 self.assertIn("C:\\\\selenium\\\\bar", str(cm.exception)) 89 90 def test_parse_simple_missing_driver(self): 91 with self.assertRaises(argparse.ArgumentTypeError) as cm: 92 BrowserConfig.parse(":chrome") 93 self.assertIn("driver", str(cm.exception)) 94 95 def test_parse_invalid_network_preset(self): 96 with self.assertRaises(argparse.ArgumentTypeError) as cm: 97 BrowserConfig.parse("selenium:chrome:1xx2") 98 self.assertIn("network", str(cm.exception)) 99 self.assertIn("1xx2", str(cm.exception)) 100 101 def test_parse_simple_with_driver(self): 102 self.assertEqual( 103 BrowserConfig.parse("selenium:chrome"), 104 BrowserConfig( 105 Chrome.stable_path(self.platform), 106 DriverConfig(BrowserDriverType.WEB_DRIVER))) 107 self.assertEqual( 108 BrowserConfig.parse("webdriver:chrome"), 109 BrowserConfig( 110 Chrome.stable_path(self.platform), 111 DriverConfig(BrowserDriverType.WEB_DRIVER))) 112 self.assertEqual( 113 BrowserConfig.parse("applescript:chrome"), 114 BrowserConfig( 115 Chrome.stable_path(self.platform), 116 DriverConfig(BrowserDriverType.APPLE_SCRIPT))) 117 self.assertEqual( 118 BrowserConfig.parse("osa:chrome"), 119 BrowserConfig( 120 Chrome.stable_path(self.platform), 121 DriverConfig(BrowserDriverType.APPLE_SCRIPT))) 122 123 def test_parse_simple_with_driver_with_network(self): 124 self.assertEqual( 125 BrowserConfig.parse("chrome:4G"), 126 BrowserConfig( 127 Chrome.stable_path(self.platform), 128 DriverConfig(BrowserDriverType.WEB_DRIVER), 129 NetworkConfig.parse_live(NetworkSpeedPreset.MOBILE_4G))) 130 self.assertEqual( 131 BrowserConfig.parse("selenium:chrome:4G"), 132 BrowserConfig( 133 Chrome.stable_path(self.platform), 134 DriverConfig(BrowserDriverType.WEB_DRIVER), 135 NetworkConfig.parse_live(NetworkSpeedPreset.MOBILE_4G))) 136 137 def test_parse_simple_ambiguous_with_driver_ios(self): 138 self.platform.sh_results = [XCTRACE_DEVICES_OUTPUT] 139 with self.assertRaises(argparse.ArgumentTypeError) as cm: 140 _ = BrowserConfig.parse("ios:chrome") 141 self.assertIn("devices", str(cm.exception)) 142 143 def test_parse_simple_with_driver_ios(self): 144 self.platform.sh_results = [ 145 XCTRACE_DEVICES_SINGLE_OUTPUT, XCTRACE_DEVICES_SINGLE_OUTPUT 146 ] 147 config = BrowserConfig.parse("ios:chrome") 148 self.assertEqual( 149 config, 150 BrowserConfig( 151 Chrome.stable_path(self.platform), 152 DriverConfig(BrowserDriverType.IOS))) 153 self.assertIsNone(config.network) 154 self.platform.sh_results = [ 155 XCTRACE_DEVICES_SINGLE_OUTPUT, 156 ] 157 config = BrowserConfig.parse("ios:chrome:live") 158 self.assertEqual(config.network, NetworkConfig.default()) 159 160 def test_parse_simple_with_driver_ios_with_network(self): 161 self.platform.sh_results = [ 162 XCTRACE_DEVICES_SINGLE_OUTPUT, XCTRACE_DEVICES_SINGLE_OUTPUT 163 ] 164 config = BrowserConfig.parse("ios:chrome:4G") 165 self.assertEqual( 166 config, 167 BrowserConfig( 168 Chrome.stable_path(self.platform), 169 DriverConfig(BrowserDriverType.IOS), 170 NetworkConfig.parse_live(NetworkSpeedPreset.MOBILE_4G))) 171 self.assertEqual(config.network, 172 NetworkConfig.parse_live(NetworkSpeedPreset.MOBILE_4G)) 173 174 def test_parse_simple_ambiguous_with_driver_android(self): 175 self.platform.sh_results = [ADB_DEVICES_OUTPUT] 176 with self.assertRaises(argparse.ArgumentTypeError) as cm: 177 _ = BrowserConfig.parse("adb:chrome") 178 self.assertIn("devices", str(cm.exception)) 179 180 def test_parse_simple_with_driver_android(self): 181 self.platform.sh_results = [ 182 ADB_DEVICES_SINGLE_OUTPUT, ADB_DEVICES_SINGLE_OUTPUT 183 ] 184 self.assertEqual( 185 BrowserConfig.parse("adb:chrome"), 186 BrowserConfig( 187 pth.AnyPosixPath("com.android.chrome"), 188 DriverConfig(BrowserDriverType.ANDROID))) 189 self.assertListEqual(self.platform.sh_results, []) 190 191 self.platform.sh_results = [ 192 ADB_DEVICES_SINGLE_OUTPUT, ADB_DEVICES_SINGLE_OUTPUT 193 ] 194 self.assertEqual( 195 BrowserConfig.parse("android:com.chrome.beta"), 196 BrowserConfig( 197 pth.AnyPosixPath("com.chrome.beta"), 198 DriverConfig(BrowserDriverType.ANDROID))) 199 self.assertListEqual(self.platform.sh_results, []) 200 201 self.platform.sh_results = [ 202 ADB_DEVICES_SINGLE_OUTPUT, ADB_DEVICES_SINGLE_OUTPUT 203 ] 204 self.assertEqual( 205 BrowserConfig.parse("android:chrome-beta"), 206 BrowserConfig( 207 pth.AnyPosixPath("com.chrome.beta"), 208 DriverConfig(BrowserDriverType.ANDROID))) 209 self.assertListEqual(self.platform.sh_results, []) 210 211 self.platform.sh_results = [ 212 ADB_DEVICES_SINGLE_OUTPUT, ADB_DEVICES_SINGLE_OUTPUT 213 ] 214 self.assertEqual( 215 BrowserConfig.parse("adb:chrome-dev"), 216 BrowserConfig( 217 pth.AnyPosixPath("com.chrome.dev"), 218 DriverConfig(BrowserDriverType.ANDROID))) 219 self.assertListEqual(self.platform.sh_results, []) 220 221 self.platform.sh_results = [ 222 ADB_DEVICES_SINGLE_OUTPUT, ADB_DEVICES_SINGLE_OUTPUT 223 ] 224 self.assertEqual( 225 BrowserConfig.parse("android:chrome-canary"), 226 BrowserConfig( 227 pth.AnyPosixPath("com.chrome.canary"), 228 DriverConfig(BrowserDriverType.ANDROID))) 229 self.assertListEqual(self.platform.sh_results, []) 230 231 self.platform.sh_results = [ 232 ADB_DEVICES_SINGLE_OUTPUT, ADB_DEVICES_SINGLE_OUTPUT 233 ] 234 self.assertEqual( 235 BrowserConfig.parse("android:chromium"), 236 BrowserConfig( 237 pth.AnyPosixPath("org.chromium.chrome"), 238 DriverConfig(BrowserDriverType.ANDROID))) 239 self.assertListEqual(self.platform.sh_results, []) 240 241 def test_parse_simple_with_local_apk(self): 242 self.platform.sh_results = [ 243 ADB_DEVICES_SINGLE_OUTPUT, ADB_DEVICES_SINGLE_OUTPUT 244 ] 245 self.assertEqual( 246 BrowserConfig.parse("adb:/chrome/src/out/Release/chromium.apk"), 247 BrowserConfig( 248 pth.LocalPosixPath("/chrome/src/out/Release/chromium.apk"), 249 DriverConfig(BrowserDriverType.ANDROID))) 250 251 def test_parse_simple_with_local_built_apk_helper(self): 252 self.platform.sh_results = [ 253 ADB_DEVICES_SINGLE_OUTPUT, ADB_DEVICES_SINGLE_OUTPUT 254 ] 255 self.assertEqual( 256 BrowserConfig.parse("adb:/chrome/src/out/Release/chrome_public_apk"), 257 BrowserConfig( 258 pth.LocalPosixPath("/chrome/src/out/Release/chrome_public_apk"), 259 DriverConfig(BrowserDriverType.ANDROID))) 260 261 @unittest.skip("Non-path browser short names are not yet supported " 262 "in complex configs.") 263 def test_parse_inline_hjson_android(self): 264 self.platform.sh_results = [ 265 ADB_DEVICES_SINGLE_OUTPUT, ADB_DEVICES_SINGLE_OUTPUT 266 ] 267 config_dict: JsonDict = { 268 "browser": "com.android.chrome", 269 "driver": "android", 270 } 271 self.assertEqual( 272 BrowserConfig.parse(config_dict), 273 BrowserConfig( 274 pth.AnyPath("com.android.chrome"), 275 DriverConfig(BrowserDriverType.ANDROID))) 276 self.assertListEqual(self.platform.sh_results, []) 277 278 def test_parse_invalid_android_package(self): 279 self.platform.sh_results = [ADB_DEVICES_SINGLE_OUTPUT] 280 with self.assertRaises(argparse.ArgumentTypeError): 281 BrowserConfig.parse("") 282 with self.assertRaises(argparse.ArgumentTypeError) as cm: 283 BrowserConfig.parse("adb:com.Foo .bar. com") 284 self.assertIn("com.Foo .bar. com", str(cm.exception)) 285 286 def test_parse_fail_android_browser_string_not_dict(self): 287 with self.assertRaises(argparse.ArgumentTypeError) as cm: 288 BrowserConfig.parse({"browser": "adb:chrome"}) 289 self.assertIn("browser", str(cm.exception)) 290 self.assertIn("short-form", str(cm.exception)) 291 292 @unittest.skipIf(plt.PLATFORM.is_win, 293 "Chrome downloading not supported on windows.") 294 def test_parse_chrome_version(self): 295 self.assertEqual( 296 BrowserConfig.parse("applescript:chrome-m100"), 297 BrowserConfig("chrome-m100", 298 DriverConfig(BrowserDriverType.APPLE_SCRIPT))) 299 self.assertEqual( 300 BrowserConfig.parse("selenium:chrome-116.0.5845.4"), 301 BrowserConfig("chrome-116.0.5845.4", 302 DriverConfig(BrowserDriverType.WEB_DRIVER))) 303 304 def test_parse_invalid_chrome_version(self): 305 with self.assertRaises(argparse.ArgumentTypeError) as cm: 306 _ = BrowserConfig.parse("applescript:chrome-m1") 307 self.assertIn("m1", str(cm.exception)) 308 with self.assertRaises(argparse.ArgumentTypeError) as cm: 309 _ = BrowserConfig.parse("selenium:chrome-116.845.4.3.2.1.0") 310 self.assertIn("116.845.4.3.2.1.0", str(cm.exception)) 311 312 def test_parse_adb_phone_serial(self): 313 self.platform.sh_results = [ADB_DEVICES_OUTPUT, ADB_DEVICES_OUTPUT] 314 config = BrowserConfig.parse("0a388e93:chrome") 315 assert isinstance(config, BrowserConfig) 316 self.assertListEqual(self.platform.sh_results, []) 317 self.assertEqual(len(self.platform.sh_cmds), 2) 318 319 self.platform.sh_results = [ADB_DEVICES_OUTPUT] 320 expected_driver = DriverConfig( 321 BrowserDriverType.ANDROID, device_id="0a388e93") 322 self.assertEqual(len(self.platform.sh_results), 0) 323 self.assertEqual(len(self.platform.sh_cmds), 3) 324 self.assertEqual( 325 config, 326 BrowserConfig(pth.AnyPosixPath("com.android.chrome"), expected_driver)) 327 328 @unittest.skipIf(plt.PLATFORM.is_macos, "Incompatible platform") 329 def test_parse_adb_phone_serial_invalid_macos(self): 330 self.platform.sh_results = [ADB_DEVICES_OUTPUT] 331 with self.assertRaises(argparse.ArgumentTypeError) as cm: 332 _ = BrowserConfig.parse("0XXXXXX:chrome") 333 self.assertIn("0XXXXXX", str(cm.exception)) 334 self.assertEqual(len(self.platform.sh_cmds), 1) 335 336 @unittest.skipIf(not plt.PLATFORM.is_macos, "Incompatible platform") 337 def test_parse_adb_phone_serial_invalid_non_macos(self): 338 self.platform.sh_results = [ADB_DEVICES_OUTPUT, XCTRACE_DEVICES_OUTPUT] 339 with self.assertRaises(argparse.ArgumentTypeError) as cm: 340 _ = BrowserConfig.parse("0XXXXXX:chrome") 341 self.assertIn("0XXXXXX", str(cm.exception)) 342 self.assertEqual(len(self.platform.sh_cmds), 2) 343 344 def test_parse_invalid_driver(self): 345 with self.assertRaises(argparse.ArgumentTypeError): 346 BrowserConfig.parse("____:chrome") 347 with self.assertRaises(argparse.ArgumentTypeError): 348 # This has to be dealt with in users of DriverConfig.parse. 349 BrowserConfig.parse("::chrome") 350 351 def test_parse_invalid_hjson(self): 352 with self.assertRaises(argparse.ArgumentTypeError): 353 BrowserConfig.parse("{:::}") 354 with self.assertRaises(argparse.ArgumentTypeError): 355 BrowserConfig.parse("{}") 356 with self.assertRaises(argparse.ArgumentTypeError): 357 BrowserConfig.parse("}") 358 with self.assertRaises(argparse.ArgumentTypeError): 359 BrowserConfig.parse("{path:something}") 360 361 def test_parse_inline_hjson(self): 362 config_dict: JsonDict = {"browser": "chrome", "driver": {"type": "adb",}} 363 364 self.platform.sh_results = [ADB_DEVICES_OUTPUT] 365 with self.assertRaises(MultiException) as cm: 366 _ = BrowserConfig.parse(hjson.dumps(config_dict)) 367 self.assertIn("devices", str(cm.exception)) 368 369 self.platform.sh_results = [ADB_DEVICES_SINGLE_OUTPUT] 370 config_1 = BrowserConfig.parse(hjson.dumps(config_dict)) 371 assert isinstance(config_1, BrowserConfig) 372 self.assertEqual(config_1.driver.type, BrowserDriverType.ANDROID) 373 374 self.platform.sh_results = [ADB_DEVICES_SINGLE_OUTPUT] 375 config_2 = BrowserConfig.parse_dict(config_dict) 376 assert isinstance(config_2, BrowserConfig) 377 self.assertEqual(config_2.driver.type, BrowserDriverType.ANDROID) 378 self.assertEqual(config_1, config_2) 379 380 short_config_dict: JsonDict = { 381 "browser": "chrome", 382 "driver": "adb", 383 } 384 self.platform.sh_results = [ADB_DEVICES_OUTPUT] 385 with self.assertRaises(MultiException) as cm: 386 _ = BrowserConfig.parse(hjson.dumps(short_config_dict)) 387 self.assertIn("devices", str(cm.exception)) 388 389 self.platform.sh_results = [ADB_DEVICES_SINGLE_OUTPUT] 390 config_3 = BrowserConfig.parse_dict(short_config_dict) 391 assert isinstance(config_3, BrowserConfig) 392 self.assertEqual(config_3.driver.type, BrowserDriverType.ANDROID) 393 self.assertEqual(config_1, config_3) 394 395 def test_parse_inline_hjson_short_string(self): 396 # We cannot easily configure the driver property from within the browser 397 # config property. 398 config_dict: JsonDict = { 399 "browser": "adb:chrome", 400 } 401 with self.assertRaises(argparse.ArgumentTypeError): 402 BrowserConfig.parse_dict(config_dict) 403 404 def test_parse_inline_driver_browser(self): 405 driver_path = pth.LocalPath("custom/chromedriver") 406 config_dict: JsonDict = { 407 "browser": "chrome", 408 "driver": str(driver_path), 409 } 410 with self.assertRaises(ValueError): 411 BrowserConfig.parse(hjson.dumps(config_dict)) 412 self.fs.create_file(driver_path, st_size=100) 413 config = BrowserConfig.parse(hjson.dumps(config_dict)) 414 assert isinstance(config, BrowserConfig) 415 self.assertEqual(config.browser, 416 mock_browser.MockChromeStable.mock_app_path()) 417 self.assertEqual(config.driver.type, BrowserDriverType.WEB_DRIVER) 418 self.assertEqual(config.driver.path, driver_path) 419 420 def test_parse_with_range_simple(self): 421 versions = BrowserConfig.parse_with_range("chrome-m100") 422 self.assertTupleEqual(versions, (BrowserConfig.parse("chrome-m100"),)) 423 424 def test_parse_with_range(self): 425 result = (BrowserConfig.parse("chrome-m99"), 426 BrowserConfig.parse("chrome-m100"), 427 BrowserConfig.parse("chrome-m101"), 428 BrowserConfig.parse("chrome-m102")) 429 versions = BrowserConfig.parse_with_range("chrome-m99...chrome-m102") 430 self.assertTupleEqual(versions, result) 431 versions = BrowserConfig.parse_with_range("chrome-m99...m102") 432 self.assertTupleEqual(versions, result) 433 versions = BrowserConfig.parse_with_range("chrome-m99...102") 434 self.assertTupleEqual(versions, result) 435 436 def test_parse_with_range_invalid_empty(self): 437 with self.assertRaises(argparse.ArgumentTypeError) as cm: 438 BrowserConfig.parse_with_range("") 439 self.assertIn("empty", str(cm.exception)) 440 441 def test_parse_with_range_invalid_prefix(self): 442 with self.assertRaises(argparse.ArgumentTypeError) as cm: 443 BrowserConfig.parse_with_range("chr-m100...chrome-m200") 444 msg = str(cm.exception) 445 self.assertIn("'chr-m'", msg) 446 self.assertIn("'chrome-m'", msg) 447 448 def test_parse_with_range_invalid_limit(self): 449 with self.assertRaises(argparse.ArgumentTypeError) as cm: 450 BrowserConfig.parse_with_range("chr-m100...chr-m90") 451 msg = str(cm.exception).lower() 452 self.assertIn("larger", msg) 453 self.assertIn("chr-m100...chr-m90", msg) 454 455 def test_parse_with_range_missing_digits(self): 456 with self.assertRaises(argparse.ArgumentTypeError) as cm: 457 BrowserConfig.parse_with_range("chr-m...chrome-m90") 458 msg = str(cm.exception).lower() 459 self.assertIn("start", msg) 460 self.assertIn("'chr-m'", msg) 461 with self.assertRaises(argparse.ArgumentTypeError) as cm: 462 BrowserConfig.parse_with_range("chr-m100...chr") 463 msg = str(cm.exception).lower() 464 self.assertIn("limit", msg) 465 self.assertIn("'chr'", msg) 466 467 468if __name__ == "__main__": 469 test_helper.run_pytest(__file__) 470