browser-hybrid 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. browser_hybrid-0.2.3/.github/workflows/ci.yaml +142 -0
  2. browser_hybrid-0.2.3/.gitignore +61 -0
  3. browser_hybrid-0.2.3/CHANGELOG.md +156 -0
  4. browser_hybrid-0.2.3/CODE_REVIEW.md +289 -0
  5. browser_hybrid-0.2.3/Makefile +45 -0
  6. browser_hybrid-0.2.3/PKG-INFO +304 -0
  7. browser_hybrid-0.2.3/README.md +276 -0
  8. browser_hybrid-0.2.3/ROADMAP.md +92 -0
  9. browser_hybrid-0.2.3/docs/EVAL_STRATEGY.md +491 -0
  10. browser_hybrid-0.2.3/docs/RECONNECT_DESIGN.md +64 -0
  11. browser_hybrid-0.2.3/docs/RECONNECT_RESEARCH.md +87 -0
  12. browser_hybrid-0.2.3/docs/RELIABILITY_PLAN.md +425 -0
  13. browser_hybrid-0.2.3/docs/SPEC.md +463 -0
  14. browser_hybrid-0.2.3/docs/TESTING_STRATEGY.md +253 -0
  15. browser_hybrid-0.2.3/evals/accessibility.yaml +48 -0
  16. browser_hybrid-0.2.3/evals/connection.yaml +18 -0
  17. browser_hybrid-0.2.3/evals/elements.yaml +39 -0
  18. browser_hybrid-0.2.3/evals/javascript.yaml +44 -0
  19. browser_hybrid-0.2.3/evals/navigation.yaml +29 -0
  20. browser_hybrid-0.2.3/pyproject.toml +62 -0
  21. browser_hybrid-0.2.3/src/browser_hybrid/__init__.py +49 -0
  22. browser_hybrid-0.2.3/src/browser_hybrid/__main__.py +8 -0
  23. browser_hybrid-0.2.3/src/browser_hybrid/browser.py +2240 -0
  24. browser_hybrid-0.2.3/src/browser_hybrid/cli.py +303 -0
  25. browser_hybrid-0.2.3/src/browser_hybrid/connection.py +622 -0
  26. browser_hybrid-0.2.3/src/browser_hybrid/errors.py +182 -0
  27. browser_hybrid-0.2.3/src/browser_hybrid/evals.py +517 -0
  28. browser_hybrid-0.2.3/src/browser_hybrid/models.py +205 -0
  29. browser_hybrid-0.2.3/tests/conftest.py +87 -0
  30. browser_hybrid-0.2.3/tests/test_browser.py +1161 -0
  31. browser_hybrid-0.2.3/tests/test_concurrency.py +77 -0
  32. browser_hybrid-0.2.3/tests/test_connection.py +511 -0
  33. browser_hybrid-0.2.3/tests/test_errors.py +57 -0
  34. browser_hybrid-0.2.3/tests/test_evals.py +142 -0
  35. browser_hybrid-0.2.3/tests/test_live.py +309 -0
  36. browser_hybrid-0.2.3/tests/test_reconnection.py +233 -0
  37. browser_hybrid-0.2.3/tests/test_smoke.py +225 -0
