@toolstackhq/cdpwright 1.1.0 → 1.2.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/README.md +25 -7
- package/dist/assert/expect.d.ts +1 -1
- package/dist/assert/expect.js +1 -1
- package/dist/{chunk-M5G6EMAS.js → chunk-PDUS6BDZ.js} +54 -20
- package/dist/chunk-PDUS6BDZ.js.map +1 -0
- package/dist/cli.js +373 -57
- package/dist/cli.js.map +1 -1
- package/dist/{expect-BY8vnFfi.d.ts → expect-CLal_AQd.d.ts} +2 -0
- package/dist/index.d.ts +25 -4
- package/dist/index.js +53 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-M5G6EMAS.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1323,6 +1323,11 @@ function ensureAllowedUrl(url, options = {}) {
|
|
|
1323
1323
|
}
|
|
1324
1324
|
|
|
1325
1325
|
// src/core/Page.ts
|
|
1326
|
+
function assertPdfBuffer(buffer) {
|
|
1327
|
+
if (buffer.length < 5 || buffer.subarray(0, 5).toString("utf-8") !== "%PDF-") {
|
|
1328
|
+
throw new Error("PDF generation failed: Chromium did not return a valid PDF");
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1326
1331
|
var Page = class {
|
|
1327
1332
|
session;
|
|
1328
1333
|
logger;
|
|
@@ -1442,9 +1447,19 @@ var Page = class {
|
|
|
1442
1447
|
async findLocators(options = {}) {
|
|
1443
1448
|
return this.mainFrame().findLocators(options);
|
|
1444
1449
|
}
|
|
1450
|
+
async content() {
|
|
1451
|
+
await this.waitForLoad();
|
|
1452
|
+
return this.mainFrame().evaluate(() => {
|
|
1453
|
+
const doctype = document.doctype;
|
|
1454
|
+
const doctypeText = doctype ? `<!DOCTYPE ${doctype.name}${doctype.publicId ? ` PUBLIC "${doctype.publicId}"` : ""}${doctype.systemId ? ` "${doctype.systemId}"` : ""}>` : "<!doctype html>";
|
|
1455
|
+
return `${doctypeText}
|
|
1456
|
+
${document.documentElement.outerHTML}`;
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1445
1459
|
async screenshot(options = {}) {
|
|
1446
1460
|
const start = Date.now();
|
|
1447
1461
|
this.events.emit("action:start", { name: "screenshot", frameId: this.mainFrameId });
|
|
1462
|
+
await this.waitForLoad();
|
|
1448
1463
|
const result = await this.session.send("Page.captureScreenshot", {
|
|
1449
1464
|
format: options.format ?? "png",
|
|
1450
1465
|
quality: options.quality,
|
|
@@ -1463,6 +1478,7 @@ var Page = class {
|
|
|
1463
1478
|
async screenshotBase64(options = {}) {
|
|
1464
1479
|
const start = Date.now();
|
|
1465
1480
|
this.events.emit("action:start", { name: "screenshotBase64", frameId: this.mainFrameId });
|
|
1481
|
+
await this.waitForLoad();
|
|
1466
1482
|
const result = await this.session.send("Page.captureScreenshot", {
|
|
1467
1483
|
format: options.format ?? "png",
|
|
1468
1484
|
quality: options.quality,
|
|
@@ -1476,26 +1492,37 @@ var Page = class {
|
|
|
1476
1492
|
async pdf(options = {}) {
|
|
1477
1493
|
const start = Date.now();
|
|
1478
1494
|
this.events.emit("action:start", { name: "pdf", frameId: this.mainFrameId });
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1495
|
+
await this.waitForLoad();
|
|
1496
|
+
await this.session.send("Emulation.setEmulatedMedia", { media: "screen" });
|
|
1497
|
+
let buffer;
|
|
1498
|
+
try {
|
|
1499
|
+
const result = await this.session.send("Page.printToPDF", {
|
|
1500
|
+
landscape: options.landscape ?? false,
|
|
1501
|
+
printBackground: options.printBackground ?? true,
|
|
1502
|
+
scale: options.scale,
|
|
1503
|
+
paperWidth: options.paperWidth,
|
|
1504
|
+
paperHeight: options.paperHeight,
|
|
1505
|
+
marginTop: options.marginTop,
|
|
1506
|
+
marginBottom: options.marginBottom,
|
|
1507
|
+
marginLeft: options.marginLeft,
|
|
1508
|
+
marginRight: options.marginRight,
|
|
1509
|
+
pageRanges: options.pageRanges,
|
|
1510
|
+
preferCSSPageSize: options.preferCSSPageSize
|
|
1511
|
+
});
|
|
1512
|
+
buffer = Buffer.from(result.data, "base64");
|
|
1513
|
+
assertPdfBuffer(buffer);
|
|
1514
|
+
if (options.path) {
|
|
1515
|
+
const resolved = path2.resolve(options.path);
|
|
1516
|
+
fs2.writeFileSync(resolved, buffer);
|
|
1517
|
+
}
|
|
1518
|
+
} finally {
|
|
1519
|
+
try {
|
|
1520
|
+
await this.session.send("Emulation.setEmulatedMedia", { media: "" });
|
|
1521
|
+
} catch {
|
|
1522
|
+
}
|
|
1523
|
+
const duration = Date.now() - start;
|
|
1524
|
+
this.events.emit("action:end", { name: "pdf", frameId: this.mainFrameId, durationMs: duration });
|
|
1496
1525
|
}
|
|
1497
|
-
const duration = Date.now() - start;
|
|
1498
|
-
this.events.emit("action:end", { name: "pdf", frameId: this.mainFrameId, durationMs: duration });
|
|
1499
1526
|
return buffer;
|
|
1500
1527
|
}
|
|
1501
1528
|
getEvents() {
|
|
@@ -1504,6 +1531,13 @@ var Page = class {
|
|
|
1504
1531
|
getDefaultTimeout() {
|
|
1505
1532
|
return this.defaultTimeout;
|
|
1506
1533
|
}
|
|
1534
|
+
async waitForLoad(timeoutMs = this.defaultTimeout) {
|
|
1535
|
+
const frame = this.mainFrame();
|
|
1536
|
+
await waitFor(async () => {
|
|
1537
|
+
const readyState = await frame.evaluate("document.readyState");
|
|
1538
|
+
return readyState === "complete";
|
|
1539
|
+
}, { timeoutMs, description: "page load" });
|
|
1540
|
+
}
|
|
1507
1541
|
buildFrameTree(tree) {
|
|
1508
1542
|
const frame = this.ensureFrame(tree.frame.id);
|
|
1509
1543
|
frame.setMeta({ name: tree.frame.name, url: tree.frame.url, parentId: tree.frame.parentId });
|
|
@@ -1605,12 +1639,16 @@ var Browser = class {
|
|
|
1605
1639
|
events;
|
|
1606
1640
|
cleanupTasks;
|
|
1607
1641
|
contexts = /* @__PURE__ */ new Set();
|
|
1608
|
-
|
|
1642
|
+
wsEndpoint;
|
|
1643
|
+
pid;
|
|
1644
|
+
constructor(connection, child, logger, events, cleanupTasks = [], wsEndpoint = "") {
|
|
1609
1645
|
this.connection = connection;
|
|
1610
1646
|
this.process = child;
|
|
1611
1647
|
this.logger = logger;
|
|
1612
1648
|
this.events = events;
|
|
1613
1649
|
this.cleanupTasks = cleanupTasks;
|
|
1650
|
+
this.wsEndpoint = wsEndpoint;
|
|
1651
|
+
this.pid = child?.pid ?? 0;
|
|
1614
1652
|
}
|
|
1615
1653
|
on(event, handler) {
|
|
1616
1654
|
this.events.on(event, handler);
|
|
@@ -1632,6 +1670,23 @@ var Browser = class {
|
|
|
1632
1670
|
await page.initialize();
|
|
1633
1671
|
return page;
|
|
1634
1672
|
}
|
|
1673
|
+
/** Attach to an existing page by target ID. */
|
|
1674
|
+
async attachPage(targetId) {
|
|
1675
|
+
const { sessionId } = await this.connection.send("Target.attachToTarget", { targetId, flatten: true });
|
|
1676
|
+
const session = this.connection.createSession(sessionId);
|
|
1677
|
+
const page = new Page(session, this.logger, this.events);
|
|
1678
|
+
await page.initialize();
|
|
1679
|
+
return page;
|
|
1680
|
+
}
|
|
1681
|
+
/** List open page targets. */
|
|
1682
|
+
async pages() {
|
|
1683
|
+
const result = await this.connection.send("Target.getTargets");
|
|
1684
|
+
return result.targetInfos.filter((t) => t.type === "page").map((t) => ({ targetId: t.targetId, url: t.url, title: t.title }));
|
|
1685
|
+
}
|
|
1686
|
+
/** Disconnect without killing the browser process. */
|
|
1687
|
+
async disconnect() {
|
|
1688
|
+
await this.connection.close();
|
|
1689
|
+
}
|
|
1635
1690
|
async disposeContext(contextId) {
|
|
1636
1691
|
if (!contextId) return;
|
|
1637
1692
|
try {
|
|
@@ -1651,7 +1706,7 @@ var Browser = class {
|
|
|
1651
1706
|
} catch {
|
|
1652
1707
|
}
|
|
1653
1708
|
await this.connection.close();
|
|
1654
|
-
if (!this.process.killed) {
|
|
1709
|
+
if (this.process && !this.process.killed) {
|
|
1655
1710
|
this.process.kill();
|
|
1656
1711
|
}
|
|
1657
1712
|
for (const task of this.cleanupTasks) {
|
|
@@ -1993,6 +2048,7 @@ var ChromiumManager = class {
|
|
|
1993
2048
|
}
|
|
1994
2049
|
if (options.maximize) {
|
|
1995
2050
|
args.push("--start-maximized");
|
|
2051
|
+
args.push("--window-size=1920,1080");
|
|
1996
2052
|
}
|
|
1997
2053
|
if (options.args) {
|
|
1998
2054
|
args.push(...options.args);
|
|
@@ -2021,7 +2077,7 @@ var ChromiumManager = class {
|
|
|
2021
2077
|
logger.info(`Assertion ${payload.name}`, ...args2);
|
|
2022
2078
|
});
|
|
2023
2079
|
}
|
|
2024
|
-
const browser = new Browser(connection, child, logger, events, cleanupTasks);
|
|
2080
|
+
const browser = new Browser(connection, child, logger, events, cleanupTasks, wsEndpoint);
|
|
2025
2081
|
return browser;
|
|
2026
2082
|
}
|
|
2027
2083
|
resolveCacheRoot(platform) {
|
|
@@ -2463,22 +2519,89 @@ var automaton = {
|
|
|
2463
2519
|
async launch(options = {}) {
|
|
2464
2520
|
const manager = new ChromiumManager(options.logger);
|
|
2465
2521
|
return manager.launch(options);
|
|
2522
|
+
},
|
|
2523
|
+
async connect(wsEndpoint, options = {}) {
|
|
2524
|
+
const logger = options.logger ?? new Logger(options.logLevel ?? "warn");
|
|
2525
|
+
const connection = new Connection(wsEndpoint, logger);
|
|
2526
|
+
await connection.waitForOpen();
|
|
2527
|
+
const events = new AutomationEvents();
|
|
2528
|
+
const logEvents = options.logEvents ?? true;
|
|
2529
|
+
const logActions = options.logActions ?? true;
|
|
2530
|
+
const logAssertions = options.logAssertions ?? true;
|
|
2531
|
+
if (logEvents && logActions) {
|
|
2532
|
+
events.on("action:end", (payload) => {
|
|
2533
|
+
const selector = payload.sensitive ? void 0 : payload.selector;
|
|
2534
|
+
const args = [];
|
|
2535
|
+
if (selector) args.push(selector);
|
|
2536
|
+
if (typeof payload.durationMs === "number") args.push(`${payload.durationMs}ms`);
|
|
2537
|
+
logger.info(`Action ${payload.name}`, ...args);
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
if (logEvents && logAssertions) {
|
|
2541
|
+
events.on("assertion:end", (payload) => {
|
|
2542
|
+
const args = [];
|
|
2543
|
+
if (payload.selector) args.push(payload.selector);
|
|
2544
|
+
if (typeof payload.durationMs === "number") args.push(`${payload.durationMs}ms`);
|
|
2545
|
+
logger.info(`Assertion ${payload.name}`, ...args);
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
return new Browser(connection, null, logger, events, [], wsEndpoint);
|
|
2466
2549
|
}
|
|
2467
2550
|
};
|
|
2468
2551
|
|
|
2469
2552
|
// src/cli.ts
|
|
2553
|
+
function sessionFilePath() {
|
|
2554
|
+
const platform = detectPlatform();
|
|
2555
|
+
const cacheRoot = defaultCacheRoot(platform);
|
|
2556
|
+
return path5.join(cacheRoot, "session.json");
|
|
2557
|
+
}
|
|
2558
|
+
function writeSession(info) {
|
|
2559
|
+
const filePath = sessionFilePath();
|
|
2560
|
+
fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
2561
|
+
fs5.writeFileSync(filePath, JSON.stringify(info));
|
|
2562
|
+
}
|
|
2563
|
+
function readSession() {
|
|
2564
|
+
const filePath = sessionFilePath();
|
|
2565
|
+
if (!fs5.existsSync(filePath)) return null;
|
|
2566
|
+
try {
|
|
2567
|
+
const data = JSON.parse(fs5.readFileSync(filePath, "utf-8"));
|
|
2568
|
+
if (!data.wsEndpoint || !data.pid) return null;
|
|
2569
|
+
try {
|
|
2570
|
+
process.kill(data.pid, 0);
|
|
2571
|
+
} catch {
|
|
2572
|
+
clearSession();
|
|
2573
|
+
return null;
|
|
2574
|
+
}
|
|
2575
|
+
return data;
|
|
2576
|
+
} catch {
|
|
2577
|
+
return null;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
function clearSession() {
|
|
2581
|
+
const filePath = sessionFilePath();
|
|
2582
|
+
try {
|
|
2583
|
+
fs5.unlinkSync(filePath);
|
|
2584
|
+
} catch {
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2470
2587
|
function printHelp() {
|
|
2471
2588
|
console.log(`cdpwright (cpw) \u2014 Chromium automation CLI
|
|
2472
2589
|
|
|
2473
2590
|
Commands:
|
|
2474
2591
|
download [options] Download pinned Chromium snapshot
|
|
2475
2592
|
install Alias for download
|
|
2476
|
-
open <url> Launch
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2593
|
+
open <url> Launch browser session and navigate to URL
|
|
2594
|
+
close Close the running browser session
|
|
2595
|
+
screenshot [url] -o f Take a screenshot (PNG/JPEG)
|
|
2596
|
+
html [url] -o file.html Save the page HTML source
|
|
2597
|
+
pdf [url] -o file.pdf Generate PDF of page (headless only)
|
|
2598
|
+
eval [url] <script> Run script in page, print result as JSON
|
|
2480
2599
|
version Print cdpwright and Chromium versions
|
|
2481
2600
|
|
|
2601
|
+
Session:
|
|
2602
|
+
'open' starts a browser and saves a session. Other commands auto-connect
|
|
2603
|
+
to the running session when no URL is given. 'close' shuts it down.
|
|
2604
|
+
|
|
2482
2605
|
Download options:
|
|
2483
2606
|
--latest Download the latest Chromium revision
|
|
2484
2607
|
--mirror <url> Custom mirror base URL
|
|
@@ -2521,14 +2644,46 @@ function positionalArgs(args) {
|
|
|
2521
2644
|
}
|
|
2522
2645
|
return result;
|
|
2523
2646
|
}
|
|
2524
|
-
|
|
2647
|
+
function launchArgs() {
|
|
2525
2648
|
const args = [];
|
|
2526
2649
|
if (process.platform === "linux") {
|
|
2527
2650
|
args.push("--no-sandbox", "--no-zygote", "--disable-dev-shm-usage");
|
|
2528
2651
|
}
|
|
2652
|
+
return args;
|
|
2653
|
+
}
|
|
2654
|
+
function pngDimensions(buffer) {
|
|
2655
|
+
if (buffer.length < 24) {
|
|
2656
|
+
throw new Error("Invalid PNG screenshot");
|
|
2657
|
+
}
|
|
2658
|
+
if (buffer.readUInt32BE(0) !== 2303741511 || buffer.readUInt32BE(4) !== 218765834) {
|
|
2659
|
+
throw new Error("Invalid PNG screenshot");
|
|
2660
|
+
}
|
|
2661
|
+
return {
|
|
2662
|
+
width: buffer.readUInt32BE(16),
|
|
2663
|
+
height: buffer.readUInt32BE(20)
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
async function connectToSession() {
|
|
2667
|
+
const session = readSession();
|
|
2668
|
+
if (!session) {
|
|
2669
|
+
console.error("No running session. Start one with: cpw open <url>");
|
|
2670
|
+
process.exit(1);
|
|
2671
|
+
}
|
|
2672
|
+
const browser = await automaton.connect(session.wsEndpoint);
|
|
2673
|
+
const targets = await browser.pages();
|
|
2674
|
+
if (targets.length === 0) {
|
|
2675
|
+
console.error("Session has no open pages.");
|
|
2676
|
+
await browser.disconnect();
|
|
2677
|
+
process.exit(1);
|
|
2678
|
+
}
|
|
2679
|
+
const page = await browser.attachPage(targets[0].targetId);
|
|
2680
|
+
return { browser, page };
|
|
2681
|
+
}
|
|
2682
|
+
async function withPage(url, options, fn) {
|
|
2529
2683
|
const browser = await automaton.launch({
|
|
2530
2684
|
headless: options.headless ?? true,
|
|
2531
|
-
|
|
2685
|
+
maximize: options.maximize ?? false,
|
|
2686
|
+
args: launchArgs(),
|
|
2532
2687
|
logLevel: "warn"
|
|
2533
2688
|
});
|
|
2534
2689
|
try {
|
|
@@ -2555,19 +2710,24 @@ async function cmdOpen(rest) {
|
|
|
2555
2710
|
process.exit(1);
|
|
2556
2711
|
}
|
|
2557
2712
|
const headless = resolveHeadless(rest, false);
|
|
2558
|
-
const args = [];
|
|
2559
|
-
if (process.platform === "linux") {
|
|
2560
|
-
args.push("--no-sandbox", "--no-zygote", "--disable-dev-shm-usage");
|
|
2561
|
-
}
|
|
2562
2713
|
const browser = await automaton.launch({
|
|
2563
2714
|
headless,
|
|
2564
|
-
args,
|
|
2715
|
+
args: launchArgs(),
|
|
2565
2716
|
logLevel: "warn"
|
|
2566
2717
|
});
|
|
2567
2718
|
const page = await browser.newPage();
|
|
2568
2719
|
await page.goto(url, { waitUntil: "load" });
|
|
2720
|
+
writeSession({
|
|
2721
|
+
wsEndpoint: browser.wsEndpoint,
|
|
2722
|
+
pid: browser.pid || process.pid
|
|
2723
|
+
});
|
|
2569
2724
|
console.log(`Browser open at ${url}`);
|
|
2570
|
-
console.log(
|
|
2725
|
+
console.log(`Session saved. Run commands in another terminal:`);
|
|
2726
|
+
console.log(` npx cpw screenshot -o shot.png`);
|
|
2727
|
+
console.log(` npx cpw eval "document.title"`);
|
|
2728
|
+
console.log(` npx cpw close`);
|
|
2729
|
+
console.log(`
|
|
2730
|
+
Press Ctrl+C to close.`);
|
|
2571
2731
|
await new Promise((resolve) => {
|
|
2572
2732
|
process.on("SIGINT", () => {
|
|
2573
2733
|
console.log("\nClosing browser...");
|
|
@@ -2575,52 +2735,200 @@ async function cmdOpen(rest) {
|
|
|
2575
2735
|
});
|
|
2576
2736
|
process.on("SIGTERM", () => resolve());
|
|
2577
2737
|
});
|
|
2738
|
+
clearSession();
|
|
2578
2739
|
try {
|
|
2579
2740
|
await browser.close();
|
|
2580
2741
|
} catch {
|
|
2581
2742
|
}
|
|
2582
2743
|
}
|
|
2744
|
+
async function cmdClose() {
|
|
2745
|
+
const session = readSession();
|
|
2746
|
+
if (!session) {
|
|
2747
|
+
console.error("No running session.");
|
|
2748
|
+
process.exit(1);
|
|
2749
|
+
}
|
|
2750
|
+
try {
|
|
2751
|
+
const browser = await automaton.connect(session.wsEndpoint);
|
|
2752
|
+
await browser.close();
|
|
2753
|
+
} catch {
|
|
2754
|
+
try {
|
|
2755
|
+
process.kill(session.pid, "SIGTERM");
|
|
2756
|
+
} catch {
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
clearSession();
|
|
2760
|
+
console.log("Session closed.");
|
|
2761
|
+
}
|
|
2583
2762
|
async function cmdScreenshot(rest) {
|
|
2584
|
-
const
|
|
2763
|
+
const pos = positionalArgs(rest);
|
|
2764
|
+
const url = pos[0];
|
|
2585
2765
|
const output = flagValue(rest, "-o") || flagValue(rest, "--output");
|
|
2586
|
-
if (!
|
|
2587
|
-
console.error("Usage: cpw screenshot
|
|
2766
|
+
if (!output) {
|
|
2767
|
+
console.error("Usage: cpw screenshot [url] -o <file> [--full-page]");
|
|
2588
2768
|
process.exit(1);
|
|
2589
2769
|
}
|
|
2590
2770
|
const fullPage = hasFlag(rest, "--full-page");
|
|
2591
|
-
const headless = resolveHeadless(rest, true);
|
|
2592
2771
|
const format = output.endsWith(".jpeg") || output.endsWith(".jpg") ? "jpeg" : "png";
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2772
|
+
if (url) {
|
|
2773
|
+
const headless = resolveHeadless(rest, true);
|
|
2774
|
+
await withPage(url, { headless }, async (page) => {
|
|
2775
|
+
await page.screenshot({ path: output, format, fullPage });
|
|
2776
|
+
});
|
|
2777
|
+
} else {
|
|
2778
|
+
const { browser, page } = await connectToSession();
|
|
2779
|
+
try {
|
|
2780
|
+
await page.screenshot({ path: output, format, fullPage });
|
|
2781
|
+
} finally {
|
|
2782
|
+
await browser.disconnect();
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2596
2785
|
console.log(`Screenshot saved to ${output}`);
|
|
2597
2786
|
}
|
|
2787
|
+
async function cmdHtml(rest) {
|
|
2788
|
+
const pos = positionalArgs(rest);
|
|
2789
|
+
const url = pos[0];
|
|
2790
|
+
const output = flagValue(rest, "-o") || flagValue(rest, "--output");
|
|
2791
|
+
if (!output) {
|
|
2792
|
+
console.error("Usage: cpw html [url] -o <file>");
|
|
2793
|
+
process.exit(1);
|
|
2794
|
+
}
|
|
2795
|
+
const writeHtml = async (page) => {
|
|
2796
|
+
const html = await page.content();
|
|
2797
|
+
fs5.writeFileSync(path5.resolve(output), html, "utf-8");
|
|
2798
|
+
};
|
|
2799
|
+
if (url) {
|
|
2800
|
+
const headless = resolveHeadless(rest, true);
|
|
2801
|
+
await withPage(url, { headless }, async (page) => {
|
|
2802
|
+
await writeHtml(page);
|
|
2803
|
+
});
|
|
2804
|
+
} else {
|
|
2805
|
+
const { browser, page } = await connectToSession();
|
|
2806
|
+
try {
|
|
2807
|
+
await writeHtml(page);
|
|
2808
|
+
} finally {
|
|
2809
|
+
await browser.disconnect();
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
console.log(`HTML saved to ${output}`);
|
|
2813
|
+
}
|
|
2598
2814
|
async function cmdPdf(rest) {
|
|
2599
|
-
const
|
|
2815
|
+
const pos = positionalArgs(rest);
|
|
2816
|
+
const url = pos[0];
|
|
2600
2817
|
const output = flagValue(rest, "-o") || flagValue(rest, "--output");
|
|
2601
|
-
if (!
|
|
2602
|
-
console.error("Usage: cpw pdf
|
|
2818
|
+
if (!output) {
|
|
2819
|
+
console.error("Usage: cpw pdf [url] -o <file>");
|
|
2603
2820
|
process.exit(1);
|
|
2604
2821
|
}
|
|
2605
|
-
|
|
2606
|
-
await page.
|
|
2607
|
-
|
|
2822
|
+
const renderVisualPdf = async (browser, page) => {
|
|
2823
|
+
const screenshot = await page.screenshotBase64({ format: "png" });
|
|
2824
|
+
const screenshotSize = pngDimensions(Buffer.from(screenshot, "base64"));
|
|
2825
|
+
const context = await browser.newContext();
|
|
2826
|
+
try {
|
|
2827
|
+
const helperPage = await context.newPage();
|
|
2828
|
+
const imageDataUrl = `data:image/png;base64,${screenshot}`;
|
|
2829
|
+
const screenshotDimensions = await helperPage.evaluate((dataUrl) => {
|
|
2830
|
+
document.open();
|
|
2831
|
+
document.write(`<!doctype html>
|
|
2832
|
+
<html>
|
|
2833
|
+
<head>
|
|
2834
|
+
<meta charset="utf-8">
|
|
2835
|
+
<style>
|
|
2836
|
+
html, body {
|
|
2837
|
+
margin: 0;
|
|
2838
|
+
padding: 0;
|
|
2839
|
+
overflow: hidden;
|
|
2840
|
+
background: #fff;
|
|
2841
|
+
}
|
|
2842
|
+
img {
|
|
2843
|
+
display: block;
|
|
2844
|
+
width: 100%;
|
|
2845
|
+
height: auto;
|
|
2846
|
+
}
|
|
2847
|
+
</style>
|
|
2848
|
+
</head>
|
|
2849
|
+
<body>
|
|
2850
|
+
<img id="shot" alt="page screenshot">
|
|
2851
|
+
</body>
|
|
2852
|
+
</html>`);
|
|
2853
|
+
document.close();
|
|
2854
|
+
const img = document.getElementById("shot");
|
|
2855
|
+
if (!(img instanceof HTMLImageElement)) {
|
|
2856
|
+
throw new Error("Failed to create PDF preview image");
|
|
2857
|
+
}
|
|
2858
|
+
return new Promise((resolve, reject) => {
|
|
2859
|
+
img.onload = () => {
|
|
2860
|
+
resolve({ width: img.naturalWidth || 0, height: img.naturalHeight || 0 });
|
|
2861
|
+
};
|
|
2862
|
+
img.onerror = () => reject(new Error("Failed to load screenshot image"));
|
|
2863
|
+
img.src = dataUrl;
|
|
2864
|
+
});
|
|
2865
|
+
}, imageDataUrl);
|
|
2866
|
+
await helperPage.pdf({
|
|
2867
|
+
path: output,
|
|
2868
|
+
printBackground: true,
|
|
2869
|
+
paperWidth: Math.max((screenshotDimensions?.width ?? screenshotSize.width) / 96, 1),
|
|
2870
|
+
paperHeight: Math.max((screenshotDimensions?.height ?? screenshotSize.height) / 96, 1),
|
|
2871
|
+
marginTop: 0,
|
|
2872
|
+
marginBottom: 0,
|
|
2873
|
+
marginLeft: 0,
|
|
2874
|
+
marginRight: 0,
|
|
2875
|
+
scale: 1,
|
|
2876
|
+
preferCSSPageSize: false
|
|
2877
|
+
});
|
|
2878
|
+
} finally {
|
|
2879
|
+
await context.close();
|
|
2880
|
+
}
|
|
2881
|
+
};
|
|
2882
|
+
if (url) {
|
|
2883
|
+
await withPage(url, { headless: true, maximize: true }, async (page, browser) => {
|
|
2884
|
+
await renderVisualPdf(browser, page);
|
|
2885
|
+
});
|
|
2886
|
+
} else {
|
|
2887
|
+
const { browser, page } = await connectToSession();
|
|
2888
|
+
try {
|
|
2889
|
+
await renderVisualPdf(browser, page);
|
|
2890
|
+
} finally {
|
|
2891
|
+
await browser.disconnect();
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2608
2894
|
console.log(`PDF saved to ${output}`);
|
|
2609
2895
|
}
|
|
2610
2896
|
async function cmdEval(rest) {
|
|
2611
2897
|
const pos = positionalArgs(rest);
|
|
2612
|
-
const
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2898
|
+
const session = readSession();
|
|
2899
|
+
let url;
|
|
2900
|
+
let script;
|
|
2901
|
+
if (pos.length >= 2) {
|
|
2902
|
+
url = pos[0];
|
|
2903
|
+
script = pos[1];
|
|
2904
|
+
} else if (pos.length === 1 && session) {
|
|
2905
|
+
script = pos[0];
|
|
2906
|
+
} else if (pos.length === 1) {
|
|
2907
|
+
console.error("Usage: cpw eval <url> <script>\n cpw eval <script> (when a session is running)");
|
|
2908
|
+
process.exit(1);
|
|
2909
|
+
return;
|
|
2910
|
+
} else {
|
|
2911
|
+
console.error("Usage: cpw eval [url] <script>");
|
|
2616
2912
|
process.exit(1);
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
if (url) {
|
|
2916
|
+
const headless = resolveHeadless(rest, true);
|
|
2917
|
+
await withPage(url, { headless }, async (page) => {
|
|
2918
|
+
const result = await page.evaluate(script);
|
|
2919
|
+
const output = result === void 0 ? "undefined" : JSON.stringify(result, null, 2);
|
|
2920
|
+
console.log(output);
|
|
2921
|
+
});
|
|
2922
|
+
} else {
|
|
2923
|
+
const { browser, page } = await connectToSession();
|
|
2924
|
+
try {
|
|
2925
|
+
const result = await page.evaluate(script);
|
|
2926
|
+
const output = result === void 0 ? "undefined" : JSON.stringify(result, null, 2);
|
|
2927
|
+
console.log(output);
|
|
2928
|
+
} finally {
|
|
2929
|
+
await browser.disconnect();
|
|
2930
|
+
}
|
|
2617
2931
|
}
|
|
2618
|
-
const headless = resolveHeadless(rest, true);
|
|
2619
|
-
await withPage(url, { headless }, async (page) => {
|
|
2620
|
-
const result = await page.evaluate(script);
|
|
2621
|
-
const output = result === void 0 ? "undefined" : JSON.stringify(result, null, 2);
|
|
2622
|
-
console.log(output);
|
|
2623
|
-
});
|
|
2624
2932
|
}
|
|
2625
2933
|
async function cmdVersion() {
|
|
2626
2934
|
const pkgPath = path5.resolve(path5.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
@@ -2646,6 +2954,10 @@ async function cmdVersion() {
|
|
|
2646
2954
|
} else {
|
|
2647
2955
|
console.log("Chromium: not installed (run 'cpw install')");
|
|
2648
2956
|
}
|
|
2957
|
+
const session = readSession();
|
|
2958
|
+
if (session) {
|
|
2959
|
+
console.log(`Session: active (pid ${session.pid})`);
|
|
2960
|
+
}
|
|
2649
2961
|
}
|
|
2650
2962
|
async function main() {
|
|
2651
2963
|
const [, , command, ...rest] = process.argv;
|
|
@@ -2659,8 +2971,12 @@ async function main() {
|
|
|
2659
2971
|
return cmdDownload(rest);
|
|
2660
2972
|
case "open":
|
|
2661
2973
|
return cmdOpen(rest);
|
|
2974
|
+
case "close":
|
|
2975
|
+
return cmdClose();
|
|
2662
2976
|
case "screenshot":
|
|
2663
2977
|
return cmdScreenshot(rest);
|
|
2978
|
+
case "html":
|
|
2979
|
+
return cmdHtml(rest);
|
|
2664
2980
|
case "pdf":
|
|
2665
2981
|
return cmdPdf(rest);
|
|
2666
2982
|
case "eval":
|