An async/await friendly Chrome debugging client with TypeScript support, designed with automation in mind.
- Promise API for async/await (most debugger commands are meant to be sequential).
- TypeScript support and uses "devtools-protocol" types, allowing you to pick a protocol version.
- Launches Chrome with a new temp user data folder so Chrome launches an isolated instance. (regardless if you already have Chrome open).
- Opens Chrome with a pipe message transport to the browser connection and supports attaching flattened session connections to targets.
- Supports cancellation in a way that avoids unhandled rejections, and allows you to add combine additional cancellation concerns.
- Supports seeing protocol debug messages with
DEBUG=chrome-debugging-client:* - Use with race-cancellation library to add timeouts or other cancellation concerns to tasks using the connection.
- The library was designed to be careful about not floating promises (promises are chained immediately after being created, combining concurrent promises with all or race), this avoids unhandled rejections.
#!/usr/bin/env node
const{ writeFileSync }=require("fs");const{ spawnChrome }=require("chrome-debugging-client");/** * Print a url to a PDF file. * @param url {string} * @param file {string} */asyncfunctionprintToPDF(url,file){constchrome=spawnChrome({headless: true});try{constbrowser=chrome.connection;// we create with a target of about:blank so that we can// setup Page events before navigating to urlconst{ targetId }=awaitbrowser.send("Target.createTarget",{url: "about:blank",});constpage=awaitbrowser.attachToTarget(targetId);// enable events for Page domainawaitpage.send("Page.enable");// concurrently wait until load and navigateawaitPromise.all([page.until("Page.loadEventFired"),page.send("Page.navigate",{ url }),]);const{ data }=awaitpage.send("Page.printToPDF");writeFileSync(file,data,"base64");// attempt graceful closeawaitchrome.close();}finally{// kill process if hasn't exitedawaitchrome.dispose();}console.log(`${url} written to ${file}`);}if(process.argv.length<4){console.log(`usage: printToPDF.js url file`);console.log(`example: printToPDF.js https://en.wikipedia.org/wiki/Binomial_coefficient Binomial_coefficient.pdf`,);process.exit(1);}printToPDF(process.argv[2],process.argv[3]).catch((err)=>{console.log("print failed %o",err);});#!/usr/bin/env node
const{ spawnWithWebSocket }=require("chrome-debugging-client");asyncfunctionmain(){constscript=`const obj = { hello: "world", }; console.log("end"); `;// start node requesting it break on start at debug port that// is availableconstnode=awaitspawnWithWebSocket(process.execPath,[// node will pick an available port and wait for debugger"--inspect-brk=0","-e",script,]);asyncfunctiondoDebugging(){const{ connection }=node;// Setup console api handlerconnection.on("Runtime.consoleAPICalled",({ type, args })=>{console.log(`console.${type}: ${JSON.stringify(args)}`);});// We requested Node to break on start, so we runIfWaitingForDebugger// and wait for it to break at the start of our script.// These commands must be sent concurrently with// the pause event setup.const[{callFrames: [{location: { scriptId },},],
reason,},]=awaitPromise.all([connection.until("Debugger.paused"),connection.send("Debugger.enable"),connection.send("Runtime.enable"),connection.send("Runtime.runIfWaitingForDebugger"),]);// Right now we are paused at the start of the scriptconsole.log(`paused reason: ${reason}`);//= paused: Break on startconsole.log(`set breakpoint on line 3`);awaitconnection.send("Debugger.setBreakpoint",{location: {lineNumber: 3,
scriptId,},});console.log("resume and wait for next paused event");const[breakpoint]=awaitPromise.all([connection.until("Debugger.paused"),connection.send("Debugger.resume"),]);const{callFrames: [{ location, callFrameId }],}=breakpoint;console.log(`paused at line ${location.lineNumber}`);console.log("evaluate `obj`");const{ result }=awaitconnection.send("Debugger.evaluateOnCallFrame",{
callFrameId,expression: "obj",returnByValue: true,});console.log(JSON.stringify(result.value));//= {"hello":"world"}console.log("resume and wait for execution context to be destroyed");awaitPromise.all([connection.until("Runtime.executionContextDestroyed"),connection.send("Debugger.resume"),]);}try{awaitdoDebugging();// Node is still alive here and waiting for the debugger to disconnectconsole.log("close websocket");node.close();// Node should exit on its own after the websocket closesconsole.log("wait for exit");awaitnode.waitForExit();console.log("node exited");}finally{// kill process if still aliveawaitnode.dispose();}}main().catch((err)=>{console.log("print failed %o",err);});