@rester159/blacktip 0.1.0
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.
- package/AGENTS.md +249 -0
- package/LICENSE +38 -0
- package/README.md +234 -0
- package/dist/behavioral/calibration.d.ts +145 -0
- package/dist/behavioral/calibration.d.ts.map +1 -0
- package/dist/behavioral/calibration.js +242 -0
- package/dist/behavioral/calibration.js.map +1 -0
- package/dist/behavioral-engine.d.ts +156 -0
- package/dist/behavioral-engine.d.ts.map +1 -0
- package/dist/behavioral-engine.js +521 -0
- package/dist/behavioral-engine.js.map +1 -0
- package/dist/blacktip.d.ts +289 -0
- package/dist/blacktip.d.ts.map +1 -0
- package/dist/blacktip.js +1574 -0
- package/dist/blacktip.js.map +1 -0
- package/dist/browser-core.d.ts +47 -0
- package/dist/browser-core.d.ts.map +1 -0
- package/dist/browser-core.js +375 -0
- package/dist/browser-core.js.map +1 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +226 -0
- package/dist/cli.js.map +1 -0
- package/dist/element-finder.d.ts +42 -0
- package/dist/element-finder.d.ts.map +1 -0
- package/dist/element-finder.js +240 -0
- package/dist/element-finder.js.map +1 -0
- package/dist/evasion.d.ts +39 -0
- package/dist/evasion.d.ts.map +1 -0
- package/dist/evasion.js +488 -0
- package/dist/evasion.js.map +1 -0
- package/dist/fingerprint.d.ts +19 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +171 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +13 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +42 -0
- package/dist/logging.js.map +1 -0
- package/dist/observability.d.ts +69 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +189 -0
- package/dist/observability.js.map +1 -0
- package/dist/proxy-pool.d.ts +101 -0
- package/dist/proxy-pool.d.ts.map +1 -0
- package/dist/proxy-pool.js +156 -0
- package/dist/proxy-pool.js.map +1 -0
- package/dist/snapshot.d.ts +59 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +91 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/examples/01-basic-navigate.ts +40 -0
- package/examples/02-login-with-mfa.ts +68 -0
- package/examples/03-agent-serve-mode.md +98 -0
- package/package.json +62 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* BlackTip CLI
|
|
4
|
+
*
|
|
5
|
+
* Server mode: npx blacktip serve [--port 9779]
|
|
6
|
+
* Send command: npx blacktip send "<js>" [--port 9779] [--pretty]
|
|
7
|
+
* Send from file: npx blacktip send --file <path> [--port 9779] [--pretty]
|
|
8
|
+
* Send from stdin: echo "<js>" | npx blacktip send --stdin [--pretty]
|
|
9
|
+
* Batch commands: npx blacktip batch <file.json> [--port 9779]
|
|
10
|
+
* Resume pause: npx blacktip resume <pauseId> "<value>" [--port 9779]
|
|
11
|
+
* Exec (one-shot): npx blacktip exec "<js>"
|
|
12
|
+
*
|
|
13
|
+
* The server returns a JSON bundle for each command:
|
|
14
|
+
* { ok, result?, url?, title?, screenshotPath?, screenshotB64?, durationMs, error? }
|
|
15
|
+
*
|
|
16
|
+
* --pretty formats the bundle but omits the screenshot payload so the
|
|
17
|
+
* console output is readable. The screenshot is still saved to disk.
|
|
18
|
+
*/
|
|
19
|
+
import { createConnection } from 'node:net';
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { BlackTip } from './blacktip.js';
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const command = args[0];
|
|
24
|
+
const portIdx = args.indexOf('--port');
|
|
25
|
+
const port = portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : 9779;
|
|
26
|
+
const pretty = args.includes('--pretty');
|
|
27
|
+
function argValue(flag) {
|
|
28
|
+
const idx = args.indexOf(flag);
|
|
29
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
30
|
+
}
|
|
31
|
+
const DELIM = '\n__END__\n';
|
|
32
|
+
/**
|
|
33
|
+
* Send a raw payload to the server and print the response(s).
|
|
34
|
+
* Returns when the connection closes or the final frame is received.
|
|
35
|
+
*/
|
|
36
|
+
async function sendAndPrint(payload, expectMultiple = false) {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const client = createConnection({ port, host: '127.0.0.1' });
|
|
39
|
+
let buf = '';
|
|
40
|
+
let lastPrinted = false;
|
|
41
|
+
client.on('data', (chunk) => {
|
|
42
|
+
buf += chunk.toString();
|
|
43
|
+
while (buf.includes(DELIM)) {
|
|
44
|
+
const idx = buf.indexOf(DELIM);
|
|
45
|
+
const frame = buf.slice(0, idx);
|
|
46
|
+
buf = buf.slice(idx + DELIM.length);
|
|
47
|
+
printFrame(frame);
|
|
48
|
+
lastPrinted = true;
|
|
49
|
+
if (!expectMultiple) {
|
|
50
|
+
client.destroy();
|
|
51
|
+
resolve();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
client.on('close', () => {
|
|
57
|
+
if (!lastPrinted && buf.length > 0)
|
|
58
|
+
printFrame(buf);
|
|
59
|
+
resolve();
|
|
60
|
+
});
|
|
61
|
+
client.on('error', () => {
|
|
62
|
+
console.error('Server not running. Start with: npx blacktip serve');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
65
|
+
client.write(payload + DELIM);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function printFrame(frame) {
|
|
69
|
+
// Try to parse as JSON; if it parses, pretty-print (optionally
|
|
70
|
+
// stripping the huge base64 screenshot). Fall back to raw output.
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(frame);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
process.stdout.write(frame + '\n');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (pretty && parsed && typeof parsed === 'object') {
|
|
80
|
+
const clone = { ...parsed };
|
|
81
|
+
if ('screenshotB64' in clone) {
|
|
82
|
+
const bytes = typeof clone.screenshotBytes === 'number' ? clone.screenshotBytes : clone.screenshotB64.length;
|
|
83
|
+
clone.screenshotB64 = `<${bytes} bytes, saved to ${clone.screenshotPath ?? 'disk'}>`;
|
|
84
|
+
}
|
|
85
|
+
process.stdout.write(JSON.stringify(clone, null, 2) + '\n');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
process.stdout.write(JSON.stringify(parsed) + '\n');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (command === 'serve') {
|
|
92
|
+
const bt = new BlackTip({
|
|
93
|
+
headless: false,
|
|
94
|
+
logLevel: 'info',
|
|
95
|
+
deviceProfile: 'desktop-windows',
|
|
96
|
+
behaviorProfile: 'scraper',
|
|
97
|
+
timeout: 10000,
|
|
98
|
+
retryAttempts: 2,
|
|
99
|
+
screenResolution: { width: 1440, height: 900 },
|
|
100
|
+
});
|
|
101
|
+
bt.on('action', (e) => {
|
|
102
|
+
console.log(`[${e.action}] ${e.target} → ${e.outcome} (${e.duration}ms)`);
|
|
103
|
+
});
|
|
104
|
+
await bt.serve(port);
|
|
105
|
+
console.log(`BlackTip server ready on port ${port}`);
|
|
106
|
+
console.log(`Send commands: npx blacktip send "await bt.navigate('...')" --port ${port}`);
|
|
107
|
+
}
|
|
108
|
+
else if (command === 'send') {
|
|
109
|
+
// Three input sources:
|
|
110
|
+
// 1. Positional argument: npx blacktip send "..."
|
|
111
|
+
// 2. --file <path>: npx blacktip send --file cmd.js
|
|
112
|
+
// 3. --stdin: echo "..." | npx blacktip send --stdin
|
|
113
|
+
let cmd;
|
|
114
|
+
const fileArg = argValue('--file');
|
|
115
|
+
const stdinFlag = args.includes('--stdin');
|
|
116
|
+
if (fileArg) {
|
|
117
|
+
cmd = readFileSync(fileArg, 'utf-8');
|
|
118
|
+
}
|
|
119
|
+
else if (stdinFlag) {
|
|
120
|
+
cmd = readFileSync(0, 'utf-8'); // read from stdin
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Positional argument (everything after "send" that isn't a flag).
|
|
124
|
+
cmd = args.slice(1)
|
|
125
|
+
.filter((a, i, arr) => {
|
|
126
|
+
if (a === '--port' || a === '--file' || a === '--stdin' || a === '--pretty')
|
|
127
|
+
return false;
|
|
128
|
+
if (i > 0 && (arr[i - 1] === '--port' || arr[i - 1] === '--file'))
|
|
129
|
+
return false;
|
|
130
|
+
return true;
|
|
131
|
+
})
|
|
132
|
+
.join(' ');
|
|
133
|
+
}
|
|
134
|
+
if (!cmd || !cmd.trim()) {
|
|
135
|
+
console.error('Usage: npx blacktip send "<js>" | --file <path> | --stdin');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
await sendAndPrint(cmd);
|
|
139
|
+
}
|
|
140
|
+
else if (command === 'batch') {
|
|
141
|
+
// Batch mode: read a JSON array of commands from a file and send them
|
|
142
|
+
// as a single BATCH request. Server runs them sequentially.
|
|
143
|
+
const filePath = args[1];
|
|
144
|
+
if (!filePath) {
|
|
145
|
+
console.error('Usage: npx blacktip batch <file.json>');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
149
|
+
const parsed = JSON.parse(raw);
|
|
150
|
+
if (!Array.isArray(parsed)) {
|
|
151
|
+
console.error('batch file must contain a JSON array of command strings');
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
await sendAndPrint('BATCH\n' + JSON.stringify(parsed));
|
|
155
|
+
}
|
|
156
|
+
else if (command === 'resume') {
|
|
157
|
+
// Resume a paused command: npx blacktip resume <id> "<value>"
|
|
158
|
+
const id = args[1];
|
|
159
|
+
const value = args[2] ?? '';
|
|
160
|
+
if (!id) {
|
|
161
|
+
console.error('Usage: npx blacktip resume <pauseId> "<value>"');
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
await sendAndPrint(`RESUME ${id}\n${value}`);
|
|
165
|
+
}
|
|
166
|
+
else if (command === 'pending') {
|
|
167
|
+
// List currently-paused commands: npx blacktip pending
|
|
168
|
+
await sendAndPrint('LIST_PENDING');
|
|
169
|
+
}
|
|
170
|
+
else if (command === 'exec') {
|
|
171
|
+
// One-shot: launch browser, run command, close.
|
|
172
|
+
const cmd = args.slice(1).filter((a) => a !== '--port' && a !== String(port)).join(' ');
|
|
173
|
+
if (!cmd) {
|
|
174
|
+
console.error('Usage: npx blacktip exec "<js>"');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
const bt = new BlackTip({ headless: false, timeout: 10000, retryAttempts: 2 });
|
|
178
|
+
await bt.launch();
|
|
179
|
+
try {
|
|
180
|
+
const fn = new Function('bt', `return (async () => { ${cmd} })();`);
|
|
181
|
+
const result = await fn(bt);
|
|
182
|
+
if (result !== undefined)
|
|
183
|
+
console.log(JSON.stringify(result));
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
187
|
+
}
|
|
188
|
+
await bt.close();
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log(`BlackTip CLI
|
|
192
|
+
|
|
193
|
+
Usage:
|
|
194
|
+
npx blacktip serve [--port 9779]
|
|
195
|
+
Start the TCP command server. Leaves a browser running.
|
|
196
|
+
|
|
197
|
+
npx blacktip send "<js>" [--port N] [--pretty]
|
|
198
|
+
npx blacktip send --file <path> [--pretty]
|
|
199
|
+
echo "<js>" | npx blacktip send --stdin [--pretty]
|
|
200
|
+
Send a single JS command to the running server. Returns a bundle:
|
|
201
|
+
{ ok, result, url, title, screenshotPath, durationMs, ... }
|
|
202
|
+
Use --pretty for human-readable output with screenshot payload
|
|
203
|
+
replaced by a placeholder.
|
|
204
|
+
|
|
205
|
+
npx blacktip batch <file.json>
|
|
206
|
+
Run an array of commands sequentially. Server returns all bundles
|
|
207
|
+
in one response, stopping on first failure.
|
|
208
|
+
|
|
209
|
+
npx blacktip resume <pauseId> "<value>"
|
|
210
|
+
Resume a command that called bt.pauseForInput().
|
|
211
|
+
|
|
212
|
+
npx blacktip pending
|
|
213
|
+
List currently-paused commands with their prompts.
|
|
214
|
+
|
|
215
|
+
npx blacktip exec "<js>"
|
|
216
|
+
One-shot: launch a fresh browser, run the command, close.
|
|
217
|
+
|
|
218
|
+
Examples:
|
|
219
|
+
npx blacktip serve
|
|
220
|
+
npx blacktip send "await bt.navigate('https://example.com')" --pretty
|
|
221
|
+
npx blacktip send --file login-flow.js --pretty
|
|
222
|
+
npx blacktip batch anthem-claim.json
|
|
223
|
+
npx blacktip resume pause-123456-78901 "116170"
|
|
224
|
+
`);
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Page, Frame, ElementHandle as PlaywrightElement } from 'patchright';
|
|
2
|
+
import type { Logger } from './logging.js';
|
|
3
|
+
import type { BoundingBox } from './types.js';
|
|
4
|
+
export declare class ElementFinder {
|
|
5
|
+
private logger;
|
|
6
|
+
constructor(logger: Logger);
|
|
7
|
+
find(pageOrFrame: Page | Frame, selector: string, options?: {
|
|
8
|
+
timeout?: number;
|
|
9
|
+
visible?: boolean;
|
|
10
|
+
}): Promise<PlaywrightElement>;
|
|
11
|
+
findInFrames(page: Page, selector: string, options?: {
|
|
12
|
+
timeout?: number;
|
|
13
|
+
visible?: boolean;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
element: PlaywrightElement;
|
|
16
|
+
frame: Page | Frame;
|
|
17
|
+
}>;
|
|
18
|
+
private looksLikeSelector;
|
|
19
|
+
private looksLikeXPath;
|
|
20
|
+
getBoundingBox(element: PlaywrightElement): Promise<BoundingBox | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Find an element inside any open shadow root reachable from the document.
|
|
23
|
+
*
|
|
24
|
+
* Recursively walks the DOM and each encountered `shadowRoot`, testing
|
|
25
|
+
* CSS selector matches at every level. Modern component libraries
|
|
26
|
+
* (Lit, Stencil, Material Web Components, Ionic) wrap everything in
|
|
27
|
+
* shadow DOM, and Playwright's default CSS engine sometimes misses
|
|
28
|
+
* deeply-nested selectors. This walker is explicit and predictable.
|
|
29
|
+
*
|
|
30
|
+
* LIMITATION: closed shadow roots are opaque to JavaScript by design.
|
|
31
|
+
* Elements inside `attachShadow({mode: 'closed'})` are not reachable
|
|
32
|
+
* by any JS-only method; reaching them requires CDP `DOM.describeNode`
|
|
33
|
+
* with `pierce: true`, which is outside this method's scope. Open
|
|
34
|
+
* shadow roots (the overwhelming majority in practice) are fully
|
|
35
|
+
* supported.
|
|
36
|
+
*/
|
|
37
|
+
findInShadowDom(pageOrFrame: Page | Frame, cssSelector: string, options?: {
|
|
38
|
+
timeout?: number;
|
|
39
|
+
}): Promise<PlaywrightElement>;
|
|
40
|
+
private notFound;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=element-finder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-finder.d.ts","sourceRoot":"","sources":["../src/element-finder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,IAAI,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA8B9C,qBAAa,aAAa;IACZ,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE5B,IAAI,CACR,WAAW,EAAE,IAAI,GAAG,KAAK,EACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAChD,OAAO,CAAC,iBAAiB,CAAC;IA2GvB,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAChD,OAAO,CAAC;QAAE,OAAO,EAAE,iBAAiB,CAAC;QAAC,KAAK,EAAE,IAAI,GAAG,KAAK,CAAA;KAAE,CAAC;IAyB/D,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,cAAc;IAKhB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAM7E;;;;;;;;;;;;;;;OAeG;IACG,eAAe,CACnB,WAAW,EAAE,IAAI,GAAG,KAAK,EACzB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7B,OAAO,CAAC,iBAAiB,CAAC;IA0C7B,OAAO,CAAC,QAAQ;CAGjB"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
const CSS_TAG_NAMES = new Set([
|
|
2
|
+
// Structural
|
|
3
|
+
'div', 'span', 'section', 'article', 'aside', 'nav', 'header', 'footer',
|
|
4
|
+
'main', 'body', 'html', 'details', 'summary', 'dialog',
|
|
5
|
+
// Text
|
|
6
|
+
'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'code',
|
|
7
|
+
'em', 'strong', 'small', 'mark', 'time',
|
|
8
|
+
// Lists & tables
|
|
9
|
+
'ul', 'ol', 'li', 'dl', 'dt', 'dd',
|
|
10
|
+
'table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th', 'caption', 'colgroup', 'col',
|
|
11
|
+
// Forms
|
|
12
|
+
'form', 'input', 'textarea', 'select', 'option', 'optgroup', 'button', 'label',
|
|
13
|
+
'fieldset', 'legend', 'datalist', 'output', 'meter', 'progress',
|
|
14
|
+
// Media & embeds
|
|
15
|
+
'img', 'picture', 'video', 'audio', 'canvas', 'svg', 'iframe', 'embed',
|
|
16
|
+
'object', 'source', 'track', 'map', 'area',
|
|
17
|
+
// Interactive
|
|
18
|
+
'a',
|
|
19
|
+
]);
|
|
20
|
+
const CSS_INDICATOR_CHARS = ['.', '#', '[', '>', ':', '+', '~', '=', '*'];
|
|
21
|
+
/**
|
|
22
|
+
* Per-strategy timeout — short so we fail fast and try the next strategy.
|
|
23
|
+
* The overall timeout is the caller's budget; individual strategies get a slice.
|
|
24
|
+
*/
|
|
25
|
+
const STRATEGY_TIMEOUT_MS = 3000;
|
|
26
|
+
export class ElementFinder {
|
|
27
|
+
logger;
|
|
28
|
+
constructor(logger) {
|
|
29
|
+
this.logger = logger;
|
|
30
|
+
}
|
|
31
|
+
async find(pageOrFrame, selector, options) {
|
|
32
|
+
const overallTimeout = options?.timeout ?? 15_000;
|
|
33
|
+
const visible = options?.visible ?? false;
|
|
34
|
+
const state = visible ? 'visible' : 'attached';
|
|
35
|
+
const triedStrategies = [];
|
|
36
|
+
const deadline = Date.now() + overallTimeout;
|
|
37
|
+
// Helper: time remaining, capped to per-strategy max
|
|
38
|
+
const strategyTimeout = () => Math.min(STRATEGY_TIMEOUT_MS, Math.max(500, deadline - Date.now()));
|
|
39
|
+
// 1. CSS selector — if input looks like one
|
|
40
|
+
if (this.looksLikeSelector(selector)) {
|
|
41
|
+
try {
|
|
42
|
+
const el = await pageOrFrame.waitForSelector(selector, { timeout: strategyTimeout(), state });
|
|
43
|
+
if (el) {
|
|
44
|
+
this.logger.info('Element found via CSS selector', { selector });
|
|
45
|
+
return el;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
triedStrategies.push('CSS');
|
|
50
|
+
this.logger.debug('CSS strategy failed', { selector });
|
|
51
|
+
}
|
|
52
|
+
if (Date.now() >= deadline)
|
|
53
|
+
throw this.notFound(selector, triedStrategies);
|
|
54
|
+
}
|
|
55
|
+
// 2. XPath
|
|
56
|
+
if (this.looksLikeXPath(selector)) {
|
|
57
|
+
try {
|
|
58
|
+
const el = await pageOrFrame.waitForSelector(`xpath=${selector}`, { timeout: strategyTimeout(), state });
|
|
59
|
+
if (el) {
|
|
60
|
+
this.logger.info('Element found via XPath', { selector });
|
|
61
|
+
return el;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
triedStrategies.push('XPath');
|
|
66
|
+
this.logger.debug('XPath strategy failed', { selector });
|
|
67
|
+
}
|
|
68
|
+
if (Date.now() >= deadline)
|
|
69
|
+
throw this.notFound(selector, triedStrategies);
|
|
70
|
+
}
|
|
71
|
+
// 3. Label association — runs BEFORE text match so that searching by
|
|
72
|
+
// a label's visible text returns the associated input, not the label
|
|
73
|
+
// element itself. Uses Playwright's getByLabel which handles for=,
|
|
74
|
+
// nested inputs, and aria-labelledby uniformly.
|
|
75
|
+
try {
|
|
76
|
+
const locator = pageOrFrame.getByLabel(selector, { exact: true }).first();
|
|
77
|
+
await locator.waitFor({ state, timeout: strategyTimeout() });
|
|
78
|
+
const handle = await locator.elementHandle();
|
|
79
|
+
if (handle) {
|
|
80
|
+
this.logger.info('Element found via label association', { selector });
|
|
81
|
+
return handle;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
triedStrategies.push('label');
|
|
86
|
+
}
|
|
87
|
+
if (Date.now() >= deadline)
|
|
88
|
+
throw this.notFound(selector, triedStrategies);
|
|
89
|
+
// 4. Exact text match
|
|
90
|
+
try {
|
|
91
|
+
const el = await pageOrFrame.waitForSelector(`text="${selector}"`, { timeout: strategyTimeout(), state });
|
|
92
|
+
if (el) {
|
|
93
|
+
this.logger.info('Element found via exact text', { selector });
|
|
94
|
+
return el;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
triedStrategies.push('exact text');
|
|
99
|
+
}
|
|
100
|
+
if (Date.now() >= deadline)
|
|
101
|
+
throw this.notFound(selector, triedStrategies);
|
|
102
|
+
// 5. Case-insensitive text match
|
|
103
|
+
try {
|
|
104
|
+
const el = await pageOrFrame.waitForSelector(`text=${selector}`, { timeout: strategyTimeout(), state });
|
|
105
|
+
if (el) {
|
|
106
|
+
this.logger.info('Element found via text (case-insensitive)', { selector });
|
|
107
|
+
return el;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
triedStrategies.push('text (case-insensitive)');
|
|
112
|
+
}
|
|
113
|
+
if (Date.now() >= deadline)
|
|
114
|
+
throw this.notFound(selector, triedStrategies);
|
|
115
|
+
// 6. ARIA label
|
|
116
|
+
try {
|
|
117
|
+
const el = await pageOrFrame.waitForSelector(`[aria-label="${selector}"]`, { timeout: strategyTimeout(), state });
|
|
118
|
+
if (el) {
|
|
119
|
+
this.logger.info('Element found via ARIA label', { selector });
|
|
120
|
+
return el;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
triedStrategies.push('ARIA label');
|
|
125
|
+
}
|
|
126
|
+
if (Date.now() >= deadline)
|
|
127
|
+
throw this.notFound(selector, triedStrategies);
|
|
128
|
+
// 7. ARIA role (button)
|
|
129
|
+
try {
|
|
130
|
+
const el = await pageOrFrame.waitForSelector(`role=button[name="${selector}"]`, { timeout: strategyTimeout(), state });
|
|
131
|
+
if (el) {
|
|
132
|
+
this.logger.info('Element found via ARIA role', { selector });
|
|
133
|
+
return el;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
triedStrategies.push('ARIA role');
|
|
138
|
+
}
|
|
139
|
+
throw this.notFound(selector, triedStrategies);
|
|
140
|
+
}
|
|
141
|
+
async findInFrames(page, selector, options) {
|
|
142
|
+
try {
|
|
143
|
+
const element = await this.find(page, selector, options);
|
|
144
|
+
return { element, frame: page };
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
this.logger.debug('Not found on main page, searching iframes...', { selector });
|
|
148
|
+
}
|
|
149
|
+
const frames = page.frames();
|
|
150
|
+
for (const frame of frames) {
|
|
151
|
+
if (frame === page.mainFrame())
|
|
152
|
+
continue;
|
|
153
|
+
try {
|
|
154
|
+
const element = await this.find(frame, selector, { ...options, timeout: 3000 });
|
|
155
|
+
this.logger.info('Element found in iframe', { selector, frameUrl: frame.url() });
|
|
156
|
+
return { element, frame };
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// continue to next iframe
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw new Error(`Element not found in any frame: "${selector}". Searched main page and ${frames.length - 1} iframe(s).`);
|
|
163
|
+
}
|
|
164
|
+
looksLikeSelector(input) {
|
|
165
|
+
const trimmed = input.trim();
|
|
166
|
+
if (!trimmed)
|
|
167
|
+
return false;
|
|
168
|
+
if (CSS_INDICATOR_CHARS.some((ch) => trimmed.includes(ch)))
|
|
169
|
+
return true;
|
|
170
|
+
const firstWord = trimmed.split(/[\s.[#:>+~]/, 1)[0].toLowerCase();
|
|
171
|
+
return CSS_TAG_NAMES.has(firstWord);
|
|
172
|
+
}
|
|
173
|
+
looksLikeXPath(input) {
|
|
174
|
+
const trimmed = input.trim();
|
|
175
|
+
return trimmed.startsWith('/') || trimmed.startsWith('//');
|
|
176
|
+
}
|
|
177
|
+
async getBoundingBox(element) {
|
|
178
|
+
await element.scrollIntoViewIfNeeded().catch(() => { });
|
|
179
|
+
const box = await element.boundingBox();
|
|
180
|
+
return box ? { x: box.x, y: box.y, width: box.width, height: box.height } : null;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Find an element inside any open shadow root reachable from the document.
|
|
184
|
+
*
|
|
185
|
+
* Recursively walks the DOM and each encountered `shadowRoot`, testing
|
|
186
|
+
* CSS selector matches at every level. Modern component libraries
|
|
187
|
+
* (Lit, Stencil, Material Web Components, Ionic) wrap everything in
|
|
188
|
+
* shadow DOM, and Playwright's default CSS engine sometimes misses
|
|
189
|
+
* deeply-nested selectors. This walker is explicit and predictable.
|
|
190
|
+
*
|
|
191
|
+
* LIMITATION: closed shadow roots are opaque to JavaScript by design.
|
|
192
|
+
* Elements inside `attachShadow({mode: 'closed'})` are not reachable
|
|
193
|
+
* by any JS-only method; reaching them requires CDP `DOM.describeNode`
|
|
194
|
+
* with `pierce: true`, which is outside this method's scope. Open
|
|
195
|
+
* shadow roots (the overwhelming majority in practice) are fully
|
|
196
|
+
* supported.
|
|
197
|
+
*/
|
|
198
|
+
async findInShadowDom(pageOrFrame, cssSelector, options) {
|
|
199
|
+
const timeout = options?.timeout ?? 5000;
|
|
200
|
+
const deadline = Date.now() + timeout;
|
|
201
|
+
// Poll until the element appears or the deadline passes. The walker
|
|
202
|
+
// runs in browser context via evaluateHandle; we pass the function
|
|
203
|
+
// body as a string so TypeScript doesn't typecheck it against Node
|
|
204
|
+
// globals (the DOM lib isn't in tsconfig).
|
|
205
|
+
const walkerScript = `((selector) => {
|
|
206
|
+
function walk(root) {
|
|
207
|
+
if (!root) return null;
|
|
208
|
+
if (root.matches && root.matches(selector)) return root;
|
|
209
|
+
if (typeof root.querySelector === 'function') {
|
|
210
|
+
const direct = root.querySelector(selector);
|
|
211
|
+
if (direct) return direct;
|
|
212
|
+
}
|
|
213
|
+
const all = typeof root.querySelectorAll === 'function' ? root.querySelectorAll('*') : [];
|
|
214
|
+
for (const el of all) {
|
|
215
|
+
if (el.shadowRoot) {
|
|
216
|
+
const found = walk(el.shadowRoot);
|
|
217
|
+
if (found) return found;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return walk(document);
|
|
223
|
+
})(${JSON.stringify(cssSelector)})`;
|
|
224
|
+
while (Date.now() < deadline) {
|
|
225
|
+
const handle = await pageOrFrame.evaluateHandle(walkerScript);
|
|
226
|
+
const element = handle.asElement();
|
|
227
|
+
if (element) {
|
|
228
|
+
this.logger.info('Element found via shadow DOM walker', { cssSelector });
|
|
229
|
+
return element;
|
|
230
|
+
}
|
|
231
|
+
await handle.dispose();
|
|
232
|
+
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
233
|
+
}
|
|
234
|
+
throw new Error(`Element not found in shadow DOM: "${cssSelector}"`);
|
|
235
|
+
}
|
|
236
|
+
notFound(selector, tried) {
|
|
237
|
+
return new Error(`Element not found: "${selector}". Tried: ${tried.join(', ')}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=element-finder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-finder.js","sourceRoot":"","sources":["../src/element-finder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DeviceProfile } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Generate all evasion scripts for a given device profile.
|
|
4
|
+
* Each script is a self-contained IIFE ready to be injected via
|
|
5
|
+
* `Page.addScriptToEvaluateOnNewDocument()`.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Evasion script generator. With BlackTip v2 running on real Chrome
|
|
9
|
+
* (`channel: 'chrome'`) + patchright's CDP-level stealth patches, most of
|
|
10
|
+
* the original JS-shim evasion is unnecessary and some of it was actively
|
|
11
|
+
* creating signals (see L012 in planning/lessons.md for the chrome.runtime
|
|
12
|
+
* and launch-flag story).
|
|
13
|
+
*
|
|
14
|
+
* What we keep:
|
|
15
|
+
* - canvasNoise: privacy/anti-tracking, adds plausible noise to canvas
|
|
16
|
+
* rendering so we don't get a stable device-wide fingerprint.
|
|
17
|
+
* - audioContextOverride: same story for audio fingerprint.
|
|
18
|
+
*
|
|
19
|
+
* What we removed (and why):
|
|
20
|
+
* - chromeRuntime: real Chrome doesn't expose `chrome.runtime` on regular
|
|
21
|
+
* pages — only on extension-accessible contexts. Our shim was adding
|
|
22
|
+
* it everywhere, which CreepJS catches as `hasBadChromeRuntime: true`.
|
|
23
|
+
* Let patchright + real Chrome handle `window.chrome` naturally.
|
|
24
|
+
* - navigatorOverrides: real Chrome's navigator is already correct when
|
|
25
|
+
* we use `channel: 'chrome'`. Overriding with profile values creates
|
|
26
|
+
* mismatches (e.g., profile says hardwareConcurrency=8 but the actual
|
|
27
|
+
* CPU has 16, and other APIs like Performance.now timing can reveal
|
|
28
|
+
* the truth).
|
|
29
|
+
* - pluginsAndMimeTypes: real Chrome populates navigator.plugins with
|
|
30
|
+
* the real PDF viewer plugin. Our shim was adding fake plugins that
|
|
31
|
+
* didn't match.
|
|
32
|
+
* - permissionsOverride: real Chrome already returns 'prompt' for
|
|
33
|
+
* notifications on most states. Shim only needed on headless Chromium.
|
|
34
|
+
* - webglOverride: real Chrome reports the real GPU through ANGLE. Our
|
|
35
|
+
* shim was replacing AMD Radeon with a profile string, making the
|
|
36
|
+
* fingerprint inconsistent with the real DirectX pipeline signals.
|
|
37
|
+
*/
|
|
38
|
+
export declare function generateEvasionScripts(profile: DeviceProfile): string[];
|
|
39
|
+
//# sourceMappingURL=evasion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evasion.d.ts","sourceRoot":"","sources":["../src/evasion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAmd7C;;;;GAIG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,EAAE,CAKvE"}
|