• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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