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.
- browser_hybrid-0.2.3/.github/workflows/ci.yaml +142 -0
- browser_hybrid-0.2.3/.gitignore +61 -0
- browser_hybrid-0.2.3/CHANGELOG.md +156 -0
- browser_hybrid-0.2.3/CODE_REVIEW.md +289 -0
- browser_hybrid-0.2.3/Makefile +45 -0
- browser_hybrid-0.2.3/PKG-INFO +304 -0
- browser_hybrid-0.2.3/README.md +276 -0
- browser_hybrid-0.2.3/ROADMAP.md +92 -0
- browser_hybrid-0.2.3/docs/EVAL_STRATEGY.md +491 -0
- browser_hybrid-0.2.3/docs/RECONNECT_DESIGN.md +64 -0
- browser_hybrid-0.2.3/docs/RECONNECT_RESEARCH.md +87 -0
- browser_hybrid-0.2.3/docs/RELIABILITY_PLAN.md +425 -0
- browser_hybrid-0.2.3/docs/SPEC.md +463 -0
- browser_hybrid-0.2.3/docs/TESTING_STRATEGY.md +253 -0
- browser_hybrid-0.2.3/evals/accessibility.yaml +48 -0
- browser_hybrid-0.2.3/evals/connection.yaml +18 -0
- browser_hybrid-0.2.3/evals/elements.yaml +39 -0
- browser_hybrid-0.2.3/evals/javascript.yaml +44 -0
- browser_hybrid-0.2.3/evals/navigation.yaml +29 -0
- browser_hybrid-0.2.3/pyproject.toml +62 -0
- browser_hybrid-0.2.3/src/browser_hybrid/__init__.py +49 -0
- browser_hybrid-0.2.3/src/browser_hybrid/__main__.py +8 -0
- browser_hybrid-0.2.3/src/browser_hybrid/browser.py +2240 -0
- browser_hybrid-0.2.3/src/browser_hybrid/cli.py +303 -0
- browser_hybrid-0.2.3/src/browser_hybrid/connection.py +622 -0
- browser_hybrid-0.2.3/src/browser_hybrid/errors.py +182 -0
- browser_hybrid-0.2.3/src/browser_hybrid/evals.py +517 -0
- browser_hybrid-0.2.3/src/browser_hybrid/models.py +205 -0
- browser_hybrid-0.2.3/tests/conftest.py +87 -0
- browser_hybrid-0.2.3/tests/test_browser.py +1161 -0
- browser_hybrid-0.2.3/tests/test_concurrency.py +77 -0
- browser_hybrid-0.2.3/tests/test_connection.py +511 -0
- browser_hybrid-0.2.3/tests/test_errors.py +57 -0
- browser_hybrid-0.2.3/tests/test_evals.py +142 -0
- browser_hybrid-0.2.3/tests/test_live.py +309 -0
- browser_hybrid-0.2.3/tests/test_reconnection.py +233 -0
- 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)"
|