|
6 | 6 | # USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. |
7 | 7 | from __future__ import annotations |
8 | 8 |
|
9 | | -import unittest |
10 | | -import time |
| 9 | +import glob |
| 10 | +import os |
| 11 | +import re |
| 12 | +import sys |
11 | 13 | import tempfile |
| 14 | +import unittest |
12 | 15 | import zipfile |
| 16 | +from collections.abc import Mapping |
13 | 17 | from pathlib import Path |
14 | | -from typing import Any, cast |
| 18 | +from typing import cast |
15 | 19 |
|
16 | 20 | from modcdp.injector.ExtensionInjector import DEFAULT_MODCDP_EXTENSION_ID |
17 | 21 | from modcdp.injector.CLIExtensionInjector import CLIExtensionInjector |
| 22 | +from modcdp.launcher.LocalBrowserLauncher import LocalBrowserLauncher |
| 23 | +from modcdp.transport.WSUpstreamTransport import WSUpstreamTransport |
18 | 24 |
|
19 | 25 |
|
20 | 26 | ROOT = Path(__file__).resolve().parents[2] |
21 | 27 | EXTENSION_PATH = ROOT / "dist" / "extension" |
| 28 | +DOES_NOT_EXIST_EXTENSION_ID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
| 29 | + |
| 30 | + |
| 31 | +# MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. |
| 32 | +# Keep setup semantics 1:1 with TS; this only selects a real browser for real --load-extension runs. |
| 33 | +def load_extension_test_browser_path() -> str: |
| 34 | + for candidate in (os.environ.get("CHROME_PATH"), "/usr/bin/chromium" if sys.platform.startswith("linux") else None): |
| 35 | + if candidate and Path(candidate).exists(): |
| 36 | + return candidate |
| 37 | + home = Path.home() |
| 38 | + if sys.platform == "darwin": |
| 39 | + patterns = [ |
| 40 | + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), |
| 41 | + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), |
| 42 | + str(home / "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), |
| 43 | + ] |
| 44 | + elif sys.platform.startswith("win"): |
| 45 | + local_app_data = Path(os.environ.get("LOCALAPPDATA") or home / "AppData/Local") |
| 46 | + patterns = [ |
| 47 | + str(local_app_data / "ms-playwright/chromium-*/chrome-win*/chrome.exe"), |
| 48 | + str(home / ".cache/puppeteer/chrome/win*-*/chrome.exe"), |
| 49 | + ] |
| 50 | + else: |
| 51 | + patterns = [ |
| 52 | + str(home / ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), |
| 53 | + "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", |
| 54 | + str(home / ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), |
| 55 | + ] |
| 56 | + candidates = sorted( |
| 57 | + dict.fromkeys(match for pattern in patterns for match in glob.glob(pattern)), |
| 58 | + key=lambda path: (-max([int(part) for part in re.findall(r"\d+", path)] or [0]), -Path(path).stat().st_mtime, path), |
| 59 | + ) |
| 60 | + if candidates: |
| 61 | + return candidates[0] |
| 62 | + raise RuntimeError("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH.") |
| 63 | + |
| 64 | + |
| 65 | +LOAD_EXTENSION_TEST_BROWSER_PATH = load_extension_test_browser_path() |
22 | 66 |
|
23 | 67 |
|
24 | 68 | class CLIExtensionInjectorTests(unittest.TestCase): |
@@ -64,34 +108,48 @@ def test_cliextensioninjector_prepares_the_default_extension_zip_for_load_extens |
64 | 108 | finally: |
65 | 109 | injector.close() |
66 | 110 |
|
67 | | - def test_cliextensioninjector_returns_immediately_when_the_launched_extension_target_is_absent(self) -> None: |
68 | | - methods: list[str] = [] |
69 | | - |
70 | | - def send(method: str, params: dict[str, Any] | None = None, session_id: str | None = None) -> dict[str, Any]: |
71 | | - methods.append(method) |
72 | | - if method == "Target.getTargets": |
73 | | - return {"targetInfos": []} |
74 | | - raise RuntimeError(f"unexpected {method}") |
75 | | - |
| 111 | + def test_cliextensioninjector_returns_null_when_a_trusted_does_not_exist_extension_id_is_absent_in_a_real_browser(self) -> None: |
76 | 112 | injector = CLIExtensionInjector( |
77 | | - cast(Any, { |
| 113 | + { |
78 | 114 | "injector_cli_extension_path": str(EXTENSION_PATH), |
| 115 | + "injector_cli_extension_id": DOES_NOT_EXIST_EXTENSION_ID, |
79 | 116 | "injector_trust_service_worker_target": True, |
80 | | - "injector_service_worker_ready_timeout_ms": 50, |
81 | | - "injector_service_worker_poll_interval_ms": 10, |
82 | | - "send": send, |
83 | | - }) |
| 117 | + "injector_service_worker_ready_timeout_ms": 250, |
| 118 | + "injector_service_worker_poll_interval_ms": 25, |
| 119 | + } |
| 120 | + ) |
| 121 | + launcher = LocalBrowserLauncher( |
| 122 | + { |
| 123 | + "launcher_local_headless": True, |
| 124 | + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, |
| 125 | + } |
84 | 126 | ) |
| 127 | + upstream = WSUpstreamTransport() |
85 | 128 | try: |
86 | 129 | injector.prepare() |
87 | | - started_at = time.perf_counter() |
| 130 | + launcher.update(injector.configForLauncher()) |
| 131 | + launcher.launch() |
| 132 | + upstream.update(launcher.configForUpstream()) |
| 133 | + upstream.connect() |
| 134 | + injector.update({"send": upstream.send}) |
| 135 | + |
| 136 | + targets = upstream.send("Target.getTargets", {}) |
| 137 | + target_infos = targets.get("targetInfos") if isinstance(targets, Mapping) else None |
| 138 | + if not isinstance(target_infos, list): |
| 139 | + raise AssertionError(f"Target.getTargets returned no targetInfos: {targets!r}") |
| 140 | + found_does_not_exist_target = False |
| 141 | + for target in target_infos: |
| 142 | + match target: |
| 143 | + case {"url": str(target_url)}: |
| 144 | + if target_url.startswith(f"chrome-extension://{DOES_NOT_EXIST_EXTENSION_ID}/"): |
| 145 | + found_does_not_exist_target = True |
| 146 | + self.assertFalse(found_does_not_exist_target) |
| 147 | + |
88 | 148 | result = injector.inject() |
89 | | - elapsed_ms = (time.perf_counter() - started_at) * 1000 |
90 | 149 | self.assertIsNone(result) |
91 | | - self.assertGreater(len(methods), 0) |
92 | | - self.assertEqual(sorted(set(methods)), ["Target.getTargets"]) |
93 | | - self.assertLess(elapsed_ms, 200) |
94 | 150 | finally: |
| 151 | + upstream.close() |
| 152 | + launcher.close() |
95 | 153 | injector.close() |
96 | 154 |
|
97 | 155 |
|
|
0 commit comments