patchright-cli 0.1.0__py3-none-any.whl
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.
- patchright_cli/__init__.py +3 -0
- patchright_cli/__main__.py +6 -0
- patchright_cli/cli.py +348 -0
- patchright_cli/daemon.py +909 -0
- patchright_cli/snapshot.py +242 -0
- patchright_cli-0.1.0.dist-info/METADATA +311 -0
- patchright_cli-0.1.0.dist-info/RECORD +10 -0
- patchright_cli-0.1.0.dist-info/WHEEL +4 -0
- patchright_cli-0.1.0.dist-info/entry_points.txt +2 -0
- patchright_cli-0.1.0.dist-info/licenses/LICENSE +191 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Generate YAML accessibility tree snapshots from Patchright pages.
|
|
2
|
+
|
|
3
|
+
Uses a two-pass approach:
|
|
4
|
+
1. TreeWalker in strict document order to assign data-patchright-ref attributes
|
|
5
|
+
2. Recursive tree builder to produce a nested YAML structure
|
|
6
|
+
|
|
7
|
+
This guarantees ref order matches DOM order (unlike recursive walk which
|
|
8
|
+
can assign refs depth-first, causing mismatches).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
_SNAPSHOT_JS = r"""
|
|
17
|
+
(() => {
|
|
18
|
+
const SKIP = new Set(["SCRIPT","STYLE","NOSCRIPT","SVG","LINK","META","BR","HR"]);
|
|
19
|
+
const INTERACTIVE = new Set(["A","BUTTON","INPUT","TEXTAREA","SELECT","DETAILS","SUMMARY","LABEL"]);
|
|
20
|
+
const SEMANTIC = new Set(["H1","H2","H3","H4","H5","H6","NAV","MAIN","HEADER","FOOTER","ARTICLE","SECTION","ASIDE","FORM","TABLE","UL","OL","LI","IMG","VIDEO","AUDIO","IFRAME"]);
|
|
21
|
+
|
|
22
|
+
document.querySelectorAll("[data-patchright-ref]").forEach(el => el.removeAttribute("data-patchright-ref"));
|
|
23
|
+
|
|
24
|
+
function getRole(el) {
|
|
25
|
+
const ar = el.getAttribute && el.getAttribute("role");
|
|
26
|
+
if (ar) return ar;
|
|
27
|
+
const t = el.tagName;
|
|
28
|
+
const m = {A:"link",BUTTON:"button",TEXTAREA:"textbox",SELECT:"combobox",IMG:"img",TABLE:"table",FORM:"form",NAV:"navigation",MAIN:"main",HEADER:"banner",FOOTER:"contentinfo"};
|
|
29
|
+
if (m[t]) return m[t];
|
|
30
|
+
if (t === "INPUT") { const tp = (el.type||"text").toLowerCase(); return tp==="checkbox"?"checkbox":tp==="radio"?"radio":(tp==="submit"||tp==="button")?"button":"textbox"; }
|
|
31
|
+
if (t === "UL" || t === "OL") return "list";
|
|
32
|
+
if (t === "LI") return "listitem";
|
|
33
|
+
if (/^H[1-6]$/.test(t)) return "heading";
|
|
34
|
+
return t.toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getName(el) {
|
|
38
|
+
const t = el.tagName;
|
|
39
|
+
const ar = el.getAttribute && el.getAttribute("aria-label");
|
|
40
|
+
if (ar) return ar.substring(0, 120);
|
|
41
|
+
if (t === "IMG") return (el.getAttribute("alt") || "").substring(0, 120);
|
|
42
|
+
if (t === "INPUT" || t === "TEXTAREA") return (el.getAttribute("placeholder") || "").substring(0, 80);
|
|
43
|
+
if (t === "A" || t === "BUTTON" || /^H[1-6]$/.test(t) || t === "LABEL" || t === "LI" || t === "SUMMARY")
|
|
44
|
+
return (el.innerText || "").replace(/\s+/g, " ").trim().substring(0, 120);
|
|
45
|
+
const ti = el.getAttribute && el.getAttribute("title");
|
|
46
|
+
if (ti) return ti.substring(0, 80);
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isVis(el) {
|
|
51
|
+
if (!el.offsetParent && el.tagName !== "BODY" && el.tagName !== "HTML") return false;
|
|
52
|
+
try { const s = getComputedStyle(el); return s.display !== "none" && s.visibility !== "hidden" && s.opacity !== "0"; }
|
|
53
|
+
catch(e) { return false; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function shouldTag(el) {
|
|
57
|
+
return INTERACTIVE.has(el.tagName) || SEMANTIC.has(el.tagName) || !!el.getAttribute("role") || !!el.getAttribute("data-testid") || !!getName(el);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Pass 1: TreeWalker in strict document order — assign refs
|
|
61
|
+
const tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, {
|
|
62
|
+
acceptNode(n) {
|
|
63
|
+
if (SKIP.has(n.tagName)) return NodeFilter.FILTER_REJECT;
|
|
64
|
+
if (!isVis(n)) return NodeFilter.FILTER_REJECT;
|
|
65
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let counter = 0;
|
|
70
|
+
const flatList = [];
|
|
71
|
+
let node;
|
|
72
|
+
while (node = tw.nextNode()) {
|
|
73
|
+
if (shouldTag(node)) {
|
|
74
|
+
counter++;
|
|
75
|
+
const ref = "e" + counter;
|
|
76
|
+
node.setAttribute("data-patchright-ref", ref);
|
|
77
|
+
const role = getRole(node);
|
|
78
|
+
const name = getName(node);
|
|
79
|
+
const entry = { ref, role, name, tag: node.tagName };
|
|
80
|
+
if (node.tagName === "INPUT" || node.tagName === "TEXTAREA" || node.tagName === "SELECT") {
|
|
81
|
+
if (node.value) entry.value = node.value.substring(0, 200);
|
|
82
|
+
if (node.type === "checkbox" || node.type === "radio") entry.checked = node.checked;
|
|
83
|
+
if (node.disabled) entry.disabled = true;
|
|
84
|
+
}
|
|
85
|
+
if (node.tagName === "A" && node.href) entry.url = node.href.substring(0, 200);
|
|
86
|
+
if (/^H[1-6]$/.test(node.tagName)) entry.level = parseInt(node.tagName[1]);
|
|
87
|
+
flatList.push(entry);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Pass 2: Build nested tree reading refs from DOM attributes
|
|
92
|
+
function buildTree(el, depth) {
|
|
93
|
+
if (depth > 12 || !el || !el.tagName) return null;
|
|
94
|
+
if (SKIP.has(el.tagName)) return null;
|
|
95
|
+
const ref = el.getAttribute("data-patchright-ref");
|
|
96
|
+
const children = [];
|
|
97
|
+
for (const ch of el.children) {
|
|
98
|
+
if (!isVis(ch)) continue;
|
|
99
|
+
const c = buildTree(ch, depth + 1);
|
|
100
|
+
if (c) children.push(c);
|
|
101
|
+
}
|
|
102
|
+
if (!ref && !children.length) return null;
|
|
103
|
+
const out = {};
|
|
104
|
+
if (ref) {
|
|
105
|
+
const f = flatList.find(x => x.ref === ref);
|
|
106
|
+
if (f) Object.assign(out, f);
|
|
107
|
+
delete out.tag;
|
|
108
|
+
}
|
|
109
|
+
if (children.length) out.children = children;
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const tree = buildTree(document.body, 0) || { role: "document", name: document.title, children: [] };
|
|
114
|
+
return { tree, flatList };
|
|
115
|
+
})()
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _walk_tree(node: dict, depth: int = 0) -> list[str]:
|
|
120
|
+
"""Recursively walk the parsed tree and produce YAML lines."""
|
|
121
|
+
lines: list[str] = []
|
|
122
|
+
ref = node.get("ref", "")
|
|
123
|
+
role = node.get("role", "")
|
|
124
|
+
name = node.get("name", "")
|
|
125
|
+
|
|
126
|
+
if ref:
|
|
127
|
+
indent = " " * depth
|
|
128
|
+
lines.append(f"{indent}- ref: {ref}")
|
|
129
|
+
lines.append(f"{indent} role: {role}")
|
|
130
|
+
if name:
|
|
131
|
+
lines.append(f"{indent} name: {_yaml_escape(name)}")
|
|
132
|
+
for key in ("value", "url"):
|
|
133
|
+
val = node.get(key, "")
|
|
134
|
+
if val:
|
|
135
|
+
lines.append(f"{indent} {key}: {_yaml_escape(str(val))}")
|
|
136
|
+
if node.get("checked") is not None:
|
|
137
|
+
lines.append(f"{indent} checked: {str(node['checked']).lower()}")
|
|
138
|
+
if node.get("disabled"):
|
|
139
|
+
lines.append(f"{indent} disabled: true")
|
|
140
|
+
if node.get("level") is not None:
|
|
141
|
+
lines.append(f"{indent} level: {node['level']}")
|
|
142
|
+
|
|
143
|
+
children = node.get("children", [])
|
|
144
|
+
if children:
|
|
145
|
+
lines.append(f"{indent} children:")
|
|
146
|
+
for child in children:
|
|
147
|
+
lines.extend(_walk_tree(child, depth + 1))
|
|
148
|
+
else:
|
|
149
|
+
for child in node.get("children", []):
|
|
150
|
+
lines.extend(_walk_tree(child, depth))
|
|
151
|
+
|
|
152
|
+
return lines
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _yaml_escape(s: str) -> str:
|
|
156
|
+
if not s:
|
|
157
|
+
return '""'
|
|
158
|
+
if any(
|
|
159
|
+
c in s
|
|
160
|
+
for c in (
|
|
161
|
+
":",
|
|
162
|
+
"#",
|
|
163
|
+
"'",
|
|
164
|
+
'"',
|
|
165
|
+
"\n",
|
|
166
|
+
"[",
|
|
167
|
+
"]",
|
|
168
|
+
"{",
|
|
169
|
+
"}",
|
|
170
|
+
",",
|
|
171
|
+
"&",
|
|
172
|
+
"*",
|
|
173
|
+
"?",
|
|
174
|
+
"|",
|
|
175
|
+
"-",
|
|
176
|
+
"<",
|
|
177
|
+
">",
|
|
178
|
+
"=",
|
|
179
|
+
"!",
|
|
180
|
+
"%",
|
|
181
|
+
"@",
|
|
182
|
+
"`",
|
|
183
|
+
)
|
|
184
|
+
):
|
|
185
|
+
escaped = s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
|
|
186
|
+
return f'"{escaped}"'
|
|
187
|
+
return s
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def take_snapshot(page) -> tuple[str, dict[str, dict]]:
|
|
191
|
+
"""Take a DOM snapshot. Returns (yaml_text, ref_map)."""
|
|
192
|
+
result = None
|
|
193
|
+
try:
|
|
194
|
+
result = await page.evaluate(_SNAPSHOT_JS, isolated_context=False)
|
|
195
|
+
except TypeError:
|
|
196
|
+
try:
|
|
197
|
+
result = await page.evaluate(_SNAPSHOT_JS)
|
|
198
|
+
except Exception:
|
|
199
|
+
pass
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
if not result or not result.get("tree"):
|
|
204
|
+
return "# Empty page - no accessible elements found\n", {}
|
|
205
|
+
|
|
206
|
+
flat_list = result.get("flatList", [])
|
|
207
|
+
|
|
208
|
+
refs: dict[str, dict] = {}
|
|
209
|
+
lines: list[str] = []
|
|
210
|
+
for item in flat_list:
|
|
211
|
+
refs[item["ref"]] = item
|
|
212
|
+
lines.append(f"- ref: {item['ref']}")
|
|
213
|
+
lines.append(f" role: {item.get('role', '')}")
|
|
214
|
+
name = item.get("name", "")
|
|
215
|
+
if name:
|
|
216
|
+
lines.append(f" name: {_yaml_escape(name)}")
|
|
217
|
+
for key in ("value", "url"):
|
|
218
|
+
val = item.get(key, "")
|
|
219
|
+
if val:
|
|
220
|
+
lines.append(f" {key}: {_yaml_escape(str(val))}")
|
|
221
|
+
if item.get("checked") is not None:
|
|
222
|
+
lines.append(f" checked: {str(item['checked']).lower()}")
|
|
223
|
+
if item.get("disabled"):
|
|
224
|
+
lines.append(" disabled: true")
|
|
225
|
+
if item.get("level") is not None:
|
|
226
|
+
lines.append(f" level: {item['level']}")
|
|
227
|
+
|
|
228
|
+
yaml_text = "\n".join(lines) + "\n"
|
|
229
|
+
return yaml_text, refs
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def save_snapshot(yaml_text: str, cwd: str | None = None) -> str:
|
|
233
|
+
"""Save snapshot YAML to .patchright-cli/ directory."""
|
|
234
|
+
base = Path(cwd) if cwd else Path.cwd()
|
|
235
|
+
snap_dir = base / ".patchright-cli"
|
|
236
|
+
snap_dir.mkdir(parents=True, exist_ok=True)
|
|
237
|
+
|
|
238
|
+
timestamp = int(time.time() * 1000)
|
|
239
|
+
filename = f"page-{timestamp}.yml"
|
|
240
|
+
filepath = snap_dir / filename
|
|
241
|
+
filepath.write_text(yaml_text, encoding="utf-8")
|
|
242
|
+
return str(filepath)
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: patchright-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Anti-detect browser automation CLI using Patchright (undetected Playwright fork)
|
|
5
|
+
Project-URL: Homepage, https://github.com/AhaiMk01/patchright-cli
|
|
6
|
+
Project-URL: Repository, https://github.com/AhaiMk01/patchright-cli
|
|
7
|
+
Project-URL: Issues, https://github.com/AhaiMk01/patchright-cli/issues
|
|
8
|
+
Author: zpahai
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: anti-detect,automation,browser,cli,patchright,playwright,scraping
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: click
|
|
25
|
+
Requires-Dist: patchright
|
|
26
|
+
Requires-Dist: pyyaml
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# patchright-cli
|
|
30
|
+
|
|
31
|
+
<!-- badges (hidden until PyPI publish)
|
|
32
|
+
[](https://pypi.org/project/patchright-cli/)
|
|
33
|
+
[](https://pypi.org/project/patchright-cli/)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](https://pypi.org/project/patchright-cli/)
|
|
36
|
+
-->
|
|
37
|
+
|
|
38
|
+
Anti-detect browser automation CLI. Same command interface as [Microsoft's playwright-cli](https://github.com/microsoft/playwright-cli) but using [Patchright](https://github.com/kaliiiiiiiiii/patchright-python) (undetected Playwright fork) to bypass bot detection.
|
|
39
|
+
|
|
40
|
+
## Why
|
|
41
|
+
|
|
42
|
+
Regular Playwright and Chrome DevTools get blocked by Akamai, Cloudflare, and other anti-bot systems. Patchright patches Chromium's detection vectors (`navigator.webdriver`, WebGL fingerprints, etc.) so automated browsers look like real users.
|
|
43
|
+
|
|
44
|
+
This CLI wraps Patchright in the same command interface as playwright-cli, so AI agents (Claude Code, etc.) can automate anti-bot-protected sites with the same workflow they'd use for regular Playwright.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
### For Humans
|
|
49
|
+
|
|
50
|
+
Just tell your AI agent:
|
|
51
|
+
|
|
52
|
+
> Install and set up patchright-cli by following the instructions here:
|
|
53
|
+
> https://raw.githubusercontent.com/AhaiMk01/patchright-cli/main/docs/installation.md
|
|
54
|
+
|
|
55
|
+
Seriously — let the agent do it. It handles configs better than you.
|
|
56
|
+
|
|
57
|
+
### For LLM Agents
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
curl -s https://raw.githubusercontent.com/AhaiMk01/patchright-cli/main/docs/installation.md
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Manual Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# With pip
|
|
67
|
+
pip install patchright-cli
|
|
68
|
+
|
|
69
|
+
# With uv (recommended)
|
|
70
|
+
uv tool install patchright-cli
|
|
71
|
+
|
|
72
|
+
# Run without installing (like npx)
|
|
73
|
+
uvx patchright-cli open https://example.com
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### From Source
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone https://github.com/AhaiMk01/patchright-cli.git
|
|
80
|
+
cd patchright-cli
|
|
81
|
+
uv venv && uv pip install -e .
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Quick Start
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Launch undetected Chrome and navigate
|
|
88
|
+
patchright-cli open https://example.com
|
|
89
|
+
|
|
90
|
+
# Take a snapshot to see interactive elements with refs
|
|
91
|
+
patchright-cli snapshot
|
|
92
|
+
|
|
93
|
+
# Interact using refs from the snapshot
|
|
94
|
+
patchright-cli click e2
|
|
95
|
+
patchright-cli fill e5 "search query"
|
|
96
|
+
patchright-cli press Enter
|
|
97
|
+
|
|
98
|
+
# Take a screenshot
|
|
99
|
+
patchright-cli screenshot
|
|
100
|
+
|
|
101
|
+
# Close the browser
|
|
102
|
+
patchright-cli close
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Architecture
|
|
106
|
+
|
|
107
|
+
```mermaid
|
|
108
|
+
graph LR
|
|
109
|
+
A[CLI Client<br/>cli.py] -->|TCP/JSON<br/>localhost:9321| B[Daemon<br/>daemon.py]
|
|
110
|
+
B -->|Patchright<br/>CDP| C[Chrome<br/>stealth]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- **Daemon** (`daemon.py`): Long-running Python process managing browser sessions via Patchright. Listens on `localhost:9321`. Auto-starts on first `open` command.
|
|
114
|
+
- **CLI** (`cli.py`): Thin client. Parses args, sends JSON to daemon, prints result. Each invocation connects and disconnects — the browser stays open between commands.
|
|
115
|
+
- **Snapshot** (`snapshot.py`): Walks the DOM with `TreeWalker` in document order, assigns `data-patchright-ref` attributes, outputs a flat YAML list of interactive elements.
|
|
116
|
+
|
|
117
|
+
## Commands
|
|
118
|
+
|
|
119
|
+
### Core
|
|
120
|
+
```bash
|
|
121
|
+
patchright-cli open [url] # Launch browser
|
|
122
|
+
patchright-cli open --persistent # With persistent profile
|
|
123
|
+
patchright-cli goto <url> # Navigate
|
|
124
|
+
patchright-cli click <ref> # Click element
|
|
125
|
+
patchright-cli dblclick <ref> # Double-click
|
|
126
|
+
patchright-cli fill <ref> <value> # Fill text input
|
|
127
|
+
patchright-cli type <text> # Type via keyboard
|
|
128
|
+
patchright-cli hover <ref> # Hover over element
|
|
129
|
+
patchright-cli select <ref> <value> # Select dropdown option
|
|
130
|
+
patchright-cli check <ref> # Check checkbox
|
|
131
|
+
patchright-cli uncheck <ref> # Uncheck checkbox
|
|
132
|
+
patchright-cli drag <from> <to> # Drag and drop
|
|
133
|
+
patchright-cli snapshot # Accessibility snapshot
|
|
134
|
+
patchright-cli snapshot --filename=f # Save to custom path
|
|
135
|
+
patchright-cli eval <expr> # Run JavaScript
|
|
136
|
+
patchright-cli screenshot # Full page screenshot
|
|
137
|
+
patchright-cli screenshot <ref> # Element screenshot
|
|
138
|
+
patchright-cli screenshot --filename=f # Save to custom path
|
|
139
|
+
patchright-cli close # Close session
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Navigation
|
|
143
|
+
```bash
|
|
144
|
+
patchright-cli go-back
|
|
145
|
+
patchright-cli go-forward
|
|
146
|
+
patchright-cli reload
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Keyboard / Mouse
|
|
150
|
+
```bash
|
|
151
|
+
patchright-cli press Enter
|
|
152
|
+
patchright-cli keydown Shift
|
|
153
|
+
patchright-cli keyup Shift
|
|
154
|
+
patchright-cli mousemove 150 300
|
|
155
|
+
patchright-cli mousedown [button]
|
|
156
|
+
patchright-cli mouseup [button]
|
|
157
|
+
patchright-cli mousewheel 0 100
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Dialog
|
|
161
|
+
```bash
|
|
162
|
+
patchright-cli dialog-accept [text] # Accept next alert/confirm/prompt
|
|
163
|
+
patchright-cli dialog-dismiss # Dismiss next dialog
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Upload / Resize
|
|
167
|
+
```bash
|
|
168
|
+
patchright-cli upload ./file.pdf # Upload to first file input
|
|
169
|
+
patchright-cli upload ./file.pdf e5 # Upload to specific input
|
|
170
|
+
patchright-cli resize 1920 1080 # Resize viewport
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Tabs
|
|
174
|
+
```bash
|
|
175
|
+
patchright-cli tab-list
|
|
176
|
+
patchright-cli tab-new [url]
|
|
177
|
+
patchright-cli tab-select <index>
|
|
178
|
+
patchright-cli tab-close [index]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### State Persistence
|
|
182
|
+
```bash
|
|
183
|
+
patchright-cli state-save [file] # Save cookies + localStorage
|
|
184
|
+
patchright-cli state-load <file> # Restore saved state
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Storage
|
|
188
|
+
```bash
|
|
189
|
+
# Cookies
|
|
190
|
+
patchright-cli cookie-list
|
|
191
|
+
patchright-cli cookie-list --domain=example.com
|
|
192
|
+
patchright-cli cookie-get <name>
|
|
193
|
+
patchright-cli cookie-set <name> <value>
|
|
194
|
+
patchright-cli cookie-set <name> <value> --domain=example.com --httpOnly --secure
|
|
195
|
+
patchright-cli cookie-delete <name>
|
|
196
|
+
patchright-cli cookie-clear
|
|
197
|
+
|
|
198
|
+
# localStorage
|
|
199
|
+
patchright-cli localstorage-list
|
|
200
|
+
patchright-cli localstorage-get <key>
|
|
201
|
+
patchright-cli localstorage-set <key> <value>
|
|
202
|
+
patchright-cli localstorage-delete <key>
|
|
203
|
+
patchright-cli localstorage-clear
|
|
204
|
+
|
|
205
|
+
# sessionStorage
|
|
206
|
+
patchright-cli sessionstorage-list
|
|
207
|
+
patchright-cli sessionstorage-get <key>
|
|
208
|
+
patchright-cli sessionstorage-set <key> <value>
|
|
209
|
+
patchright-cli sessionstorage-delete <key>
|
|
210
|
+
patchright-cli sessionstorage-clear
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Request Mocking
|
|
214
|
+
```bash
|
|
215
|
+
patchright-cli route "**/*.jpg" --status=404
|
|
216
|
+
patchright-cli route "https://api.example.com/**" --body='{"mock":true}'
|
|
217
|
+
patchright-cli route-list
|
|
218
|
+
patchright-cli unroute "**/*.jpg"
|
|
219
|
+
patchright-cli unroute # Remove all routes
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Tracing / PDF
|
|
223
|
+
```bash
|
|
224
|
+
patchright-cli tracing-start
|
|
225
|
+
patchright-cli tracing-stop # Saves .zip trace file
|
|
226
|
+
patchright-cli pdf --filename=page.pdf
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### DevTools
|
|
230
|
+
```bash
|
|
231
|
+
patchright-cli console # All console messages
|
|
232
|
+
patchright-cli console warning # Filter by level
|
|
233
|
+
patchright-cli network # Network request log
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Sessions
|
|
237
|
+
```bash
|
|
238
|
+
patchright-cli -s=mysession open https://example.com --persistent
|
|
239
|
+
patchright-cli -s=mysession click e6
|
|
240
|
+
patchright-cli -s=mysession close
|
|
241
|
+
patchright-cli list # List all sessions
|
|
242
|
+
patchright-cli close-all
|
|
243
|
+
patchright-cli kill-all
|
|
244
|
+
patchright-cli delete-data # Delete persistent profile
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Snapshots
|
|
248
|
+
|
|
249
|
+
After each state-changing command, the CLI outputs page info and a YAML snapshot:
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
### Page
|
|
253
|
+
- Page URL: https://example.com/
|
|
254
|
+
- Page Title: Example Domain
|
|
255
|
+
### Snapshot
|
|
256
|
+
[Snapshot](.patchright-cli/page-1774376207818.yml)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The snapshot lists interactive elements with refs:
|
|
260
|
+
|
|
261
|
+
```yaml
|
|
262
|
+
- ref: e1
|
|
263
|
+
role: heading
|
|
264
|
+
name: Example Domain
|
|
265
|
+
level: 1
|
|
266
|
+
- ref: e2
|
|
267
|
+
role: link
|
|
268
|
+
name: Learn more
|
|
269
|
+
url: "https://iana.org/domains/example"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Use refs in commands: `patchright-cli click e2`, `patchright-cli fill e5 "text"`.
|
|
273
|
+
|
|
274
|
+
## Anti-Detect Features
|
|
275
|
+
|
|
276
|
+
- Real Chrome browser (not Chromium)
|
|
277
|
+
- Patchright patches `navigator.webdriver` and other detection vectors
|
|
278
|
+
- Persistent profiles maintain cookies/sessions across runs
|
|
279
|
+
- No custom user-agent or headers (natural fingerprint)
|
|
280
|
+
- Headed by default (headless is more detectable)
|
|
281
|
+
|
|
282
|
+
## Claude Code Integration
|
|
283
|
+
|
|
284
|
+
Add the skill to your project:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
cp -r skills/patchright-cli ~/.claude/skills/
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Then use in Claude Code:
|
|
291
|
+
```
|
|
292
|
+
patchright-cli open https://protected-site.com
|
|
293
|
+
patchright-cli snapshot
|
|
294
|
+
patchright-cli fill e3 "username"
|
|
295
|
+
patchright-cli fill e4 "password"
|
|
296
|
+
patchright-cli click e5
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Star History
|
|
300
|
+
|
|
301
|
+
<a href="https://star-history.com/#AhaiMk01/patchright-cli&Date">
|
|
302
|
+
<picture>
|
|
303
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=AhaiMk01/patchright-cli&type=Date&theme=dark" />
|
|
304
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=AhaiMk01/patchright-cli&type=Date" />
|
|
305
|
+
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=AhaiMk01/patchright-cli&type=Date" />
|
|
306
|
+
</picture>
|
|
307
|
+
</a>
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
Apache 2.0 — same as [playwright-cli](https://github.com/microsoft/playwright-cli)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
patchright_cli/__init__.py,sha256=l3g0CuQ8etdd49K2XOpNKHxK0LTJMU-3YrmBH5LfGeQ,100
|
|
2
|
+
patchright_cli/__main__.py,sha256=iVuBuI_rPOENLhffOEHoS7JWCDBQciqT1kPXjRuhbZ4,127
|
|
3
|
+
patchright_cli/cli.py,sha256=2GDRDwrkzGo3sW0pTwGErRQr_qRHy6SOGTLuHdLOgDM,12241
|
|
4
|
+
patchright_cli/daemon.py,sha256=w7L7s6mVjEs9DbHHsLqRpJPjijpqXrL6W49KFBQPqvg,35427
|
|
5
|
+
patchright_cli/snapshot.py,sha256=KTIcRyUl2kUFs3yfpPi3YVvz17zRjr4rcfqFgiyt1PM,8906
|
|
6
|
+
patchright_cli-0.1.0.dist-info/METADATA,sha256=ia2kLfALkYX_eccpgtJVnr6Su77mN1Xend_PntOP3ls,9961
|
|
7
|
+
patchright_cli-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
patchright_cli-0.1.0.dist-info/entry_points.txt,sha256=Syh904eOQA8ylakyqIvG4LmIHx3LMjgeNcnFap3O0NQ,59
|
|
9
|
+
patchright_cli-0.1.0.dist-info/licenses/LICENSE,sha256=adszqL4YR4pQUrc7LeAFxeg5Mc2cvljXp7QPlzKVWm8,10760
|
|
10
|
+
patchright_cli-0.1.0.dist-info/RECORD,,
|