@ultimat3/scraping 20.2.0 → 20.2.1
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/package.json +5 -5
- package/src/cdp-fake.ts +12 -0
- package/src/cdp-port.ts +8 -0
- package/src/cdp-target.ts +12 -0
- package/src/html-target.ts +12 -0
- package/src/page-over-target.ts +4 -0
- package/src/page.ts +12 -0
- package/src/target.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/scraping",
|
|
3
|
-
"version": "20.2.
|
|
3
|
+
"version": "20.2.1",
|
|
4
4
|
"description": "Browser automation as a job: scrape() returns a JobHandle",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"test": "bun test"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@ultimat3/core": "20.2.
|
|
34
|
-
"@ultimat3/jobs": "20.2.
|
|
35
|
-
"@ultimat3/schema": "20.2.
|
|
36
|
-
"@ultimat3/storage": "20.2.
|
|
33
|
+
"@ultimat3/core": "20.2.1",
|
|
34
|
+
"@ultimat3/jobs": "20.2.1",
|
|
35
|
+
"@ultimat3/schema": "20.2.1",
|
|
36
|
+
"@ultimat3/storage": "20.2.1"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/cdp-fake.ts
CHANGED
|
@@ -98,6 +98,12 @@ export interface FakeCdpBrowser extends CdpBrowserLike {
|
|
|
98
98
|
readonly pressed: readonly string[];
|
|
99
99
|
/** Every selector `page.focus()` was handed, in order. */
|
|
100
100
|
readonly focused: readonly string[];
|
|
101
|
+
/**
|
|
102
|
+
* Every expression `page.evaluateOnNewDocument()` was handed, in order — the scripts a real
|
|
103
|
+
* browser would run ahead of each document's own. Recorded, never executed: this fake has no JS
|
|
104
|
+
* engine, and a test asserts on WHAT was prepared and that it was prepared before `goto`.
|
|
105
|
+
*/
|
|
106
|
+
readonly prepared: readonly string[];
|
|
101
107
|
/** How many raw CDP sessions were created and how many detached — a leak is a difference. */
|
|
102
108
|
readonly sessions: { readonly created: number; readonly detached: number };
|
|
103
109
|
}
|
|
@@ -149,6 +155,7 @@ export function fakeCdpBrowser(init: FakeCdpPageInit): FakeCdpBrowser {
|
|
|
149
155
|
let cookies: readonly ScrapeCookie[] = init.cookies ?? [];
|
|
150
156
|
const pressed: string[] = [];
|
|
151
157
|
const focused: string[] = [];
|
|
158
|
+
const prepared: string[] = [];
|
|
152
159
|
const sessions = { created: 0, detached: 0 };
|
|
153
160
|
const axBySelector = init.accessibility ?? {};
|
|
154
161
|
|
|
@@ -316,6 +323,10 @@ export function fakeCdpBrowser(init: FakeCdpPageInit): FakeCdpBrowser {
|
|
|
316
323
|
focused.push(selector);
|
|
317
324
|
return Promise.resolve();
|
|
318
325
|
},
|
|
326
|
+
evaluateOnNewDocument: (expression: string) => {
|
|
327
|
+
prepared.push(expression);
|
|
328
|
+
return Promise.resolve(undefined);
|
|
329
|
+
},
|
|
319
330
|
createCDPSession: () => {
|
|
320
331
|
sessions.created += 1;
|
|
321
332
|
return Promise.resolve(cdpSession());
|
|
@@ -391,6 +402,7 @@ export function fakeCdpBrowser(init: FakeCdpPageInit): FakeCdpBrowser {
|
|
|
391
402
|
},
|
|
392
403
|
pressed,
|
|
393
404
|
focused,
|
|
405
|
+
prepared,
|
|
394
406
|
sessions,
|
|
395
407
|
};
|
|
396
408
|
}
|
package/src/cdp-port.ts
CHANGED
|
@@ -129,6 +129,14 @@ export interface CdpPageLike {
|
|
|
129
129
|
readonly keyboard?: CdpKeyboardLike | undefined;
|
|
130
130
|
/** `page.focus(selector)`. OPTIONAL for `keyboard`'s reason, and refused by name when absent. */
|
|
131
131
|
focus?(selector: string): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* `page.evaluateOnNewDocument(expression)`: a script the browser runs in EVERY document this
|
|
134
|
+
* page navigates to, before any of the document's own scripts — `Page.addScriptToEvaluateOnNewDocument`
|
|
135
|
+
* under CDP. It is the only moment that beats an inlined boot script, which is what a
|
|
136
|
+
* storage-seeded theme choice needs (`ScrapePage.prepare`). The string form, for `evaluate`'s
|
|
137
|
+
* reason. OPTIONAL for `keyboard`'s reason, and refused by name when absent.
|
|
138
|
+
*/
|
|
139
|
+
evaluateOnNewDocument?(expression: string): Promise<unknown>;
|
|
132
140
|
/**
|
|
133
141
|
* A raw protocol session, for the one read this package makes that the library has no method
|
|
134
142
|
* for: the accessibility tree (`Accessibility.getPartialAXTree`). OPTIONAL for `keyboard`'s
|
package/src/cdp-target.ts
CHANGED
|
@@ -300,6 +300,18 @@ export async function cdpTarget(init: CdpTargetInit): Promise<ScrapeTarget> {
|
|
|
300
300
|
// puppeteer's own `Page` needs it.
|
|
301
301
|
await emulate.call(init.page, colorSchemeFeatures(scheme));
|
|
302
302
|
}),
|
|
303
|
+
prepare: (expression: string): Promise<void> =>
|
|
304
|
+
guard('prepare', async () => {
|
|
305
|
+
// Read off the object and called through it, for `setColorScheme`'s reason.
|
|
306
|
+
const evaluateOnNewDocument = init.page.evaluateOnNewDocument;
|
|
307
|
+
if (typeof evaluateOnNewDocument !== 'function') {
|
|
308
|
+
throw scrapeNotImplemented(
|
|
309
|
+
'prepare() on a CDP page with no evaluateOnNewDocument() method',
|
|
310
|
+
'upgrade the launcher to a puppeteer-core that exposes page.evaluateOnNewDocument(), or seed the state from the app under test instead of the browser',
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
await evaluateOnNewDocument.call(init.page, expression);
|
|
314
|
+
}),
|
|
303
315
|
screenshot: (options: CaptureOptions) =>
|
|
304
316
|
guard('screenshot', async () => {
|
|
305
317
|
// `fullPage` is OMITTED when a clip is given rather than sent as `false`: the two are
|
package/src/html-target.ts
CHANGED
|
@@ -412,6 +412,18 @@ export function htmlTarget(init: HtmlTargetInit): ScrapeTarget {
|
|
|
412
412
|
colorScheme = scheme === 'no-preference' ? null : scheme;
|
|
413
413
|
return Promise.resolve();
|
|
414
414
|
},
|
|
415
|
+
/**
|
|
416
|
+
* ACCEPTED, for `setColorScheme`'s reason and not `setOfflineMode`'s. This driver parses
|
|
417
|
+
* markup and executes none of it, so there is no document script for the expression to run
|
|
418
|
+
* ahead of — and no assertion a resolved promise could let through: what a prepared script
|
|
419
|
+
* seeds is the INPUT to a boot this driver never runs. Refusing would cost `x shot` and every
|
|
420
|
+
* `ui.*` tool their unit tests on a machine with no Chrome, for an outcome that does not exist
|
|
421
|
+
* here. `live()` still refuses a closed target, which is the one thing this can be wrong about
|
|
422
|
+
* — and `async`, so that refusal REJECTS rather than escaping a caller's `.catch()`.
|
|
423
|
+
*/
|
|
424
|
+
async prepare(_expression: string): Promise<void> {
|
|
425
|
+
live();
|
|
426
|
+
},
|
|
415
427
|
screenshot: (options: CaptureOptions): Promise<Uint8Array> =>
|
|
416
428
|
Promise.resolve(framedPng(options.clip, colorScheme)),
|
|
417
429
|
pdf: (_options: CaptureOptions): Promise<Uint8Array> => Promise.resolve(FAKE_PDF),
|
package/src/page-over-target.ts
CHANGED
|
@@ -274,6 +274,10 @@ export function pageOverTarget(target: ScrapeTarget, ctx: PageContext): ScrapePa
|
|
|
274
274
|
async colorScheme(scheme: ColorScheme): Promise<void> {
|
|
275
275
|
await target.setColorScheme(scheme);
|
|
276
276
|
},
|
|
277
|
+
// `async`, for `offline()`'s reason.
|
|
278
|
+
async prepare(expression: string): Promise<void> {
|
|
279
|
+
await target.prepare(expression);
|
|
280
|
+
},
|
|
277
281
|
cookies: (): Promise<readonly ScrapeCookie[]> => target.cookies(),
|
|
278
282
|
session: () => target.session(),
|
|
279
283
|
// Redacted BY VALUE on the way out, the same pass `html()` makes. A console line and a request
|
package/src/page.ts
CHANGED
|
@@ -154,6 +154,18 @@ export interface ScrapePage extends ScrapeFrame {
|
|
|
154
154
|
* method, which is a fact about the build rather than about the driver.
|
|
155
155
|
*/
|
|
156
156
|
colorScheme(scheme: ColorScheme): Promise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* A script the browser runs in EVERY document this page navigates to, BEFORE the document's
|
|
159
|
+
* own scripts — the one moment that beats an inlined boot script. Call it before `goto()`: what
|
|
160
|
+
* it seeds (a `localStorage` key a boot reads, a flag a component checks) is then in place for
|
|
161
|
+
* the first script the page executes, where an `evaluate()` after navigation is one boot too
|
|
162
|
+
* late. Same string discipline as `evaluate()`: an expression, never a closure.
|
|
163
|
+
*
|
|
164
|
+
* ACCEPTED on every driver, for `colorScheme()`'s reason: the offline drivers run no scripts,
|
|
165
|
+
* so nothing there could be wrong about it. `X_NOT_IMPLEMENTED` is reserved for a LAUNCHER
|
|
166
|
+
* that lacks the method, which is a fact about the build rather than about the driver.
|
|
167
|
+
*/
|
|
168
|
+
prepare(expression: string): Promise<void>;
|
|
157
169
|
/**
|
|
158
170
|
* The handoff, made explicit: what the HTTP leg will send, as a value an author can inspect and
|
|
159
171
|
* a fixture can assert on. `http` uses it automatically — this is for seeing what carried over.
|
package/src/target.ts
CHANGED
|
@@ -201,6 +201,20 @@ export interface ScrapeTarget {
|
|
|
201
201
|
* so a driver that dropped the preference fails a test rather than passing every one.
|
|
202
202
|
*/
|
|
203
203
|
setColorScheme(scheme: ColorScheme): Promise<void>;
|
|
204
|
+
/**
|
|
205
|
+
* A script every document this target navigates to runs BEFORE its own — the browser's
|
|
206
|
+
* `evaluateOnNewDocument`. REQUIRED here where `CdpPageLike.evaluateOnNewDocument` is optional,
|
|
207
|
+
* for `setOfflineMode`'s reason: the asymmetry is the enforcement, and a launcher without the
|
|
208
|
+
* method is `X_NOT_IMPLEMENTED`.
|
|
209
|
+
*
|
|
210
|
+
* A DRIVER WITH NO JS ENGINE ACCEPTS IT, for `setColorScheme`'s reason and not
|
|
211
|
+
* `setOfflineMode`'s: the offline drivers execute nothing, so there is no document script for
|
|
212
|
+
* the expression to run ahead of and no assertion a resolved promise could let through — a
|
|
213
|
+
* seeded `localStorage` key is the INPUT to a boot script this driver never runs. Refusing
|
|
214
|
+
* would make `x shot` and every `ui.*` tool untestable on a machine with no Chrome, and the
|
|
215
|
+
* outcome it would protect does not exist here. The call is still checked for a closed target.
|
|
216
|
+
*/
|
|
217
|
+
prepare(expression: string): Promise<void>;
|
|
204
218
|
screenshot(options: CaptureOptions): Promise<Uint8Array>;
|
|
205
219
|
pdf(options: CaptureOptions): Promise<Uint8Array>;
|
|
206
220
|
cookies(): Promise<readonly ScrapeCookie[]>;
|