Headless Chrome Automation: API Overview, Coding Tips, and Example Scripts
This article introduces the Chrome DevTools Protocol API, provides practical coding tips for using the chrome-remote-interface library, and demonstrates complete Node.js examples for gathering performance metrics, automating Baidu searches, and extracting first‑page results with headless Chrome.
1 API Overview & Coding Tips
1.1 Documentation
GitHub Chrome DevTools Protocol repository – the protocol source; issues can be filed here.
GitHub debugger-protocol-viewer – repository for the API documentation viewer.
API documentation site (https://chromedevtools.github.io/devtools-protocol/) – frequently used for browsing the API.
1.2 Common APIs
Network – network requests, cookies, cache, certificates, etc.
Page – page loading, resources, dialogs, screenshots, printing, etc.
DOM – DOM retrieval, modification, deletion, queries, etc.
Runtime – JavaScript execution; this is where most custom logic lives.
1.3 Coding Tips
Instead of using raw WebSocket commands, employ the chrome-remote-interface library, which offers a Promise‑based wrapper.
Each functional area is represented as a separate domain (e.g., Network, Page, DOM).
Most domains provide an enable method that must be called before using other methods.
Domain method parameters are passed as a single object (or Map); you do not need to worry about positional arguments.
Domain method return values are also objects; retrieve the needed fields by key.
Parameters and return values often contain meta‑information such as IDs rather than full object content.
2 Coding Examples
First, create a simple wrapper to prepare the API execution environment (see the previous article for the utility library).
const chromeLauncher = require('chrome-launcher');
const chromeRemoteInterface = require('chrome-remote-interface');
const prepareAPI = (config = {}) => {
const {host = 'localhost', port = 9222, autoSelectChrome = true, headless = true} = config;
const wrapperEntry = chromeLauncher.launch({
host,
port,
autoSelectChrome,
additionalFlags: [
'--disable-gpu',
headless ? '--headless' : ''
]
}).then(chromeInstance => {
const remoteInterface = chromeRemoteInterface(config).then(chromeAPI => chromeAPI).catch(err => { throw err; });
return Promise.all([chromeInstance, remoteInterface]);
}).catch(err => { throw err; });
return wrapperEntry;
};Collect Page Performance Data (Navigation Timing)
const wrapper = require('the-wrapper-module');
const performanceParser = (performanceTiming) => {
let timingGather = {};
performanceTiming = performanceTiming || {};
timingGather.redirect = performanceTiming.redirectEnd - performanceTiming.redirectStart;
timingGather.dns = performanceTiming.domainLookupEnd - performanceTiming.domainLookupStart;
timingGather.tcp = performanceTiming.connectEnd - performanceTiming.connectStart;
timingGather.request = performanceTiming.responseStart - performanceTiming.requestStart;
timingGather.response = performanceTiming.responseEnd - performanceTiming.responseStart;
timingGather.domReady = performanceTiming.domContentLoadedEventStart - performanceTiming.navigationStart;
timingGather.load = performanceTiming.loadEventStart - performanceTiming.navigationStart;
return timingGather;
};
const showPerformanceInfo = (performanceInfo) => {
performanceInfo = performanceInfo || {};
console.log(`Redirect time: ${performanceInfo.redirect}`);
console.log(`DNS lookup time: ${performanceInfo.dns}`);
console.log(`TCP connection time: ${performanceInfo.tcp}`);
console.log(`Request send time: ${performanceInfo.request}`);
console.log(`Response receive time: ${performanceInfo.response}`);
console.log(`DOM ready time: ${performanceInfo.domReady}`);
console.log(`Page load time: ${performanceInfo.load}`);
};
wrapper.prepareAPI().then(([chromeInstance, remoteInterface]) => {
const {Runtime, Page} = remoteInterface;
Page.loadEventFired(() => {
Runtime.evaluate({
expression: 'window.performance.timing.toJSON()',
returnByValue: true
}).then(resultObj => {
const {result, exceptionDetails} = resultObj;
if (!exceptionDetails) {
showPerformanceInfo(performanceParser(result.value));
} else {
throw exceptionDetails;
}
});
});
Page.enable().then(() => {
Page.navigate({url: 'http://www.baidu.com'});
});
});Search Baidu and Scrape First‑Page Results
const wrapper = require('the-wrapper-module');
// Note: arrow functions lose their own `this` binding; use regular functions when needed.
const buttonClick = function () { this.click(); };
const setInputValue = () => {
var input = document.getElementById('kw');
input.value = 'Web automation headless chrome';
};
const parseSearchResult = () => {
let resultList = [];
const linkBlocks = document.querySelectorAll('div.result.c-container');
for (let block of Array.from(linkBlocks)) {
let targetObj = block.querySelector('h3');
resultList.push({
title: targetObj.textContent,
link: targetObj.querySelector('a').getAttribute('href')
});
}
return resultList;
};
wrapper.prepareAPI({/* headless: false // uncomment to see the browser */}).then(([chromeInstance, remoteInterface]) => {
const {Runtime, DOM, Page, Network} = remoteInterface;
let framePointer;
Promise.all([Page.enable(), Network.enable(), DOM.enable(), Page.setAutoAttachToCreatedPages({autoAttach: true})])
.then(() => {
Page.domContentEventFired(() => {
console.log('Page.domContentEventFired');
Runtime.evaluate({expression: `window.location.href`, returnByValue: true}).then(result => console.log(result));
});
Page.frameNavigated(() => console.log('Page.frameNavigated'));
Page.loadEventFired(() => {
console.log('Page.loadEventFired');
Runtime.evaluate({expression: `window.location.href`, returnByValue: true}).then(result => console.log(result));
DOM.getDocument().then(({root}) => {
DOM.querySelector({nodeId: root.nodeId, selector: '#form'}).then(({nodeId}) => {
Promise.all([
// Fill the search box
DOM.querySelector({nodeId, selector: '#kw'}).then(inputNode => {
Runtime.evaluate({expression: `(${setInputValue})()`, returnByValue: true});
}),
// Get the submit button
DOM.querySelector({nodeId, selector: '#su'})
]).then(([inputNode, buttonNode]) => {
// Click the button
DOM.resolveNode({nodeId: buttonNode.nodeId}).then(({object}) => {
const {objectId} = object;
return Runtime.callFunctionOn({objectId, functionDeclaration: `(${buttonClick})`});
}).then(() => {
setTimeout(() => {
Runtime.evaluate({expression: `(${parseSearchResult})()`, returnByValue: true})
.then(({result}) => console.log(result.value));
}, 3000);
});
});
});
});
});
Page.navigate({url: 'http://www.baidu.com'}).then(frameObj => { framePointer = frameObj; });
});
});The article concludes with screenshots illustrating the automation results.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Qunar Tech Salon
Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
