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.
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.