@@ -0,0 +1,142 @@
1
+ # CI/CD Pipeline for browser-hybrid
2
+
3
+ name: CI
4
+
5
+ on:
6
+ push:
7
+ branches: [main, develop]
8
+ tags: [v*]
9
+ pull_request:
10
+ branches: [main]
11
+
12
+ jobs:
13
+ lint:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.11"
22
+
23
+ - name: Install dependencies
24
+ run: |
25
+ pip install -e ".[dev]"
26
+
27
+ - name: Run ruff
28
+ run: ruff check src/ tests/
29
+
30
+ - name: Run ruff format check
31
+ run: ruff format --check src/ tests/
32
+
33
+ - name: Run mypy
34
+ run: mypy src/browser_hybrid/
35
+
36
+ test:
37
+ runs-on: ubuntu-latest
38
+ needs: lint
39
+ strategy:
40
+ matrix:
41
+ python-version: ["3.10", "3.11", "3.12"]
42
+
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+
46
+ - name: Set up Python ${{ matrix.python-version }}
47
+ uses: actions/setup-python@v5
48
+ with:
49
+ python-version: ${{ matrix.python-version }}
50
+
51
+ - name: Install dependencies
52
+ run: |
53
+ pip install -e ".[dev]"
54
+
55
+ - name: Run unit tests
56
+ run: pytest tests/ -v --cov=browser_hybrid --cov-report=xml
57
+
58
+ - name: Upload coverage
59
+ uses: codecov/codecov-action@v4
60
+ with:
61
+ files: coverage.xml
62
+
63
+ integration:
64
+ runs-on: ubuntu-latest
65
+ needs: test
66
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
67
+
68
+ steps:
69
+ - uses: actions/checkout@v4
70
+
71
+ - name: Set up Python
72
+ uses: actions/setup-python@v5
73
+ with:
74
+ python-version: "3.11"
75
+
76
+ - name: Install dependencies
77
+ run: pip install -e ".[dev]"
78
+
79
+ - name: Install Chrome
80
+ uses: browser-actions/setup-chrome@v1
81
+ with:
82
+ chrome-version: stable
83
+
84
+ - name: Start Chrome debug mode
85
+ run: |
86
+ chrome --remote-debugging-port=9222 --headless --disable-gpu &
87
+ sleep 5
88
+
89
+ - name: Run integration tests
90
+ run: pytest tests/ -v -m integration --tb=short
91
+
92
+ build:
93
+ runs-on: ubuntu-latest
94
+ needs: [test]
95
+ if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
96
+
97
+ steps:
98
+ - uses: actions/checkout@v4
99
+
100
+ - name: Set up Python
101
+ uses: actions/setup-python@v5
102
+ with:
103
+ python-version: "3.11"
104
+
105
+ - name: Install build tools
106
+ run: pip install build twine
107
+
108
+ - name: Build package
109
+ run: python -m build
110
+
111
+ - name: Check package
112
+ run: twine check dist/*
113
+
114
+ - name: Upload artifacts
115
+ uses: actions/upload-artifact@v4
116
+ with:
117
+ name: dist
118
+ path: dist/
119
+
120
+ publish:
121
+ runs-on: ubuntu-latest
122
+ needs: build
123
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
124
+
125
+ steps:
126
+ - uses: actions/checkout@v4
127
+
128
+ - name: Set up Python
129
+ uses: actions/setup-python@v5
130
+ with:
131
+ python-version: "3.11"
132
+
133
+ - name: Install build tools
134
+ run: pip install build twine
135
+
136
+ - name: Build package
137
+ run: python -m build
138
+
139
+ - name: Publish to PyPI
140
+ uses: pypa/gh-action-pypi-publish@release/v1
141
+ with:
142
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,61 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ venv/
25
+ ENV/
26
+ env/
27
+ .venv/
28
+
29
+ # IDE
30
+ .idea/
31
+ .vscode/
32
+ *.swp
33
+ *.swo
34
+ *~
35
+
36
+ # Testing
37
+ .pytest_cache/
38
+ .coverage
39
+ htmlcov/
40
+ coverage.xml
41
+ .tox/
42
+ .nox/
43
+
44
+ # Type checking
45
+ .mypy_cache/
46
+ .pytype/
47
+
48
+ # Build
49
+ *.manifest
50
+ *.spec
51
+
52
+ # macOS
53
+ .DS_Store
54
+ .AppleDouble
55
+ .LSOverride
56
+
57
+ # Project specific
58
+ *.log
59
+ .env
60
+ # Antigravity working state
61
+ ANTIGRAVITY_PROMPTS.md
@@ -0,0 +1,156 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.3] - 2026-03-23
9
+
10
+ ### Added
11
+
12
+ **Automatic Reconnection**
13
+ - WebSocket reconnection: browser automatically recovers from connection drops
14
+ - Configurable retry strategy: `reconnect_max_retries` and `reconnect_backoff` parameters
15
+ - Reconnection monitoring: `reconnect_callback` for status updates
16
+ - State preservation: operations are queued during reconnection and replayed after recovery
17
+
18
+ ### Fixed
19
+
20
+ - Resource safety: background reconnection tasks are cancelled and awaited in `Browser.close()`
21
+ - Pending task warnings: eliminated `Task was destroyed but it is pending` in tests
22
+
23
+ ## [0.2.2] - 2026-03-23
24
+
25
+ ### Added
26
+
27
+ **Keyboard Input**
28
+ - `press_key(key, modifiers=)` — Press a key with optional modifiers (Ctrl, Shift, Alt, Meta)
29
+ - `type_slowly(ref, text, delay=)` — Type text character by character with configurable delay
30
+
31
+ **Mouse Actions**
32
+ - `hover(ref)` — Hover over element by accessibility ref
33
+ - `click(ref, double=True)` — Double-click support
34
+ - `click(ref, button="right")` — Right-click (context menu) support
35
+
36
+ ### Fixed
37
+
38
+ - CI/CD: All lint (ruff), type (mypy), and test checks passing
39
+ - Tests: Added `mock_check_connection` fixture for CI (Chrome not running)
40
+ - Tests: `real_connection_check` marker for tests that need real connection behavior
41
+
42
+ ## [0.2.1] - 2026-03-23
43
+
44
+ ### Fixed
45
+
46
+ **Cookie Operations**
47
+ - `set_cookies()`: Now auto-derives URL from current tab when neither `url` nor `domain` provided
48
+ - `delete_cookies()`: Derives URL from current tab when only `name` specified (CDP requires url/domain)
49
+ - `close_tab()`: Now accepts both `Tab` object and tab ID string
50
+
51
+ ### Improved
52
+
53
+ - Simplified cookie handling - no need to specify URL/domain when setting cookies for current page
54
+ - Better test coverage for cookie derivation logic
55
+
56
+ ## [0.2.0] - 2026-03-23
57
+
58
+ ### Added
59
+
60
+ **Wait Conditions API**
61
+ - `wait_for_url(url=, pattern=)` — Wait for exact URL or regex pattern match
62
+ - `wait_for_selector_visible(selector)` — Wait for element to be visible
63
+ - `wait_for_selector_hidden(selector)` — Wait for element to be hidden/removed
64
+ - `wait_for_function(js_function)` — Wait for custom JavaScript predicate
65
+ - `wait_for_navigation(timeout, wait_until)` — Convenience for page loads
66
+
67
+ **Cookie Management API**
68
+ - `get_cookies(urls=)` — Get all cookies, optionally filtered by URLs
69
+ - `set_cookies(cookies)` — Set cookies with validation (name/value required)
70
+ - `delete_cookies(name=, url=, domain=, path=)` — Delete specific or all cookies
71
+
72
+ **Network Interception API**
73
+ - `on_request(callback)` — Monitor outgoing requests
74
+ - `on_response(callback)` — Monitor incoming responses
75
+ - `on_request_failed(callback)` — Handle failed requests
76
+ - `clear_interceptors()` — Remove all registered handlers
77
+
78
+ **Connection Event System**
79
+ - `Connection.on_event(method, callback)` — Register CDP event listener
80
+ - `Connection.off_event(method, callback)` — Unregister listener
81
+ - Event dispatch in `_receive_loop()` for async event handling
82
+
83
+ ### Fixed
84
+
85
+ - Timeout boundary: changed `<` to `<=` in all wait methods for full timeout window
86
+ - Regex compilation: `wait_for_url()` compiles pattern once outside loop
87
+ - Event listeners: cleared on connection disconnect to prevent stale callbacks
88
+ - Thread safety: `_network_handlers` uses `setdefault` for atomic dict access
89
+ - Cookie validation: `set_cookies()` validates `name` and `value` keys
90
+
91
+ ### Tests
92
+
93
+ - 25 new tests added (106 total, 20 skipped)
94
+ - Wait conditions: 13 tests
95
+ - Cookie management: 9 tests (including validation)
96
+ - Network interception: 7 tests
97
+
98
+ ## [0.1.1] - 2026-03-23
99
+
100
+ ### Fixed
101
+
102
+ **Critical Fixes**
103
+ - `type_text()` KeyError on `objectId` — DOM.resolveNode returns `{"object": {"objectId": ...}}`, not `{"node": {"nodeId": ...}}` (Round 3)
104
+ - `click()` silently no-op — Chrome accessibility tree has no `[data-ax-id]` attributes; fixed with proper CDP object targeting (Round 3)
105
+
106
+ **Medium Fixes**
107
+ - Connection pool limits with LRU eviction — added `max_connections=32` default with `ConnectionPool._evict_oldest()` (Round 2)
108
+ - Rate limiting for CDP calls — configurable `rate_limit` parameter prevents message flooding (Round 2)
109
+ - Rate limit race condition — moved `_last_send_time` assignment before sleep (Round 3)
110
+ - `wait_for_chrome_async` timeout boundary — changed `<` to `<=` for full timeout window (Round 4)
111
+ - `wait_for` raises on transient errors — wrapped `evaluate()` in try/except for resilience (Round 4)
112
+ - Accessibility tree caching — `cache_accessibility=True` with `refresh=True` to invalidate (Round 2)
113
+ - Multiple windows documentation — documented single-window limitation with workaround (Round 2)
114
+ - `to_dict()`/`to_json()` output methods — added to `AXNode` for structured output (Round 2)
115
+ - Python 3.9 compatibility — `from __future__ import annotations` + proper typing imports (Round 3)
116
+
117
+ **Low Fixes**
118
+ - Logging in `_http_put` — added debug logging for HTTP operations (Round 2)
119
+ - `__repr__` on `BrowserError` — added string representation for debugging (Round 2)
120
+ - CLI argparse + standard exit codes — `EXIT_SUCCESS=0`, `EXIT_ERROR=1`, `EXIT_USAGE=2`, `EXIT_INTERRUPT=130` (Round 2)
121
+ - `format` parameter shadows built-in — renamed to `fmt` in `AXNode.to_tree_str()` (Round 3)
122
+ - Eviction disconnect fire-and-forget — documented acceptable behavior (Round 3)
123
+
124
+ **Test Coverage**
125
+ - Improved test coverage to 84% (Round 2)
126
+
127
+ ### Dependencies
128
+ - Python >= 3.9 (compatibility fix)
129
+
130
+ ## [0.1.0] - 2024-03-22
131
+
132
+ ### Added
133
+ - Initial release
134
+ - Browser class for Chrome DevTools Protocol integration
135
+ - Tab management (list, new, activate, close)
136
+ - Accessibility tree parsing (`Accessibility.getFullAXTree`)
137
+ - Element targeting by role and name
138
+ - Click by text functionality
139
+ - Form filling
140
+ - Screenshot capture
141
+ - JavaScript execution
142
+ - CLI interface
143
+ - Comprehensive type hints
144
+ - Documentation and examples
145
+
146
+ ### Features
147
+ - **Your authenticated sessions**: Use Gmail, banking, SSO without re-auth
148
+ - **Accessibility tree**: Target elements like agent-browser (`[ref=e1]`)
149
+ - **Simple API**: Python class + CLI
150
+ - **Zero dependencies**: Only websockets package required
151
+
152
+ ### Breaking Changes
153
+ - None (initial release)
154
+
155
+ ### Dependencies
156
+ - Python
@@ -0,0 +1,289 @@
1
+ # browser-hybrid — Code Review Round 5 (v0.2.0 Features)
2
+
3
+ **Date:** 2026-03-23
4
+ **Branch:** feature/wait-conditions
5
+ **Commits:** 4
6
+ **Files Changed:** 4
7
+ **Lines Added:** ~950
8
+
9
+ ---
10
+
11
+ ## Commits Under Review
12
+
13
+ ```
14
+ 892780e feat: add wait conditions API
15
+ 1647bc2 feat: add cookie management API
16
+ 180454b feat: add network interception API
17
+ 0215e0e fix: Round 5 code review issues
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Section-by-Section Review
23
+
24
+ ### 1. Wait Conditions API (browser.py lines 668-824)
25
+
26
+ #### `wait_for_url()`
27
+
28
+ | Check | Result |
29
+ |-------|--------|
30
+ | Input validation | ✅ Raises ValueError for both/neither params |
31
+ | Transient error handling | ✅ Try/except with retry |
32
+ | Timeout boundary | ✅ Uses `<=` |
33
+ | Regex compilation | ✅ Compiled once, outside loop |
34
+ | Code quality | ✅ Clear, documented |
35
+
36
+ **Verdict:** ✅ PASS
37
+
38
+ #### `wait_for_selector_visible()`
39
+
40
+ | Check | Result |
41
+ |-------|--------|
42
+ | JS correctness | ✅ Checks offsetParent for visibility |
43
+ | Transient error handling | ✅ Try/except |
44
+ | Timeout boundary | ✅ Uses `<=` |
45
+ | Selector escaping | ✅ Uses escape_js_string |
46
+
47
+ **Verdict:** ✅ PASS
48
+
49
+ #### `wait_for_selector_hidden()`
50
+
51
+ | Check | Result |
52
+ |-------|--------|
53
+ | JS correctness | ✅ Returns true if element missing OR hidden |
54
+ | Transient error handling | ✅ Try/except |
55
+ | Timeout boundary | ✅ Uses `<=` |
56
+
57
+ **Verdict:** ✅ PASS
58
+
59
+ #### `wait_for_function()`
60
+
61
+ | Check | Result |
62
+ |-------|--------|
63
+ | Flexible input | ✅ Accepts any JS expression |
64
+ | Polling interval | ✅ Configurable |
65
+ | Transient error handling | ✅ Try/except |
66
+
67
+ **Verdict:** ✅ PASS
68
+
69
+ #### `wait_for_navigation()`
70
+
71
+ | Check | Result |
72
+ |-------|--------|
73
+ | Delegation | ✅ Calls wait_for_load_state correctly |
74
+ | Parameters | ✅ Passes through timeout and wait_until |
75
+
76
+ **Verdict:** ✅ PASS
77
+
78
+ ---
79
+
80
+ ### 2. Cookie Management API (browser.py lines 1413-1588)
81
+
82
+ #### `get_cookies()`
83
+
84
+ | Check | Result |
85
+ |-------|--------|
86
+ | CDP usage | ✅ Network.enable → Network.getCookies |
87
+ | URL filtering | ✅ Optional urls param |
88
+ | Return type | ✅ list[dict] |
89
+ | Error handling | ⚠️ No explicit error handling |
90
+
91
+ **Concern:** If Network.enable fails, error propagates. But this matches existing pattern in the codebase.
92
+
93
+ **Verdict:** ✅ PASS (consistent with codebase style)
94
+
95
+ #### `set_cookies()`
96
+
97
+ | Check | Result |
98
+ |-------|--------|
99
+ | CDP usage | ✅ Network.enable → Network.setCookie (per cookie) |
100
+ | Input validation | ⚠️ No validation of cookie dict |
101
+ | Error handling | ⚠️ No explicit error handling |
102
+
103
+ **Concern:** No validation that cookie dict has required `name` and `value`. CDP will return error, but error message won't be helpful.
104
+
105
+ **Recommendation (optional):** Add validation:
106
+ ```python
107
+ required = {"name", "value"}
108
+ for i, cookie in enumerate(cookies):
109
+ if not required.issubset(cookie):
110
+ raise ValueError(f"Cookie {i} missing required keys: {required - set(cookie)}")
111
+ ```
112
+
113
+ **Verdict:** ⚠️ PASS (but consider validation)
114
+
115
+ #### `delete_cookies()`
116
+
117
+ | Check | Result |
118
+ |-------|--------|
119
+ | CDP usage | ✅ Network.deleteCookies |
120
+ | Clear all logic | ✅ Gets cookies then deletes each |
121
+ | Edge case | ✅ Handles domain extraction from cookie dict |
122
+
123
+ **Verdict:** ✅ PASS
124
+
125
+ ---
126
+
127
+ ### 3. Network Interception API (browser.py lines 1590-1745)
128
+
129
+ #### `_on_network_event()`
130
+
131
+ | Check | Result |
132
+ |-------|--------|
133
+ | Thread safety | ✅ Uses setdefault |
134
+ | CDP enable | ✅ Network.enable before registering |
135
+ | Blocking | ✅ _run_async returns result (blocks) |
136
+ | Handler tracking | ✅ Stored in _network_handlers |
137
+
138
+ **Verdict:** ✅ PASS
139
+
140
+ #### `on_request()`, `on_response()`, `on_request_failed()`
141
+
142
+ | Check | Result |
143
+ |-------|--------|
144
+ | Event names | ✅ Correct CDP event names |
145
+ | Documentation | ✅ Clear docstrings with examples |
146
+
147
+ **Verdict:** ✅ PASS
148
+
149
+ #### `clear_interceptors()`
150
+
151
+ | Check | Result |
152
+ |-------|--------|
153
+ | In-memory sync clear | ✅ _network_handlers cleared first |
154
+ | Connection clear | ✅ _event_listeners cleared async |
155
+ | Blocking | ✅ _run_async blocks |
156
+
157
+ **Verdict:** ✅ PASS
158
+
159
+ ---
160
+
161
+ ### 4. Connection Event System (connection.py lines 55-58, 218-220, 255-260)
162
+
163
+ #### Event listener storage
164
+
165
+ | Check | Result |
166
+ |-------|--------|
167
+ | Type | ✅ dict[str, list[Callable]] |
168
+ | Initialization | ✅ In __init__ |
169
+
170
+ **Verdict:** ✅ PASS
171
+
172
+ #### Event dispatch in `_receive_loop()`
173
+
174
+ | Check | Result |
175
+ |-------|--------|
176
+ | Event detection | ✅ Checks "method" key |
177
+ | Dispatch | ✅ _dispatch_event called |
178
+ | Error handling | ✅ Exception caught, doesn't break loop |
179
+
180
+ **Verdict:** ✅ PASS
181
+
182
+ #### `_dispatch_event()`
183
+
184
+ | Check | Result |
185
+ |-------|--------|
186
+ | Lookup | ✅ Checks method in _event_listeners |
187
+ | Callback invocation | ✅ Iterates and calls |
188
+ | Exception handling | ✅ Caught, silently ignored |
189
+
190
+ **Verdict:** ✅ PASS
191
+
192
+ #### `on_event()`, `off_event()`
193
+
194
+ | Check | Result |
195
+ |-------|--------|
196
+ | Registration | ✅ Appends to list |
197
+ | Removal | ✅ Removes specific or all |
198
+ | Thread safety | ⚠️ Not thread-safe for concurrent registration |
199
+
200
+ **Concern:** If two callers register listeners for same event simultaneously from different threads, list append is not atomic under dict lookup.
201
+
202
+ **Analysis:** Under Python's GIL, `list.append()` is atomic. `setdefault` is also atomic. The pattern `conn.on_event(method, callback)` is fine for single-threaded use. Multi-threaded registration is a edge case.
203
+
204
+ **Verdict:** ✅ PASS (acceptable for v0.2.0)
205
+
206
+ #### Cleanup on disconnect
207
+
208
+ | Check | Result |
209
+ |-------|--------|
210
+ | _event_listeners cleared | ✅ In finally block of _receive_loop |
211
+
212
+ **Verdict:** ✅ PASS
213
+
214
+ ---
215
+
216
+ ### 5. Tests (test_browser.py +339 lines)
217
+
218
+ #### TestWaitConditions (13 tests)
219
+
220
+ | Check | Result |
221
+ |-------|--------|
222
+ | Coverage | ✅ All methods tested |
223
+ | Edge cases | ✅ timeout, transient errors, invalid params |
224
+ | Mock usage | ✅ Correct use of patch.object |
225
+
226
+ **Verdict:** ✅ PASS
227
+
228
+ #### TestCookies (5 tests)
229
+
230
+ | Check | Result |
231
+ |-------|--------|
232
+ | Coverage | ✅ get, set, delete tested |
233
+ | CDP verification | ✅ Checks correct methods called |
234
+ | Edge cases | ⚠️ No test for missing name/value |
235
+
236
+ **Recommendation:** Add test for invalid cookie dict.
237
+
238
+ **Verdict:** ✅ PASS (with minor recommendation)
239
+
240
+ #### TestNetworkInterception (7 tests)
241
+
242
+ | Check | Result |
243
+ |-------|--------|
244
+ | Registration | ✅ on_event called with correct event |
245
+ | Dispatch | ✅ _dispatch_event calls callbacks |
246
+ | Removal | ✅ off_event removes correctly |
247
+ | Clear | ✅ clear_interceptors empties dicts |
248
+
249
+ **Verdict:** ✅ PASS
250
+
251
+ ---
252
+
253
+ ## Summary
254
+
255
+ | Category | Issues | Severity |
256
+ |----------|--------|----------|
257
+ | Critical | 0 | — |
258
+ | Medium | 0 | — |
259
+ | Low | 2 | Optional improvements |
260
+
261
+ ### Low-Priority Recommendations
262
+
263
+ | # | Issue | Status |
264
+ |---|-------|--------|
265
+ | 1 | Add cookie validation for `set_cookies()` | ✅ FIXED |
266
+ | 2 | Add test for invalid cookie dict | ✅ FIXED |
267
+
268
+ ### Deferred (v0.3.0)
269
+
270
+ - `off_request()` / `off_response()` methods for selective handler removal
271
+
272
+ ---
273
+
274
+ ## Final Verdict
275
+
276
+ **✅ APPROVED FOR MERGE**
277
+
278
+ All critical and medium issues from earlier review have been fixed. Code is consistent with existing patterns. Tests pass. Ready to merge to `main` and tag `v0.2.0`.
279
+
280
+ ---
281
+
282
+ ## Merge Checklist
283
+
284
+ - [ ] Merge `feature/wait-conditions` to `main`
285
+ - [ ] Update `__version__` to `"0.2.0"`
286
+ - [ ] Update `CHANGELOG.md`
287
+ - [ ] Run full test suite
288
+ - [ ] Tag `v0.2.0`
289
+ - [ ] Push
@@ -0,0 +1,45 @@
1
+ .PHONY: install dev test lint format clean build publish
2
+
3
+ install:
4
+ pip install -e .
5
+
6
+ dev:
7
+ pip install -e ".[dev]"
8
+
9
+ test:
10
+ pytest tests/ -v --cov=browser_hybrid --cov-report=term-missing
11
+
12
+ test-integration:
13
+ pytest tests/ -v -m integration
14
+
15
+ lint:
16
+ ruff check src/
17
+ mypy src/browser_hybrid
18
+
19
+ format:
20
+ ruff format src/
21
+
22
+ clean:
23
+ rm -rf dist/ build/ *.egg-info
24
+ rm -rf src/browser_hybrid/__pycache__ tests/__pycache__
25
+ find . -type d -name __pycache__ -exec rm -rf {} +
26
+ find . -type f -name "*.pyc" -delete
27
+
28
+ build: clean
29
+ python -m build
30
+
31
+ publish: build
32
+ twine upload dist/*
33
+
34
+ check: build
35
+ twine check dist/*
36
+
37
+ # Run with Chrome debug mode
38
+ debug-chrome:
39
+ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
40
+ --remote-debugging-port=9222 \
41
+ --user-data-dir=/tmp/chrome-debug &
42
+
43
+ # Quick test
44
+ quick:
45
+ python -c "from browser_hybrid import Browser; b = Browser(); print(b.get_version().browser)"