Automating report generation using Selenium Webdriver
If you need to automate the generation of chart images or PDF, Selenium Webdriver might be the best option. It can handle different browsers even though this example only shows it using Firefox.
Setting up
Section titled “Setting up”- Install NodeJS
- Install Firefox
- Create a folder
- Create a file called
package.jsonin the new folder with the content shown below:
{ "name": "amcharts", "description": "Automate chart export", "engines": { "node": ">=6.0.0" }, "devDependencies": { "selenium-webdriver": "4.0.0-alpha.1", "geckodriver": "1.15.1" } }- Open a terminal and run
npm ion the same folder to install the dependencies
Exporting a chart from an external link
Section titled “Exporting a chart from an external link”You can export charts that exist on a current page.
Add a file called external.js for now. You can rename it as you wish. Add the code below in it:
const { Builder, until } = require('selenium-webdriver');const firefox = require('selenium-webdriver/firefox');require('geckodriver');(async (server) => { let url = 'https://www.amcharts.com/demos/exporting-chart-to-image/', options = new firefox.Options(); // Set some conditions for the download manager options.setPreference('browser.helperApps.neverAsk.saveToDisk', 'application/pdf'); options.setPreference('browser.download.folderList', 2); options.setPreference('browser.download.manager.showWhenStarting', false); options.setPreference('pdfjs.disabled', true); // Set the directory to save the exported file options.setPreference('browser.download.dir', __dirname); // Open the browser headless options.addArguments('-headless'); let driver = await new Builder() .forBrowser('firefox') .setFirefoxOptions(options) .build(); try { await driver.get(url); await driver.executeAsyncScript(() => { let callback = arguments[arguments.length - 1], exportChart = () => { chart.exporting.events.on('exportfinished', () => { setTimeout(callback, 5000); }); // Start the export chart.exporting.export('pdf'); }, startExportWhenReady = () => { if (chart.isReady()) exportChart(); else chart.events.on('ready', exportChart); }; if (document.readyState === 'complete') startExportWhenReady(); else document.addEventListener('DOMContentLoaded', startExportWhenReady); }) .then(() => { driver.quit(); }); } finally { await driver.quit(); }})();Now run node external.js in the terminal.
The exported files will be in the folder you created
Exporting a chart from the same package
Section titled “Exporting a chart from the same package”If you prefer to create charts without requiring external pre-existing pages, you can have them built into this solution. We will setup a server using HapiJS by first adding the following dependencies in the package.json file:
{ "name": "amcharts", "description": "Automate chart export", "engines": { "node": ">=6.0.0" }, "devDependencies": { "selenium-webdriver": "4.0.0-alpha.1", "geckodriver": "1.15.1", "hapi": "18.1.0", "inert": "5.1.2" } }Run npm i again to install the new dependencies.
Add server.js file and the following code into it:
const Hapi = require('hapi');const Inert = require('inert');const exportChart = require('./internal');const server = new Hapi.Server({ host: 'localhost', port: 3000});server.route({ method: 'GET', path: '/', handler: (request, h) => { return h.file('./public/index.html'); }});server.route({ method: 'GET', path: '/{fileName*}', handler: (request, h) => { return h.file('./public/' + request.params.fileName); }});const init = async () => {await server.register(Inert); await server.start(); console.log(`Server running at: ${server.info.uri}`);}server.events.on('start', () => {// Start the export feature exportChart(server); console.log('Server started and the export module is loaded');});server.events.on('stop', () => { console.log('Server stopped');});init();Adding files and folders
Section titled “Adding files and folders”At this point you should some of the files below. Please add the missing folders and files as shown and then the correspondent content:
/├── package.json├── server.js├── internal.js├── external.js└── public/ ├── index.html ├── styles/ | └── app.css └── scripts/ └── chart.jsThe index.html file
Section titled “The index.html file”<!DOCTYPE html><html lang="en"><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>amCharts 4</title> <link rel="stylesheet" href="/styles/app.css"></head><body> <div id="chartdiv"></div> <script src="//www.amcharts.com/lib/4/core.js"></script> <script src="//www.amcharts.com/lib/4/charts.js"></script> <script src="//www.amcharts.com/lib/4/themes/animated.js"></script> <script src="/scripts/chart.js"></script> </body></html>The app.css file
Section titled “The app.css file”@import url("https://fonts.googleapis.com/css?family=Archivo+Narrow");body { font-family: "Archivo Narrow";}#chartdiv { width: 100%; height: 500px;}The chart.js file
Section titled “The chart.js file”am4core.useTheme(am4themes_animated);var chart = am4core.create("chartdiv", am4charts.XYChart);chart.data = [{ category: "One", value1: 1, value2: 5, value3: 3, value4: 3}, { category: "Two", value1: 2, value2: 5, value3: 3, value4: 4}, { category: "Three", value1: 3, value2: 5, value3: 4, value4: 4}, { category: "Four", value1: 4, value2: 5, value3: 6, value4: 4}, { category: "Five", value1: 3, value2: 5, value3: 4, value4: 4}, { category: "Six", value1: 8, value2: 7, value3: 10, value4: 4}, { category: "Seven", value1: 10, value2: 8, value3: 6, value4: 4}];chart.legend = new am4charts.Legend();chart.colors.step = 2;var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());categoryAxis.dataFields.category = "category";categoryAxis.renderer.grid.template.location = 0;var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());valueAxis.min = 0;valueAxis.renderer.minWidth = 35;var series1 = chart.series.push(new am4charts.ColumnSeries());series1.columns.template.width = am4core.percent(80);series1.columns.template.tooltipText = "{name}: {valueY.value}";series1.name = "Series 1";series1.dataFields.categoryX = "category";series1.dataFields.valueY = "value1";series1.stacked = true;var series2 = chart.series.push(new am4charts.ColumnSeries());series2.columns.template.width = am4core.percent(80);series2.columns.template.tooltipText = "{name}: {valueY.value}";series2.name = "Series 2";series2.dataFields.categoryX = "category";series2.dataFields.valueY = "value2";series2.stacked = true;var series3 = chart.series.push(new am4charts.ColumnSeries());series3.columns.template.width = am4core.percent(80);series3.columns.template.tooltipText = "{name}: {valueY.value}";series3.name = "Series 3";series3.dataFields.categoryX = "category";series3.dataFields.valueY = "value3";series3.stacked = true;var series4 = chart.series.push(new am4charts.ColumnSeries());series4.columns.template.width = am4core.percent(80);series4.columns.template.tooltipText = "{name}: {valueY.value}";series4.name = "Series 4";series4.dataFields.categoryX = "category";series4.dataFields.valueY = "value4";series4.stacked = true;chart.scrollbarX = new am4core.Scrollbar();chart.exporting.menu = new am4core.ExportMenu();The internal.js file
Section titled “The internal.js file”const { Builder, until } = require('selenium-webdriver');const firefox = require('selenium-webdriver/firefox');require('geckodriver');module.exports = async (server) => { let url = 'http://localhost:3000', options = new firefox.Options(); // Set some conditions for the download manager options.setPreference('browser.helperApps.neverAsk.saveToDisk', 'application/pdf'); options.setPreference('browser.download.folderList', 2); options.setPreference('browser.download.manager.showWhenStarting', false); options.setPreference('pdfjs.disabled', true); // Set the directory to save the exported file options.setPreference('browser.download.dir', __dirname); // Open the browser headless options.addArguments('-headless'); let driver = await new Builder() .forBrowser('firefox') .setFirefoxOptions(options) .build(); try { await driver.get(url); await driver.executeAsyncScript(() => { let callback = arguments[arguments.length - 1], exportChart = () => { chart.exporting.events.on('exportfinished', () => { setTimeout(callback, 5000); }); // Start the export chart.exporting.export('pdf'); }, startExportWhenReady = () => { if (chart.isReady()) exportChart(); else chart.events.on('ready', exportChart); }; if (document.readyState === 'complete') startExportWhenReady(); else document.addEventListener('DOMContentLoaded', startExportWhenReady); }) .then(() => { driver.quit(); }); } finally { await driver.quit(); }};Now run node server.js to start the server and to generate the chart.
Where to customize the export functionality
Section titled “Where to customize the export functionality”You can customize the code calling the export functionality inside the driver.executeAsyncScript method as shown below:
// Run scripts in the pageawait driver.executeAsyncScript(() => { // Get the Selenium callback let callback = arguments[arguments.length - 1], exportChart = () => { chart.exporting.events.on('exportfinished', () => { // The callback lets the Selenium driver know the script is done setTimeout(callback, 5000); }); // Start the export chart.exporting.export('pdf'); }, startExportWhenReady = () => { // Start exporting of the chart is ready if (chart.isReady()) exportChart(); // Start exporting when the document is ready else chart.events.on('ready', exportChart); }; // Start exporting if the document is ready if (document.readyState === 'complete') startExportWhenReady(); // Start exporting when the document is ready else document.addEventListener('DOMContentLoaded', startExportWhenReady);})Please check more about exporting here.
Download the example
Section titled “Download the example”Download the whole example here and run npm i to load the dependencies.