cdp-toolkit 1.2.0 → 1.6.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/CHANGELOG.md +223 -0
- package/CONTRACT.md +4 -2
- package/README.md +67 -20
- package/dist/cli.js +783 -63
- package/dist/index.js +694 -48
- package/dist/mcp.js +982 -66
- package/package.json +5 -3
- package/src/bidi/driver.ts +140 -1
- package/src/capabilities.ts +1 -1
- package/src/cdp/driver.ts +148 -1
- package/src/driver.ts +110 -1
- package/src/firefox-tools.ts +1 -1
- package/src/index.ts +22 -4
- package/src/leases-tools.ts +9 -3
- package/src/leases.ts +9 -1
- package/src/manifest.ts +198 -2
- package/src/mcp.ts +1 -1
- package/src/origins.ts +266 -0
- package/src/shared-tools.ts +317 -11
- package/src/tools/screencast.ts +741 -0
package/src/shared-tools.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* (ADR-001), used by BOTH Chrome (via cdp/driver.ts's createCdpDriver()) and Firefox (via
|
|
4
4
|
* bidi/driver.ts's createFirefoxDriver()). This file replaces the split that used to exist
|
|
5
5
|
* between src/tools/*.ts (raw CDP, Chrome only) and src/neutral.ts (Driver-based, Firefox only)
|
|
6
|
-
* for the
|
|
6
|
+
* for 20 of the 23 tools below. The other three, the list_cookies/set_cookie/delete_cookies
|
|
7
|
+
* cookie group, landed here directly and never had a Chrome-only copy.
|
|
7
8
|
*
|
|
8
9
|
* THE HARD REQUIREMENT this file exists to satisfy: Chrome's return shape, timing, and quirks
|
|
9
10
|
* stay byte-identical to what src/tools/*.ts produced before this migration: that already
|
|
@@ -33,12 +34,13 @@
|
|
|
33
34
|
* behavior outranks the abstraction, these 7 stay two implementations; see the migration report.
|
|
34
35
|
*/
|
|
35
36
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
36
|
-
import { join } from "node:path";
|
|
37
|
+
import { dirname, join } from "node:path";
|
|
37
38
|
import type {
|
|
38
|
-
BrowserDriver, DriverUid, ElementLocator, NavigateResult, PageDriver, PageInfo, SnapshotNode,
|
|
39
|
+
BrowserCookie, BrowserDriver, DriverUid, ElementLocator, NavigateResult, PageDriver, PageInfo, SnapshotNode,
|
|
39
40
|
} from "./driver.ts";
|
|
40
41
|
import type { TargetSelector } from "./types.ts";
|
|
41
|
-
import { assertLeaseOk, claimLease, leaseTtlMs, releaseLeaseFor, type LeaseBackend, type LeaseToken } from "./leases.ts";
|
|
42
|
+
import { assertLeaseOk, claimLease, defaultLabel, leaseTtlMs, releaseLeaseFor, type LeaseBackend, type LeaseToken } from "./leases.ts";
|
|
43
|
+
import { newTrackedPage, originIndex, type PageOrigin } from "./origins.ts";
|
|
42
44
|
|
|
43
45
|
const ARTIFACT_DIR = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
|
|
44
46
|
const STATE_DIR = process.env.CDP_STATE_DIR ?? "/tmp/cdp-toolkit";
|
|
@@ -133,21 +135,75 @@ function locatorOf(args: { uid?: DriverUid; selector?: string }): ElementLocator
|
|
|
133
135
|
|
|
134
136
|
/* -------------------------------- pages (4) -------------------------------- */
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
/**
|
|
139
|
+
* A page listing entry, PageInfo plus provenance. STRICTLY ADDITIVE: {id, url,
|
|
140
|
+
* title, type} are unchanged in name, type and value, because consumers read
|
|
141
|
+
* them today.
|
|
142
|
+
*/
|
|
143
|
+
export interface ListedPage extends PageInfo {
|
|
144
|
+
/** "agent" when the creation ledger has a record for this target. Otherwise
|
|
145
|
+
* "unknown", NEVER "human": the absence of a record is not proof that a
|
|
146
|
+
* person opened the tab. See src/origins.ts. */
|
|
147
|
+
origin: PageOrigin;
|
|
148
|
+
/** The creating agent's label. Present only when origin is "agent". */
|
|
149
|
+
label?: string;
|
|
150
|
+
/** When this toolkit created the tab (epoch ms). Only when origin is "agent". */
|
|
151
|
+
createdAt?: number;
|
|
152
|
+
/** Present only when this target HAS a ledger record that could not be read:
|
|
153
|
+
* the errno, or "unparseable". origin stays "unknown" because provenance is
|
|
154
|
+
* genuinely unknown, but this field is what keeps an unreadable record from
|
|
155
|
+
* being reported as a clean "nothing was ever recorded". */
|
|
156
|
+
originUnreadable?: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The reap set for the origin ledger: every target the browser currently has,
|
|
161
|
+
* INCLUDING non-page targets, regardless of what this call is listing.
|
|
162
|
+
*
|
|
163
|
+
* Using the filtered page list would be a bug with a plausible disguise: a
|
|
164
|
+
* default list_pages call drops workers and iframes, so reaping against it
|
|
165
|
+
* would delete the ledger record of any target that is alive but filtered out.
|
|
166
|
+
* Returning undefined (which listOrigins reads as "reap nothing") when the
|
|
167
|
+
* unfiltered listing fails is the same principle one step further: a failed
|
|
168
|
+
* enumeration must never be able to empty the ledger.
|
|
169
|
+
*/
|
|
170
|
+
async function reapSet(driver: BrowserDriver, pages: PageInfo[], all?: boolean): Promise<readonly string[] | undefined> {
|
|
171
|
+
if (all === true) return pages.map((p) => p.id);
|
|
172
|
+
try {
|
|
173
|
+
return (await driver.listPages({ all: true })).map((p) => p.id);
|
|
174
|
+
} catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function listPages(driver: BrowserDriver, args: { all?: boolean } = {}): Promise<{ pages: ListedPage[]; count: number }> {
|
|
137
180
|
const pages = await driver.listPages({ all: args.all });
|
|
138
|
-
|
|
181
|
+
// Never let provenance break the listing: originIndex does not throw, and a
|
|
182
|
+
// missing or unreadable ledger yields an empty map, so every page falls back
|
|
183
|
+
// to origin "unknown" exactly as a pre-ledger consumer always saw.
|
|
184
|
+
const ledger = await originIndex(backendOf(driver), await reapSet(driver, pages, args.all));
|
|
185
|
+
const annotated: ListedPage[] = pages.map((p) => {
|
|
186
|
+
const rec = ledger.get(p.id);
|
|
187
|
+
if (!rec) return { ...p, origin: "unknown" };
|
|
188
|
+
if (rec.unreadable !== undefined) return { ...p, origin: "unknown", originUnreadable: rec.unreadable };
|
|
189
|
+
return { ...p, origin: "agent", label: rec.label, createdAt: rec.createdAt };
|
|
190
|
+
});
|
|
191
|
+
return { pages: annotated, count: annotated.length };
|
|
139
192
|
}
|
|
140
193
|
|
|
141
194
|
export async function newPage(
|
|
142
195
|
driver: BrowserDriver,
|
|
143
196
|
args: { url?: string; claim?: boolean; label?: string; ttlMs?: number } = {},
|
|
144
197
|
): Promise<{ targetId: string; url: string; lease?: LeaseToken; label?: string; expiresAt?: number }> {
|
|
145
|
-
const
|
|
198
|
+
const label = typeof args.label === "string" && args.label.length ? args.label : defaultLabel();
|
|
199
|
+
// Creates the tab AND writes its creation record. The record outlives any
|
|
200
|
+
// lease taken below, which is the point: a released tab is still an agent's.
|
|
201
|
+
const p = await newTrackedPage(driver, backendOf(driver), { url: args.url, label });
|
|
146
202
|
if (args.claim !== true) return { targetId: p.id, url: p.url };
|
|
147
203
|
// Atomic in the sense that matters: the tab is claimed before this call
|
|
148
204
|
// returns, so no other agent can see it unclaimed and take it first.
|
|
149
205
|
const { record, token } = await claimLease(backendOf(driver), p.id, {
|
|
150
|
-
label
|
|
206
|
+
label,
|
|
151
207
|
ttlMs: typeof args.ttlMs === "number" && args.ttlMs > 0 ? args.ttlMs : leaseTtlMs(),
|
|
152
208
|
});
|
|
153
209
|
return { targetId: p.id, url: p.url, lease: token, label: record.label, expiresAt: record.lastUsedAt + record.ttlMs };
|
|
@@ -232,14 +288,261 @@ export const waitFor = waitForText;
|
|
|
232
288
|
|
|
233
289
|
/* ------------------------------- evaluate (1) ------------------------------- */
|
|
234
290
|
|
|
291
|
+
/**
|
|
292
|
+
* The `savePath` result shape. Deliberately carries NO form of the evaluated
|
|
293
|
+
* value: not the value, not a preview, not a truncation, not a byte prefix.
|
|
294
|
+
* The whole reason `savePath` exists is that the value may be a credential
|
|
295
|
+
* (a JWT or session token read out of localStorage), and anything this object
|
|
296
|
+
* carried would land in the caller's transcript, which is the exact leak the
|
|
297
|
+
* file sink is here to prevent. `type` is the JS typeof of the value ("null"
|
|
298
|
+
* for null), which describes the value without disclosing any of it.
|
|
299
|
+
*/
|
|
300
|
+
export interface EvaluateScriptSaveResult {
|
|
301
|
+
path: string;
|
|
302
|
+
bytes: number;
|
|
303
|
+
type: string;
|
|
304
|
+
target: { id: string; url: string; title: string };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Serialize an evaluated value for the file sink. `undefined` (an expression
|
|
309
|
+
* with no result) is written as JSON null rather than left unwritten, so the
|
|
310
|
+
* file always exists and always parses.
|
|
311
|
+
*/
|
|
312
|
+
function serializeForSink(value: unknown): string {
|
|
313
|
+
const json = JSON.stringify(value === undefined ? null : value, null, 2);
|
|
314
|
+
return json ?? "null";
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* THE file sink, shared by every tool that takes a `savePath`: serialize the
|
|
319
|
+
* value as JSON, write it, and report only where it went and how big it was.
|
|
320
|
+
* It returns no form of the value on purpose, so a caller of this helper
|
|
321
|
+
* cannot accidentally leak one back into a response.
|
|
322
|
+
*
|
|
323
|
+
* Path resolution matches take_heapsnapshot: an absolute path is used as-is, a
|
|
324
|
+
* relative one resolves under the artifact dir, and missing parent directories
|
|
325
|
+
* are created.
|
|
326
|
+
*/
|
|
327
|
+
async function writeJsonSink(savePath: string, value: unknown): Promise<{ path: string; bytes: number }> {
|
|
328
|
+
const path = savePath.startsWith("/") ? savePath : join(ARTIFACT_DIR, savePath);
|
|
329
|
+
await mkdir(dirname(path), { recursive: true });
|
|
330
|
+
const json = serializeForSink(value);
|
|
331
|
+
await writeFile(path, json, "utf8");
|
|
332
|
+
return { path, bytes: Buffer.byteLength(json, "utf8") };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** True when a savePath argument actually asks for the sink. An empty string is
|
|
336
|
+
* treated as absent, so the caller gets the value inline. */
|
|
337
|
+
function sinkRequested(savePath?: string): savePath is string {
|
|
338
|
+
return savePath !== undefined && savePath !== "";
|
|
339
|
+
}
|
|
340
|
+
|
|
235
341
|
export async function evaluateScript(
|
|
236
342
|
driver: BrowserDriver,
|
|
237
|
-
args: { target?: TargetSelector; expression: string; awaitPromise?: boolean; args?: unknown[] },
|
|
343
|
+
args: { target?: TargetSelector; expression: string; awaitPromise?: boolean; args?: unknown[]; savePath?: string },
|
|
238
344
|
): Promise<unknown> {
|
|
239
345
|
if (typeof args.expression !== "string" || args.expression.length === 0) {
|
|
240
346
|
throw new SharedToolError("evaluateScript: 'expression' must be a non-empty string");
|
|
241
347
|
}
|
|
242
|
-
return withPage(driver, args.target, (page) =>
|
|
348
|
+
return withPage(driver, args.target, async (page) => {
|
|
349
|
+
const value = await page.evaluate(args.expression, { args: args.args, awaitPromise: args.awaitPromise ?? true });
|
|
350
|
+
// No savePath: byte-identical to the pre-sink behavior, the value itself.
|
|
351
|
+
// A thrown page-side exception never reaches here, so it still surfaces as
|
|
352
|
+
// an error rather than being written into the file.
|
|
353
|
+
if (!sinkRequested(args.savePath)) return value;
|
|
354
|
+
|
|
355
|
+
const { path, bytes } = await writeJsonSink(args.savePath, value);
|
|
356
|
+
const result: EvaluateScriptSaveResult = {
|
|
357
|
+
path,
|
|
358
|
+
bytes,
|
|
359
|
+
type: value === null ? "null" : typeof value,
|
|
360
|
+
target: target3(page.info),
|
|
361
|
+
};
|
|
362
|
+
return result;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* -------------------------------- cookies (1) -------------------------------- */
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* The `savePath` result shape for list_cookies. Same rule as
|
|
370
|
+
* EvaluateScriptSaveResult: NO cookie value in any form, not even a prefix. A
|
|
371
|
+
* session cookie IS the credential, so a preview of one is a leaked one.
|
|
372
|
+
* `count` is how many cookies were written, which tells a caller the read
|
|
373
|
+
* worked without disclosing anything about what it read.
|
|
374
|
+
*/
|
|
375
|
+
export interface ListCookiesSaveResult {
|
|
376
|
+
path: string;
|
|
377
|
+
bytes: number;
|
|
378
|
+
count: number;
|
|
379
|
+
target: { id: string; url: string; title: string };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export interface ListCookiesResult {
|
|
383
|
+
cookies: BrowserCookie[];
|
|
384
|
+
count: number;
|
|
385
|
+
target: { id: string; url: string; title: string };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Cookie domain matching for the `domain` filter, deliberately small.
|
|
390
|
+
*
|
|
391
|
+
* A cookie's own domain carries a leading dot when it was set for subdomains
|
|
392
|
+
* (".example.test") and none when it was host-only ("example.test"), and a
|
|
393
|
+
* caller who types "example.test" means both. So the comparison strips a
|
|
394
|
+
* leading dot from each side and then accepts an exact match or a subdomain of
|
|
395
|
+
* the filter ("app.example.test" matches "example.test"). That is the whole
|
|
396
|
+
* rule: no wildcards, no patterns, no query language.
|
|
397
|
+
*/
|
|
398
|
+
function cookieDomainMatches(cookieDomain: string, filter: string): boolean {
|
|
399
|
+
const bare = (d: string) => (d.startsWith(".") ? d.slice(1) : d).toLowerCase();
|
|
400
|
+
const c = bare(cookieDomain);
|
|
401
|
+
const f = bare(filter);
|
|
402
|
+
return c === f || c.endsWith(`.${f}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Apply the optional name/domain filters. Exported for the unit tests, which
|
|
406
|
+
* can then check the matching rule without standing up a browser. */
|
|
407
|
+
export function filterCookies(cookies: readonly BrowserCookie[], filter: { domain?: string; name?: string }): BrowserCookie[] {
|
|
408
|
+
return cookies.filter((c) => {
|
|
409
|
+
if (filter.name !== undefined && filter.name !== "" && c.name !== filter.name) return false;
|
|
410
|
+
if (filter.domain !== undefined && filter.domain !== "" && !cookieDomainMatches(c.domain, filter.domain)) return false;
|
|
411
|
+
return true;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Read the cookies of the target page, httpOnly ones included.
|
|
417
|
+
*
|
|
418
|
+
* This is the one read that a page script cannot do: document.cookie omits
|
|
419
|
+
* every httpOnly cookie by design, which is precisely the set a session
|
|
420
|
+
* credential lives in. Both drivers answer over the protocol's own cookie
|
|
421
|
+
* store, so the flags come from the browser rather than being inferred.
|
|
422
|
+
*
|
|
423
|
+
* With `savePath` set the cookie array is written to that file as JSON and the
|
|
424
|
+
* response carries only {path, bytes, count, target}: no value, no preview, no
|
|
425
|
+
* truncation. Without it, the cookies are returned inline.
|
|
426
|
+
*/
|
|
427
|
+
export async function listCookies(
|
|
428
|
+
driver: BrowserDriver,
|
|
429
|
+
args: { target?: TargetSelector; domain?: string; name?: string; savePath?: string } = {},
|
|
430
|
+
): Promise<ListCookiesResult | ListCookiesSaveResult> {
|
|
431
|
+
return withPage(driver, args.target, async (page) => {
|
|
432
|
+
const all = await page.getCookies();
|
|
433
|
+
const cookies = filterCookies(all, { domain: args.domain, name: args.name });
|
|
434
|
+
if (!sinkRequested(args.savePath)) {
|
|
435
|
+
return { cookies, count: cookies.length, target: target3(page.info) };
|
|
436
|
+
}
|
|
437
|
+
const { path, bytes } = await writeJsonSink(args.savePath, cookies);
|
|
438
|
+
return { path, bytes, count: cookies.length, target: target3(page.info) };
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export interface SetCookieResult {
|
|
443
|
+
set: true;
|
|
444
|
+
target: { id: string; url: string; title: string };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export interface DeleteCookiesResult {
|
|
448
|
+
deleted: true;
|
|
449
|
+
target: { id: string; url: string; title: string };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* THE site constraint both write tools share: a cookie has to be attributed to
|
|
454
|
+
* a site, and `url` or `domain` is how a caller says which.
|
|
455
|
+
*
|
|
456
|
+
* Checked here, above the drivers, so the refusal is ONE message on both
|
|
457
|
+
* backends rather than a CDP protocol error on one and a Firefox no-op on the
|
|
458
|
+
* other. It throws rather than defaulting to the current page's origin: a
|
|
459
|
+
* guessed domain writes a real cookie somewhere the caller did not name, and a
|
|
460
|
+
* cookie written to the wrong site is worse than a call that failed loudly.
|
|
461
|
+
*
|
|
462
|
+
* Exported for the unit tests, which pin the message without a browser.
|
|
463
|
+
*/
|
|
464
|
+
export function assertCookieSite(
|
|
465
|
+
args: { url?: string; domain?: string },
|
|
466
|
+
toolName: string,
|
|
467
|
+
): void {
|
|
468
|
+
const hasUrl = args.url !== undefined && args.url !== "";
|
|
469
|
+
const hasDomain = args.domain !== undefined && args.domain !== "";
|
|
470
|
+
if (!hasUrl && !hasDomain) {
|
|
471
|
+
throw new Error(`${toolName} requires either 'url' or 'domain': a cookie has to be attributed to a site, and neither was given.`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** The shared 'name' check. A write tool with no name has nothing to write and
|
|
476
|
+
* a delete tool with no name would match every cookie, so an empty one is
|
|
477
|
+
* refused rather than treated as absent. */
|
|
478
|
+
function assertCookieName(name: string | undefined, toolName: string): asserts name is string {
|
|
479
|
+
if (name === undefined || name === "") {
|
|
480
|
+
throw new Error(`${toolName} requires a non-empty 'name'.`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Write one cookie into the target page's cookie store.
|
|
486
|
+
*
|
|
487
|
+
* The response is deliberately tiny: `{set: true, target}`. It does NOT echo
|
|
488
|
+
* the value back, because the value is frequently the credential and a caller
|
|
489
|
+
* who just supplied it does not need it repeated into their transcript. The
|
|
490
|
+
* `set: true` is earned rather than assumed: the CDP driver throws on Chrome's
|
|
491
|
+
* `{success:false}` refusal and the BiDi driver throws when it cannot derive
|
|
492
|
+
* the domain BiDi requires, so reaching this line means a backend confirmed
|
|
493
|
+
* the write.
|
|
494
|
+
*/
|
|
495
|
+
export async function setCookie(
|
|
496
|
+
driver: BrowserDriver,
|
|
497
|
+
args: {
|
|
498
|
+
target?: TargetSelector; name: string; value: string; url?: string; domain?: string;
|
|
499
|
+
path?: string; expires?: number; httpOnly?: boolean; secure?: boolean;
|
|
500
|
+
sameSite?: "strict" | "lax" | "none" | "default";
|
|
501
|
+
},
|
|
502
|
+
): Promise<SetCookieResult> {
|
|
503
|
+
assertCookieName(args.name, "set_cookie");
|
|
504
|
+
if (typeof args.value !== "string") throw new Error("set_cookie requires a string 'value'.");
|
|
505
|
+
assertCookieSite(args, "set_cookie");
|
|
506
|
+
return withPage(driver, args.target, async (page) => {
|
|
507
|
+
await page.setCookie({
|
|
508
|
+
name: args.name,
|
|
509
|
+
value: args.value,
|
|
510
|
+
...(args.url !== undefined ? { url: args.url } : {}),
|
|
511
|
+
...(args.domain !== undefined ? { domain: args.domain } : {}),
|
|
512
|
+
...(args.path !== undefined ? { path: args.path } : {}),
|
|
513
|
+
...(args.expires !== undefined ? { expires: args.expires } : {}),
|
|
514
|
+
...(args.httpOnly !== undefined ? { httpOnly: args.httpOnly } : {}),
|
|
515
|
+
...(args.secure !== undefined ? { secure: args.secure } : {}),
|
|
516
|
+
...(args.sameSite !== undefined ? { sameSite: args.sameSite } : {}),
|
|
517
|
+
});
|
|
518
|
+
return { set: true as const, target: target3(page.info) };
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Remove the named cookie from the target page's cookie store.
|
|
524
|
+
*
|
|
525
|
+
* `{deleted: true, target}` and NO count, on purpose. Neither protocol reports
|
|
526
|
+
* how many cookies it removed, so any number here would be invented; a caller
|
|
527
|
+
* who needs one reads list_cookies before and after. `deleted: true` means the
|
|
528
|
+
* backend accepted and performed the deletion, not that a matching cookie
|
|
529
|
+
* existed: deleting an absent cookie is a success on both backends.
|
|
530
|
+
*/
|
|
531
|
+
export async function deleteCookies(
|
|
532
|
+
driver: BrowserDriver,
|
|
533
|
+
args: { target?: TargetSelector; name: string; url?: string; domain?: string; path?: string },
|
|
534
|
+
): Promise<DeleteCookiesResult> {
|
|
535
|
+
assertCookieName(args.name, "delete_cookies");
|
|
536
|
+
assertCookieSite(args, "delete_cookies");
|
|
537
|
+
return withPage(driver, args.target, async (page) => {
|
|
538
|
+
await page.deleteCookies({
|
|
539
|
+
name: args.name,
|
|
540
|
+
...(args.url !== undefined ? { url: args.url } : {}),
|
|
541
|
+
...(args.domain !== undefined ? { domain: args.domain } : {}),
|
|
542
|
+
...(args.path !== undefined ? { path: args.path } : {}),
|
|
543
|
+
});
|
|
544
|
+
return { deleted: true as const, target: target3(page.info) };
|
|
545
|
+
});
|
|
243
546
|
}
|
|
244
547
|
|
|
245
548
|
/* -------------------------------- snapshot (1) -------------------------------- */
|
|
@@ -440,7 +743,7 @@ export async function handleDialog(
|
|
|
440
743
|
|
|
441
744
|
/* ------------------------------------- registry ------------------------------------- */
|
|
442
745
|
|
|
443
|
-
/** The
|
|
746
|
+
/** The 23 tools this file unifies, importable by both src/index.ts (Chrome) and
|
|
444
747
|
* src/firefox-tools.ts (Firefox). Each function is (driver, args) => Promise<result>. */
|
|
445
748
|
export const SHARED_TOOLS = {
|
|
446
749
|
list_pages: listPages,
|
|
@@ -450,6 +753,9 @@ export const SHARED_TOOLS = {
|
|
|
450
753
|
navigate_page: navigatePage,
|
|
451
754
|
wait_for: waitForText,
|
|
452
755
|
evaluate_script: evaluateScript,
|
|
756
|
+
list_cookies: listCookies,
|
|
757
|
+
set_cookie: setCookie,
|
|
758
|
+
delete_cookies: deleteCookies,
|
|
453
759
|
take_snapshot: takeSnapshot,
|
|
454
760
|
click,
|
|
455
761
|
hover,
|