@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/scraping",
3
- "version": "20.2.0",
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.0",
34
- "@ultimat3/jobs": "20.2.0",
35
- "@ultimat3/schema": "20.2.0",
36
- "@ultimat3/storage": "20.2.0"
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
@@ -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),
@@ -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[]>;