cdpbrowser 0.1.0__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.
- cdpbrowser-0.1.0/PKG-INFO +436 -0
- cdpbrowser-0.1.0/README.md +413 -0
- cdpbrowser-0.1.0/pyproject.toml +40 -0
- cdpbrowser-0.1.0/setup.cfg +4 -0
- cdpbrowser-0.1.0/src/CdpBrowser.py +19 -0
- cdpbrowser-0.1.0/src/cdpbrowser/__init__.py +68 -0
- cdpbrowser-0.1.0/src/cdpbrowser/bridge.js +909 -0
- cdpbrowser-0.1.0/src/cdpbrowser/cdp/__init__.py +32 -0
- cdpbrowser-0.1.0/src/cdpbrowser/cdp/chrome.py +495 -0
- cdpbrowser-0.1.0/src/cdpbrowser/cdp/errors.py +47 -0
- cdpbrowser-0.1.0/src/cdpbrowser/cdp/transport.py +384 -0
- cdpbrowser-0.1.0/src/cdpbrowser/keys.py +128 -0
- cdpbrowser-0.1.0/src/cdpbrowser/library.py +1440 -0
- cdpbrowser-0.1.0/src/cdpbrowser/listener.py +112 -0
- cdpbrowser-0.1.0/src/cdpbrowser/locators.py +183 -0
- cdpbrowser-0.1.0/src/cdpbrowser/page.py +1880 -0
- cdpbrowser-0.1.0/src/cdpbrowser/polling.py +133 -0
- cdpbrowser-0.1.0/src/cdpbrowser/promises.py +345 -0
- cdpbrowser-0.1.0/src/cdpbrowser/recorder.js +258 -0
- cdpbrowser-0.1.0/src/cdpbrowser/recorder.py +208 -0
- cdpbrowser-0.1.0/src/cdpbrowser/visual.py +286 -0
- cdpbrowser-0.1.0/src/cdpbrowser.egg-info/PKG-INFO +436 -0
- cdpbrowser-0.1.0/src/cdpbrowser.egg-info/SOURCES.txt +59 -0
- cdpbrowser-0.1.0/src/cdpbrowser.egg-info/dependency_links.txt +1 -0
- cdpbrowser-0.1.0/src/cdpbrowser.egg-info/requires.txt +10 -0
- cdpbrowser-0.1.0/src/cdpbrowser.egg-info/top_level.txt +2 -0
- cdpbrowser-0.1.0/tests/test_accessibility.py +134 -0
- cdpbrowser-0.1.0/tests/test_chrome.py +353 -0
- cdpbrowser-0.1.0/tests/test_cookies.py +117 -0
- cdpbrowser-0.1.0/tests/test_dialogs.py +91 -0
- cdpbrowser-0.1.0/tests/test_downloads.py +87 -0
- cdpbrowser-0.1.0/tests/test_errors.py +94 -0
- cdpbrowser-0.1.0/tests/test_escape_hatches.py +86 -0
- cdpbrowser-0.1.0/tests/test_ext_locator.py +87 -0
- cdpbrowser-0.1.0/tests/test_frames.py +270 -0
- cdpbrowser-0.1.0/tests/test_gapfill_core.py +208 -0
- cdpbrowser-0.1.0/tests/test_gapfill_library.py +262 -0
- cdpbrowser-0.1.0/tests/test_keys.py +135 -0
- cdpbrowser-0.1.0/tests/test_known_issues.py +173 -0
- cdpbrowser-0.1.0/tests/test_library_polling.py +320 -0
- cdpbrowser-0.1.0/tests/test_listener.py +165 -0
- cdpbrowser-0.1.0/tests/test_locators.py +245 -0
- cdpbrowser-0.1.0/tests/test_packaging.py +110 -0
- cdpbrowser-0.1.0/tests/test_page.py +277 -0
- cdpbrowser-0.1.0/tests/test_polling.py +178 -0
- cdpbrowser-0.1.0/tests/test_promises.py +105 -0
- cdpbrowser-0.1.0/tests/test_real_keys.py +94 -0
- cdpbrowser-0.1.0/tests/test_real_mouse.py +104 -0
- cdpbrowser-0.1.0/tests/test_recorder_flows.py +119 -0
- cdpbrowser-0.1.0/tests/test_recorder_integration.py +128 -0
- cdpbrowser-0.1.0/tests/test_recorder_unit.py +89 -0
- cdpbrowser-0.1.0/tests/test_shadow_dom.py +100 -0
- cdpbrowser-0.1.0/tests/test_state_keywords.py +79 -0
- cdpbrowser-0.1.0/tests/test_tabs.py +91 -0
- cdpbrowser-0.1.0/tests/test_transport.py +173 -0
- cdpbrowser-0.1.0/tests/test_transport_real_chrome.py +163 -0
- cdpbrowser-0.1.0/tests/test_upload_scroll.py +76 -0
- cdpbrowser-0.1.0/tests/test_viewport.py +100 -0
- cdpbrowser-0.1.0/tests/test_visual_advanced.py +157 -0
- cdpbrowser-0.1.0/tests/test_visual_engine.py +122 -0
- cdpbrowser-0.1.0/tests/test_visual_keyword.py +90 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cdpbrowser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Framework-independent pure-CDP browser automation core for Python, with an optional Robot Framework adapter
|
|
5
|
+
Project-URL: Homepage, https://github.com/lhjnano/cdpbrowser
|
|
6
|
+
Classifier: Development Status :: 4 - Beta
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Topic :: Software Development :: Testing
|
|
13
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: websockets>=17.0
|
|
17
|
+
Provides-Extra: robot
|
|
18
|
+
Requires-Dist: robotframework>=7.0; extra == "robot"
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest; extra == "dev"
|
|
21
|
+
Provides-Extra: visual
|
|
22
|
+
Requires-Dist: Pillow>=10; extra == "visual"
|
|
23
|
+
|
|
24
|
+
# CdpBrowser
|
|
25
|
+
|
|
26
|
+
CdpBrowser is a browser automation library that speaks the Chrome DevTools
|
|
27
|
+
Protocol directly from Python. It has no driver binaries, no Node runtime,
|
|
28
|
+
and no Selenium. The core is framework independent; a Robot Framework
|
|
29
|
+
keyword adapter is available as an optional extra.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install cdpbrowser # core only
|
|
33
|
+
pip install "cdpbrowser[robot]" # Robot Framework keywords
|
|
34
|
+
pip install "cdpbrowser[visual]" # Pillow, for visual regression
|
|
35
|
+
pip install "cdpbrowser[dev,robot,visual]" # development
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Without robotframework installed, `cdpbrowser.CdpBrowser` raises an
|
|
39
|
+
`ImportError` that names the `[robot]` extra. The core (`PageSession` and
|
|
40
|
+
the modules under `cdpbrowser.*`) needs no extras.
|
|
41
|
+
|
|
42
|
+
## Design
|
|
43
|
+
|
|
44
|
+
- **Direct CDP.** One background asyncio loop per library, one WebSocket to
|
|
45
|
+
Chrome. Nothing in between.
|
|
46
|
+
- **Poll by default.** Interaction and assertion keywords retry their
|
|
47
|
+
condition until the deadline set by `Set Timeout`. A missing element is a
|
|
48
|
+
retry, not an error.
|
|
49
|
+
- **Atomic bridge.** bridge.js runs find, visibility, and enabled checks in
|
|
50
|
+
a single step inside the browser. Python receives plain JSON.
|
|
51
|
+
- **Evidence.** Screenshots accumulate under
|
|
52
|
+
`{outputdir}/evidence/{suite}/{test}/` with per-test sequence numbers. A
|
|
53
|
+
listener saves the last screen on failure.
|
|
54
|
+
- **Optional adapter.** The core imports nothing from Robot. Only
|
|
55
|
+
`library.py` and `listener.py` do.
|
|
56
|
+
|
|
57
|
+
## Quick start, Robot Framework
|
|
58
|
+
|
|
59
|
+
```robotframework
|
|
60
|
+
*** Settings ***
|
|
61
|
+
Library CdpBrowser
|
|
62
|
+
|
|
63
|
+
*** Test Cases ***
|
|
64
|
+
Hello CdpBrowser
|
|
65
|
+
Go To file:///tmp/demo.html
|
|
66
|
+
${title}= Get Title
|
|
67
|
+
Should Be Equal ${title} Demo
|
|
68
|
+
Click testid:submit-button
|
|
69
|
+
Element Should Be Visible testid:done-panel
|
|
70
|
+
[Teardown] Close Browser
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Chrome launches lazily on the first keyword. `headless` defaults to `auto`:
|
|
74
|
+
headless when `CI` is set or `DISPLAY` is missing. `--no-sandbox` is added
|
|
75
|
+
under root, in detected CI environments, or when
|
|
76
|
+
`CDPBROWSER_NO_SANDBOX=1`; `--disable-dev-shm-usage` is added when
|
|
77
|
+
`/dev/shm` is under 1 GiB.
|
|
78
|
+
|
|
79
|
+
### Import arguments
|
|
80
|
+
|
|
81
|
+
| Argument | Default | Description |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| `chrome_path` | auto-detected | Chrome executable path |
|
|
84
|
+
| `headless` | `auto` | `True`, `False`, or `auto` |
|
|
85
|
+
| `timeout` | `5s` | Default polling deadline (Robot time string) |
|
|
86
|
+
| `poll_interval` | `0.1s` | Polling retry interval |
|
|
87
|
+
| `baseline_dir` | `visual-baselines/` | Root for visual-regression baselines |
|
|
88
|
+
|
|
89
|
+
## Using the core from Python
|
|
90
|
+
|
|
91
|
+
The core is a synchronous Python API. The test suite drives it without
|
|
92
|
+
Robot everywhere.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from cdpbrowser import ChromeProcess, CdpConnection, PageSession, find_chrome
|
|
96
|
+
|
|
97
|
+
chrome = ChromeProcess(find_chrome(), headless=True)
|
|
98
|
+
chrome.start()
|
|
99
|
+
try:
|
|
100
|
+
session = PageSession(CdpConnection(chrome.ws_url)).attach()
|
|
101
|
+
session.navigate("https://example.com")
|
|
102
|
+
|
|
103
|
+
session.call("click", {"s": "id", "v": "submit"}) # testid: selector
|
|
104
|
+
text = session.call("getText", {"s": "id", "v": "result"})
|
|
105
|
+
|
|
106
|
+
handle = session.arm_download()
|
|
107
|
+
session.call("click", {"s": "id", "v": "download-link"})
|
|
108
|
+
saved = session.wait_download(handle, 10.0)
|
|
109
|
+
|
|
110
|
+
# Frames, viewport, cookies, and real key/mouse input are also plain
|
|
111
|
+
# methods. See the PageSession docstrings.
|
|
112
|
+
finally:
|
|
113
|
+
session.close()
|
|
114
|
+
chrome.stop()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
For waits, `cdpbrowser.polling.poll_until` retries any callable until a
|
|
118
|
+
deadline. It is the same primitive the Robot adapter uses.
|
|
119
|
+
|
|
120
|
+
## Concepts
|
|
121
|
+
|
|
122
|
+
### Dialogs: arm before the trigger
|
|
123
|
+
|
|
124
|
+
While a JS dialog (alert, confirm, prompt) is open, the CDP session blocks
|
|
125
|
+
other commands, including the evaluation inside `Click`. Reserve the dialog
|
|
126
|
+
first, then trigger it, then collect the result:
|
|
127
|
+
|
|
128
|
+
```robotframework
|
|
129
|
+
${handle}= Promise Next Alert action=ACCEPT
|
|
130
|
+
Click testid:submit
|
|
131
|
+
${text}= Wait For ${handle}
|
|
132
|
+
Should Be Equal As Strings ${text} Passwords don't match
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
A dialog opened without an arm is dismissed automatically after
|
|
136
|
+
`Set Timeout` elapses, with a warning. This prevents deadlocks.
|
|
137
|
+
|
|
138
|
+
`Handle Alert` combines the three steps for clicks:
|
|
139
|
+
`Handle Alert ACCEPT Click testid:delete`.
|
|
140
|
+
|
|
141
|
+
### Downloads
|
|
142
|
+
|
|
143
|
+
Downloads do not block the session, so the order matters less, but the
|
|
144
|
+
convention is the same:
|
|
145
|
+
|
|
146
|
+
```robotframework
|
|
147
|
+
${promise}= Promise Next Download
|
|
148
|
+
Click testid:download-link
|
|
149
|
+
${file}= Wait For ${promise}
|
|
150
|
+
File Should Exist ${file}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The first `Promise Next Download` configures Chrome to save under
|
|
154
|
+
`results/downloads/{suite}/{test}/`. `Wait For` returns the saved file's
|
|
155
|
+
full path.
|
|
156
|
+
|
|
157
|
+
### Fast and real input tracks
|
|
158
|
+
|
|
159
|
+
`Click` and `Fill Text` simulate input in JavaScript. `Type Text`,
|
|
160
|
+
`Press Keys`, and the mouse keywords send real input events through CDP.
|
|
161
|
+
Use the real track when the application checks `isTrusted` or listens for
|
|
162
|
+
keydown. ASCII goes through per-key events; non-ASCII such as Hangul falls
|
|
163
|
+
back to `Input.insertText` and is documented as such.
|
|
164
|
+
|
|
165
|
+
### Visual regression
|
|
166
|
+
|
|
167
|
+
`Page Should Match Baseline` compares the viewport against a committed PNG.
|
|
168
|
+
Tolerances are a per-channel delta (`pixel_delta`) and a mismatch-ratio cap
|
|
169
|
+
(`max_mismatch_ratio`). Failures write `.actual.png` and `.diff.png`
|
|
170
|
+
artifacts and describe the change in words: one localized region, a whole
|
|
171
|
+
image shift, or scattered changes. Fix the viewport with
|
|
172
|
+
`Set Viewport Size` before comparing. Without Pillow the comparison is
|
|
173
|
+
exact-match only.
|
|
174
|
+
|
|
175
|
+
### Recording and replay
|
|
176
|
+
|
|
177
|
+
`Start Recording` captures real input on the current tab: manual driving,
|
|
178
|
+
real-mouse keywords, or any keyword that produces DOM events. Typing
|
|
179
|
+
collapses into one fill per element. Observed dialogs and downloads are
|
|
180
|
+
attached to the action that triggered them.
|
|
181
|
+
|
|
182
|
+
```robotframework
|
|
183
|
+
Start Recording
|
|
184
|
+
Go To https://app.example.com/
|
|
185
|
+
# interact, by hand or with keywords
|
|
186
|
+
Save Recording ${CURDIR}${/}recorded.robot
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The generated Robot test uses poll-by-default keywords, so replays absorb
|
|
190
|
+
timing differences. `Save Recording` with `format=Python` writes a
|
|
191
|
+
plain-Python replay against the core instead. Recording covers the main
|
|
192
|
+
frame only.
|
|
193
|
+
|
|
194
|
+
## Keyword reference
|
|
195
|
+
|
|
196
|
+
Unless noted, interaction and assertion keywords poll until the deadline.
|
|
197
|
+
|
|
198
|
+
### Session
|
|
199
|
+
|
|
200
|
+
| Keyword | Arguments | Description |
|
|
201
|
+
| --- | --- | --- |
|
|
202
|
+
| `Open Browser` | | Explicit launch. No-op when already open. |
|
|
203
|
+
| `Close Browser` | | Closes all tabs, the connection, and Chrome. Idempotent. |
|
|
204
|
+
| `Go To` | `url` | Navigates, waits for load, returns the final URL. |
|
|
205
|
+
| `Get Title` | | `document.title` |
|
|
206
|
+
| `Get Current Url` | | `location.href` |
|
|
207
|
+
|
|
208
|
+
### Interaction
|
|
209
|
+
|
|
210
|
+
| Keyword | Arguments | Description |
|
|
211
|
+
| --- | --- | --- |
|
|
212
|
+
| `Click` | `selector` | Atomic find, visibility, and enabled checks, then click. |
|
|
213
|
+
| `Fill Text` | `selector`, `text` | Native setter plus input and change events. |
|
|
214
|
+
| `Type Text` | `selector`, `text` | Real key events. Appends; does not clear. Non-ASCII uses `insertText`. |
|
|
215
|
+
| `Press Keys` | `*keys` | Special keys (`ENTER`, `TAB`, `ARROW_*`, ...) as real events. |
|
|
216
|
+
| `Click With Real Mouse` | `selector` | Scrolls into view, clicks the center with real events. Returns `"x,y"`. |
|
|
217
|
+
| `Click At Coordinates` | `x`, `y` | Real click at viewport coordinates. For canvas. |
|
|
218
|
+
| `Hover` | `selector` | Real pointer move. Triggers CSS `:hover`. |
|
|
219
|
+
| `Drag From To` | `source`, `target` | Real press, move, release. No HTML5 drag events. |
|
|
220
|
+
| `Scroll By` | `x=0`, `y=0` | Real wheel scroll in pixels. Returns the final scrollY. |
|
|
221
|
+
| `Scroll To Element` | `selector` | Scrolls the element into the viewport. |
|
|
222
|
+
| `Upload File` | `selector`, `*paths` | Sets files directly with `DOM.setFileInputFiles`. Absolute paths; `css=` or `testid:` selectors only. |
|
|
223
|
+
|
|
224
|
+
### Assertions
|
|
225
|
+
|
|
226
|
+
| Keyword | Arguments | Description |
|
|
227
|
+
| --- | --- | --- |
|
|
228
|
+
| `Element Should Be Visible` | `selector` | Waits for visibility. |
|
|
229
|
+
| `Element Should Exist` | `selector` | Waits for DOM presence. |
|
|
230
|
+
| `Element Should Not Exist` | `selector` | Confirms presence first, then waits for removal. |
|
|
231
|
+
|
|
232
|
+
### Reading state
|
|
233
|
+
|
|
234
|
+
| Keyword | Arguments | Returns | Description |
|
|
235
|
+
| --- | --- | --- | --- |
|
|
236
|
+
| `Get Text` | `selector` | `str` | `textContent`. Does not read input values. |
|
|
237
|
+
| `Get Attribute` | `selector`, `name` | `str` or `None` | A missing attribute is `None`, not a failure. |
|
|
238
|
+
| `Get Element Count` | `selector` | `int` | Snapshot. Zero is valid; precede with `Element Should Exist` to wait. |
|
|
239
|
+
|
|
240
|
+
### Frames
|
|
241
|
+
|
|
242
|
+
| Keyword | Arguments | Description |
|
|
243
|
+
| --- | --- | --- |
|
|
244
|
+
| `Switch To Frame` | `frame` | Numeric index, `name`, `id`, or a CSS selector for the iframe. Calls stack. |
|
|
245
|
+
| `Reset Frame` | `scope=` | Pops one level, or all with `scope=ALL`. |
|
|
246
|
+
|
|
247
|
+
Same-site frames only. Cross-site iframes (OOPIF) run in a separate process
|
|
248
|
+
and have no execution context in this session.
|
|
249
|
+
|
|
250
|
+
### Dialogs and downloads
|
|
251
|
+
|
|
252
|
+
| Keyword | Arguments | Description |
|
|
253
|
+
| --- | --- | --- |
|
|
254
|
+
| `Promise Next Alert` | `action`, `prompt_text=` | Arms the next dialog. Returns a handle. |
|
|
255
|
+
| `Promise Next Download` | | Arms the next download completion. Returns a handle. |
|
|
256
|
+
| `Wait For` | `handle`, `timeout=` | Waits for an armed promise. Returns the dialog text or the saved file path. |
|
|
257
|
+
| `Handle Alert` | `action`, `*trigger`, `text=` | Arm, run trigger keyword, wait, and optionally assert the text. |
|
|
258
|
+
|
|
259
|
+
### Tabs
|
|
260
|
+
|
|
261
|
+
| Keyword | Arguments | Description |
|
|
262
|
+
| --- | --- | --- |
|
|
263
|
+
| `New Tab` | `url=` | Creates a tab, switches to it, returns the 0-based index. |
|
|
264
|
+
| `Switch To Tab` | `tab` | 0-based index, or a title or URL substring. |
|
|
265
|
+
| `Close Tab` | `tab=` | Default current. Refuses the last tab; use `Close Browser`. |
|
|
266
|
+
|
|
267
|
+
Dialog arms, downloads, and frame scope are isolated per tab.
|
|
268
|
+
|
|
269
|
+
### Cookies
|
|
270
|
+
|
|
271
|
+
| Keyword | Arguments | Description |
|
|
272
|
+
| --- | --- | --- |
|
|
273
|
+
| `Get Cookies` | `urls=` | Comma-separated URLs narrow the result. Use http(s) origins. |
|
|
274
|
+
| `Set Cookie` | `url`, `name`, `value`, ... | Optional `expires`, `http_only`, `secure`, `same_site`. |
|
|
275
|
+
| `Delete Cookie` | `name`, `url=` | Deletes by name, optionally per origin. |
|
|
276
|
+
| `Delete All Cookies` | | Browser-global. |
|
|
277
|
+
|
|
278
|
+
### Viewport
|
|
279
|
+
|
|
280
|
+
| Keyword | Arguments | Description |
|
|
281
|
+
| --- | --- | --- |
|
|
282
|
+
| `Set Viewport Size` | `width`, `height`, `device_scale_factor=1.0`, `mobile=False` | Per tab. `mobile=True` enables touch emulation. |
|
|
283
|
+
| `Reset Viewport` | | Clears the override. |
|
|
284
|
+
|
|
285
|
+
### Visual regression
|
|
286
|
+
|
|
287
|
+
| Keyword | Arguments | Description |
|
|
288
|
+
| --- | --- | --- |
|
|
289
|
+
| `Page Should Match Baseline` | `name`, `pixel_delta=0`, `max_mismatch_ratio=0.0`, `mask=` | Compares against `{baseline_dir}/{name}.png`. |
|
|
290
|
+
| `Update Baseline` | `name`, `mask=` | Recreates a baseline. Use the same mask as the comparison. |
|
|
291
|
+
|
|
292
|
+
`mask` takes comma-separated selectors and blacks out matching elements
|
|
293
|
+
before capture, for volatile areas such as timestamps. Mask boxes follow
|
|
294
|
+
element rects, so keep length-changing content in a fixed-width container.
|
|
295
|
+
Set `CDPBROWSER_UPDATE_BASELINES=1` to rewrite baselines instead of
|
|
296
|
+
failing.
|
|
297
|
+
|
|
298
|
+
### Recording
|
|
299
|
+
|
|
300
|
+
| Keyword | Arguments | Description |
|
|
301
|
+
| --- | --- | --- |
|
|
302
|
+
| `Start Recording` | | Records real input on the current tab. |
|
|
303
|
+
| `Stop Recording` | `name=` | Stops and returns a Robot replay script. |
|
|
304
|
+
| `Save Recording` | `path`, `name=`, `format=Robot` | Stops and writes the script. `format=Python` for a core script. |
|
|
305
|
+
|
|
306
|
+
### Escape hatches
|
|
307
|
+
|
|
308
|
+
| Keyword | Arguments | Description |
|
|
309
|
+
| --- | --- | --- |
|
|
310
|
+
| `Run Javascript` | `expression` | Evaluation with JSON return and awaited promises. Respects frame scope. |
|
|
311
|
+
| `Execute Javascript` | `script` | Same execution, but the return value is discarded before serialization. For jQuery-style chains whose result fails CDP serialization. |
|
|
312
|
+
| `Execute CDP Command` | `method`, `params_json=` | Raw CDP. `Browser.*` and `Target.*` route at browser level. |
|
|
313
|
+
| `Insert Text` | `text` | `Input.insertText` into the focused element. The IME bypass. |
|
|
314
|
+
|
|
315
|
+
## Selectors
|
|
316
|
+
|
|
317
|
+
| Syntax | Strategy | Description |
|
|
318
|
+
| --- | --- | --- |
|
|
319
|
+
| `.nav > a` | css | Default when unprefixed. |
|
|
320
|
+
| `testid:submit` | testid | Matches `data-testid`. |
|
|
321
|
+
| `text:Log in` | text | Exact match first, then case-insensitive partial. |
|
|
322
|
+
| `x://button[1]` | xpath | XPath. |
|
|
323
|
+
| `css:#id` | css | Explicit prefix, for values that collide with the reserved ones. |
|
|
324
|
+
| `role:button Save` | role | Explicit `role` attribute, then implicit roles. Optional accessible name after the first space. Case-insensitive. |
|
|
325
|
+
| `label:User name` | label | Accessible name, simplified accname. Case-insensitive. |
|
|
326
|
+
| `deep:...` | modifier | Pierces open shadow DOM. Opt-in. Closed roots are inaccessible. |
|
|
327
|
+
| `ext:...` | ext | Ext JS ComponentQuery when `window.Ext` is exposed. DOM-independent. |
|
|
328
|
+
|
|
329
|
+
Selector stability, best to worst: explicit `data-testid`, accessibility
|
|
330
|
+
anchors, explicit id or form name, stable text, structural CSS or XPath.
|
|
331
|
+
Never anchor on generated ids such as `ext-gen123` or `:r1:`.
|
|
332
|
+
|
|
333
|
+
## Scope
|
|
334
|
+
|
|
335
|
+
Controllable: what the browser exposes about the page, meaning the DOM,
|
|
336
|
+
accessibility tree, events, downloads, uploads, tabs, and network. Not
|
|
337
|
+
controllable: the OS, browser chrome, and physical devices.
|
|
338
|
+
|
|
339
|
+
| Level | Scope |
|
|
340
|
+
| --- | --- |
|
|
341
|
+
| Full | Apps whose elements live in the DOM or accessibility tree, including shadow DOM via `deep:` and same-site iframes. |
|
|
342
|
+
| Partial | Canvas and WebGL. No element anchors; use `Click At Coordinates` with visual assertions. |
|
|
343
|
+
| Unsupported | OS-native dialogs, desktop-to-browser drags, real IME composition (use `Insert Text`), anti-bot evasion. |
|
|
344
|
+
|
|
345
|
+
## Parallel and multi-agent use
|
|
346
|
+
|
|
347
|
+
Several agents or test runners can drive CdpBrowser at the same time.
|
|
348
|
+
|
|
349
|
+
**Separate processes, separate browsers.** Every launch uses
|
|
350
|
+
`--remote-debugging-port=0`, so the OS assigns a free port, and a fresh
|
|
351
|
+
temporary `--user-data-dir`, so profiles never collide. Nothing is shared
|
|
352
|
+
at module level: each process gets its own Chrome, connection, and state.
|
|
353
|
+
Parallel `pabot` runs work the same way. When several processes share one
|
|
354
|
+
working directory, give each its own `CDPBROWSER_OUTPUT_DIR` so evidence
|
|
355
|
+
and download paths do not overlap.
|
|
356
|
+
|
|
357
|
+
**Several browsers inside one process.** The core is instance based, so
|
|
358
|
+
nothing stops a single script from running two independent browsers:
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
chrome_a = ChromeProcess(find_chrome(), headless=True).start()
|
|
362
|
+
chrome_b = ChromeProcess(find_chrome(), headless=True).start()
|
|
363
|
+
session_a = PageSession(CdpConnection(chrome_a.ws_url)).attach()
|
|
364
|
+
session_b = PageSession(CdpConnection(chrome_b.ws_url)).attach()
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Each `PageSession` owns its target, dialog arms, downloads, and frame
|
|
368
|
+
scope. Tabs within one browser are cheaper still: see `New Tab`.
|
|
369
|
+
|
|
370
|
+
**Not supported: sharing one Chrome across processes.** There is no
|
|
371
|
+
keyword to attach to an externally running browser. Ownership of the
|
|
372
|
+
lifecycle and the browser-global download path would need an explicit
|
|
373
|
+
policy first.
|
|
374
|
+
|
|
375
|
+
The Robot keyword layer drives one browser per library instance; a single
|
|
376
|
+
suite that needs two browsers should either use `New Tab`, or run the
|
|
377
|
+
second browser through the core from a keyword.
|
|
378
|
+
|
|
379
|
+
## Evidence
|
|
380
|
+
|
|
381
|
+
Captures land in `{outputdir}/evidence/{suite}/{test}/NNNN-{label}.png`.
|
|
382
|
+
Numbering resets per test and is shared between `Numbered Screenshot` and
|
|
383
|
+
automatic captures. `Set Evidence Mode` selects `off`, `on-failure`
|
|
384
|
+
(default), or `every-step`.
|
|
385
|
+
|
|
386
|
+
## Environment variables
|
|
387
|
+
|
|
388
|
+
| Variable | Effect |
|
|
389
|
+
| --- | --- |
|
|
390
|
+
| `CDPBROWSER_CHROME_PATH` | Chrome path, highest detection priority. |
|
|
391
|
+
| `CHROME_PATH` | Fallback path. |
|
|
392
|
+
| `CDPBROWSER_NO_SANDBOX` | `1` forces `--no-sandbox`. |
|
|
393
|
+
| `CDPBROWSER_DISABLE_DEV_SHM` | `1` forces `--disable-dev-shm-usage`. |
|
|
394
|
+
| `CDPBROWSER_UPDATE_BASELINES` | `1` rewrites visual baselines instead of failing. |
|
|
395
|
+
| `CDPBROWSER_SEND_RETRIES` | How many times a timed-out CDP command is retried. Default 1. |
|
|
396
|
+
| `CDPBROWSER_OUTPUT_DIR` | Overrides the artifact outputdir. |
|
|
397
|
+
| `CI` | Set means headless by default. |
|
|
398
|
+
|
|
399
|
+
## Development
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
python3 -m venv .venv
|
|
403
|
+
.venv/bin/pip install -e ".[dev,robot,visual]"
|
|
404
|
+
|
|
405
|
+
make test # pytest
|
|
406
|
+
make atest # robot acceptance suite
|
|
407
|
+
make coverage # combined branch coverage plus docs/coverage.md
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Real-Chrome tests find the binary through `find_chrome()` and skip when
|
|
411
|
+
absent. Unit tests use a mock CDP WebSocket server (`tests/cdp_mock.py`).
|
|
412
|
+
Packaging guards run the core with robotframework import-blocked. Robot
|
|
413
|
+
fixtures are plain HTML files under `atest/fixtures/`.
|
|
414
|
+
|
|
415
|
+
## Status and coverage
|
|
416
|
+
|
|
417
|
+
Implemented: the full keyword set above, including recording and replay,
|
|
418
|
+
visual regression, and the `ext:` registry locator. Planned: an external
|
|
419
|
+
locator-registry plugin API, element-scoped visual assertions, OOPIF
|
|
420
|
+
support via child-target attach, iframe-interior recording.
|
|
421
|
+
|
|
422
|
+
The suite is 403 pytest tests plus 28 Robot acceptance tests, measured
|
|
423
|
+
together at 87% branch coverage. Details, including the gap classification
|
|
424
|
+
and a ledger of bugs found during development, are in
|
|
425
|
+
[docs/coverage.md](docs/coverage.md). An HTML rendering of the same report
|
|
426
|
+
sits at [docs/coverage.html](docs/coverage.html).
|
|
427
|
+
|
|
428
|
+
## Verified environments
|
|
429
|
+
|
|
430
|
+
| Component | Version |
|
|
431
|
+
| --- | --- |
|
|
432
|
+
| Python | 3.10 or newer; 3.12 verified |
|
|
433
|
+
| Robot Framework | 7.4.2 |
|
|
434
|
+
| websockets | 17.1 |
|
|
435
|
+
| Chrome | Chrome for Testing 131 locally, Chrome stable 152 on CI |
|
|
436
|
+
| Pillow, optional | 12.x |
|