509f1387de
- Respect the globally selected browser when renaming client aliases. - Pass the resolved local profile into sync and async local transports so BROWSER_CLI_PROFILE is honored consistently. - Stop tabs.active from explicitly focusing the OS browser window, avoiding virtual-desktop jumps during tab activation. - Make window merging skip audible, unmuted windows so video playback windows are not selected as merge targets. - Bump the Python package and extension manifest versions to 0.12.2. - Add regression coverage for browser selection and focus-stealing behavior.
183 lines
9.0 KiB
Python
183 lines
9.0 KiB
Python
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
def test_extension_retries_error_page_script_injection_before_failing():
|
|
# core.ts was split into a core/ subfolder during the structure refactor:
|
|
# the URL/error classifiers live in core/errors.ts and the executeScript
|
|
# retry wrapper (which calls isTransientScriptError) lives in core/scripting.ts.
|
|
errors = (ROOT / "extension" / "src" / "core" / "errors.ts").read_text()
|
|
scripting = (ROOT / "extension" / "src" / "core" / "scripting.ts").read_text()
|
|
|
|
assert "isBrowserErrorUrl" in errors
|
|
assert "isErrorPageScriptError" in errors
|
|
assert "chrome-error://" in errors
|
|
assert "edge-error://" in errors
|
|
assert "brave-error://" in errors
|
|
assert "about:neterror" in errors
|
|
assert "about:certerror" in errors
|
|
assert "isTransientScriptError(e)" in scripting
|
|
|
|
def test_read_only_dom_commands_have_error_page_fallbacks():
|
|
dom = (ROOT / "extension" / "src" / "commands" / "dom.ts").read_text()
|
|
|
|
assert "fallbackForErrorPageDomOp" in dom
|
|
assert 'case "domExists":' in dom
|
|
assert "return false;" in dom
|
|
assert 'case "domQuery":' in dom
|
|
assert 'case "extractText":' in dom
|
|
assert "isBrowserErrorUrl(tabUrl)" in dom
|
|
assert "isErrorPageScriptError(e)" in dom
|
|
|
|
def test_navigation_and_tabs_report_browser_error_pages():
|
|
# tabs.watch_url (which reports tab error pages) moved into the read-only
|
|
# TabsQueryCommands class in tabs-query.ts during the structure refactor.
|
|
tabs = (ROOT / "extension" / "src" / "commands" / "tabs-query.ts").read_text()
|
|
navigation = (ROOT / "extension" / "src" / "commands" / "navigation.ts").read_text()
|
|
|
|
assert "lastUrl" in tabs
|
|
assert "lastStatus" in tabs
|
|
assert "showing an error page" in tabs
|
|
assert "last URL:" in tabs
|
|
assert "isBrowserErrorUrl" in navigation
|
|
assert "showing an error page while waiting for load" in navigation
|
|
|
|
def test_large_extension_operations_yield_between_batches():
|
|
# The large-operation throttling/queue helpers moved from core.ts into
|
|
# core/throttle.ts when core was split into a subfolder. The slice-based
|
|
# batch loop (cancel-check -> batch call -> progress -> yield) was then
|
|
# centralized into the processInBatches() helper in core/throttle.ts, so the
|
|
# command modules call that instead of inlining the loop.
|
|
core = (ROOT / "extension" / "src" / "core" / "throttle.ts").read_text()
|
|
tabs = (ROOT / "extension" / "src" / "commands" / "tabs.ts").read_text()
|
|
groups = (ROOT / "extension" / "src" / "commands" / "groups.ts").read_text()
|
|
session = (ROOT / "extension" / "src" / "commands" / "session.ts").read_text()
|
|
|
|
assert "yieldForLargeOperation" in core
|
|
assert "getLargeOperationThrottle" in core
|
|
assert "hasAudibleTabs" in core
|
|
assert "runLargeOperation" in core
|
|
assert "largeOperationQueue" in core
|
|
assert "updateJobProgress" in core
|
|
assert "throwIfJobCancelled" in core
|
|
assert "getPerformanceProfile" in core
|
|
assert "setPerformanceProfile" in core
|
|
assert "GENTLE_OPERATION_BATCH_SIZE" in core
|
|
assert "GENTLE_OPERATION_PAUSE_MS" in core
|
|
assert "itemCount >= 300" in core
|
|
assert "itemCount >= 100" in core
|
|
assert "chrome.tabs.query({ audible: true })" in core
|
|
# The centralized batch loop drives cancellation + progress + throttled yield.
|
|
assert "processInBatches" in core
|
|
assert "throwIfJobCancelled(progress.job)" in core
|
|
assert "yieldForLargeOperation(done" in core
|
|
# tabs.close / tabs.merge_windows now batch via processInBatches; tabs.sort
|
|
# still runs its own per-item move loop with yieldForLargeOperation.
|
|
assert "processInBatches(toClose" in tabs
|
|
assert "processInBatches(ids" in tabs
|
|
assert "yieldForLargeOperation" in tabs
|
|
assert "w.tabs.every" in tabs
|
|
assert "getLargeOperationThrottle" in tabs
|
|
assert "runLargeOperation(\"tabs.sort\"" in tabs
|
|
assert "processInBatches(tabIds" in groups
|
|
assert "getLargeOperationThrottle" in groups
|
|
assert "runLargeOperation(\"group.close\"" in groups
|
|
assert "yieldForLargeOperation(createdTabs.length" in session
|
|
assert "getLargeOperationThrottle" in session
|
|
assert "runLargeOperation(\"session.load\"" in session
|
|
assert "chrome.tabs.discard" in session
|
|
assert "lazyPlaceholderUrl" in session
|
|
assert "activateLazyTab" in session
|
|
assert "lazySessionTabs" in session
|
|
assert "throwIfJobCancelled" in session
|
|
assert "updateJobProgress" in session
|
|
|
|
# The background-job machinery moved out of index.ts into the JobManager
|
|
# (classes/JobManager.ts), the message router (classes/NativeConnection.ts)
|
|
# and the command registry/groups during the class-based refactor. The
|
|
# behavior is identical; assert the defining patterns still exist in their
|
|
# new homes.
|
|
jobs_module = (ROOT / "extension" / "src" / "classes" / "JobManager.ts").read_text()
|
|
connection = (ROOT / "extension" / "src" / "classes" / "NativeConnection.ts").read_text()
|
|
perf = (ROOT / "extension" / "src" / "commands" / "perf.ts").read_text()
|
|
tabs_module = (ROOT / "extension" / "src" / "commands" / "tabs.ts").read_text()
|
|
|
|
assert "background: true" in tabs_module # background command flag (was BACKGROUND_COMMANDS)
|
|
assert "persistJobs" in jobs_module
|
|
assert "recentJobs" in jobs_module
|
|
assert "jobs.status" in perf
|
|
assert "jobs.cancel" in perf
|
|
assert "perf.status" in perf
|
|
assert "perf.set_profile" in perf
|
|
assert "__background" in connection
|
|
|
|
def test_tab_activation_and_merge_do_not_steal_audible_video_window():
|
|
tabs = (ROOT / "extension" / "src" / "commands" / "tabs.ts").read_text()
|
|
|
|
assert "await chrome.windows.update(tab.windowId, { focused: true });" not in tabs
|
|
assert "windowHasAudibleTabs" in tabs
|
|
assert "!this.windowHasAudibleTabs(w)" in tabs
|
|
assert "skippedAudibleWindows" in tabs
|
|
assert "const target = movableWindows.find(w => w.focused) || movableWindows[0];" in tabs
|
|
|
|
def test_session_autosave_is_debounced_and_non_overlapping():
|
|
# The autosave lifecycle moved out of session.ts into a dedicated
|
|
# AutoSaveManager (autosave.ts) during the structure refactor; the shared
|
|
# snapshot/signature helpers live in session-snapshot.ts. Behavior is
|
|
# identical — the defining patterns just live in their new homes.
|
|
autosave = (ROOT / "extension" / "src" / "commands" / "autosave.ts").read_text()
|
|
snapshot = (ROOT / "extension" / "src" / "commands" / "session-snapshot.ts").read_text()
|
|
|
|
assert "autoSaveTimer" in autosave
|
|
assert "autoSaveInFlight" in autosave
|
|
assert "autoSavePending" in autosave
|
|
assert "scheduleAutoSave" in autosave
|
|
assert "autoSaveUpdatedHandler" in autosave
|
|
assert "saveAutoSessionIfChanged" in autosave
|
|
assert "sessionSignature" in snapshot
|
|
assert "autoSaveSignature" in autosave
|
|
# AutoSaveManager binds the handlers as instance fields (this.*), so the
|
|
# add/removeListener references stay identity-stable across enable/disable.
|
|
assert "chrome.tabs.onUpdated.addListener(this.autoSaveUpdatedHandler)" in autosave
|
|
assert "chrome.tabs.onCreated.addListener(this.autoSaveHandler)" in autosave
|
|
assert "chrome.tabs.onMoved.addListener(this.autoSaveHandler)" in autosave
|
|
assert "if (!(\"url\" in changeInfo)) return;" in autosave
|
|
assert "setTimeout(() => this.runAutoSave(), delayMs)" in autosave
|
|
assert "clearTimeout(this.autoSaveTimer)" in autosave
|
|
|
|
def test_cli_and_sdk_expose_gentle_restore_controls():
|
|
session_cli = (ROOT / "browser_cli" / "commands" / "session.py").read_text()
|
|
tabs_cli = (ROOT / "browser_cli" / "commands" / "tabs.py").read_text()
|
|
groups_cli = (ROOT / "browser_cli" / "commands" / "groups.py").read_text()
|
|
commands_init = (ROOT / "browser_cli" / "commands" / "__init__.py").read_text()
|
|
sdk_session = (ROOT / "browser_cli" / "sdk" / "session.py").read_text()
|
|
sdk_perf = (ROOT / "browser_cli" / "sdk" / "perf.py").read_text()
|
|
|
|
# The --gentle-mode flag is defined once via the shared gentle_mode_option helper.
|
|
assert '"--gentle-mode"' in commands_init
|
|
assert "gentle_mode_option" in session_cli
|
|
assert "--discard-background-tabs" in session_cli
|
|
assert "--background" in session_cli
|
|
assert "--lazy" in session_cli
|
|
assert "--eager-tabs" in session_cli
|
|
assert "job-status" in session_cli
|
|
assert "job-cancel" in session_cli
|
|
assert "discard_background_tabs" in session_cli
|
|
assert "gentle_mode_option" in tabs_cli
|
|
assert "gentle_mode" in tabs_cli
|
|
assert "gentle_mode_option" in groups_cli
|
|
assert "discard_background_tabs" in sdk_session
|
|
assert "discardBackgroundTabs" in sdk_session
|
|
assert "def load_background" in sdk_session
|
|
assert "def job_status" in sdk_perf
|
|
assert "def job_cancel" in sdk_perf
|
|
assert "def status" in sdk_perf
|
|
assert "def set_profile" in sdk_perf
|
|
|
|
perf_cli = (ROOT / "browser_cli" / "commands" / "perf.py").read_text()
|
|
root_cli = (ROOT / "browser_cli" / "cli.py").read_text()
|
|
assert "perf_group" in perf_cli
|
|
assert "perf.status" in perf_cli
|
|
assert "perf.set_profile" in perf_cli
|
|
assert "main.add_command(perf_group)" in root_cli
|