UI performance metrics with Google Lighthouse & Selenium 4 CDP
Introduction:
In most MVC-based applications, the UI primarily renders data received from backend services. However, this isn’t always the case — there are scenarios where data transformation logic is implemented directly in the UI, depending on the architecture and organisational requirements. While MVC serves as a guiding principle, we cannot simply criticise or question UI-heavy applications that involve significant processing. In such cases, UI performance becomes crucial, especially when analysing the performance differences between the initial load and subsequent interactions.
Google Lighthouse: The Performance Auditor
Google Lighthouse is a powerful open-source tool from Google that audits UI pages for performance bottlenecks. It provides valuable insights and suggests improvements to optimize web page loading and interactivity. Lighthouse can be added as a Chrome plugin to analyze webpage performance manually. However, manual audits can be tedious and time-consuming.
Can We Automate Lighthouse with Selenium?
The answer is YES! Instead of running Lighthouse audits manually, we can integrate Google Lighthouse with Selenium to collect performance metrics programmatically. This enables us to automate UI performance checks within our test framework.
Google Lighthouse Performance Metrics:
Selenium CDP Metrics:
Selenium 4 introduced Chrome DevTools Protocol (CDP) integration, allowing deeper insights into browser behavior, including performance metrics. With CDP, you can capture network requests, page load times, JavaScript execution times, and rendering performance, making it a powerful tool for UI performance testing. But for the analysis, I am extracting below metrics
Installing Google Lighthouse CLI:
Before integrating with Selenium, let’s install and run Lighthouse manually.
Step1: Install node.js and Lighthouse:
Lighthouse is a Node.js-based tool, so we need to install it first
# Install Node.js (if not installed)
https://nodejs.org/en/download/
# Install Lighthouse globally
npm install -g lighthouse
Step2: Run Lighthouse Audit via CLI:
Once installed, you can run Lighthouse on any website:
lighthouse https://automationteststore.com --preset=desktop --output=json --output-path=report.json
This will generate a performance report in JSON format.
Step 3: Extract Metrics Manually:
To manually extract performance data, open the Lighthouse JSON report and find key values like:
- Cumulative Layout Shift (CLS) → Measures page stability
- First Contentful Paint (FCP) → Time to first visible content
- Largest Contentful Paint (LCP) → Time to largest visible content
- Total Blocking Time (TBT) → Measures interactivity
- Server Response Time → Backend latency analysis
Extract these values by running:
cat report.json | jq '.audits["first-contentful-paint"].numericValue'
Automating Lighthouse Audit with Selenium:
By default, Lighthouse launches a new Chrome instance. However, if we already have a Selenium WebDriver session running, we can run Lighthouse on the same browser instance via CDP remote debugging.
Step 1: Enable Remote debugging in Selenium
We configure Selenium to launch the browser with remote debugging enabled.
public class CustomBrowserOptions extends BrowserOptions {
public CustomBrowserOptions() {
super(false, "--remote-debugging-port=9222");
}
}
Step 2: Run Lighthouse on Selenium Browser Instance
Now, Lighthouse can analyse the existing Selenium browser without launching a new one.
package com.anji.sel.light;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
@Slf4j
public class LightHouse {
public static void analyse(String url, String channel) throws Exception {
ProcessBuilder processBuilder =
new ProcessBuilder("zsh", "-c", "./run_lighthouse.sh" + " " + url + " " + channel);
processBuilder.redirectErrorStream(true);
processBuilder.environment().put("PATH", getPath());
Process process = processBuilder.start();
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
log.info(line);
}
}
process.waitFor();
String report = new String(Files.readAllBytes(Paths.get("target/lighthouse-report.json")));
JSONObject jsonReport = new JSONObject(report);
double performanceScore =
jsonReport.getJSONObject("categories").getJSONObject("performance").getDouble("score");
log.info("Performance Score: " + performanceScore);
}
private static String getPath() {
String homeDirectory = System.getProperty("user.home");
String nodePath = homeDirectory + "/.nvm/versions/node/v21.5.0/bin";
String currentPath = System.getenv("PATH");
return nodePath + ":" + currentPath;
}
}
The shell script (run_lighthouse.sh
) looks like this
#!/bin/zsh
# Check if Lighthouse is installed
if ! command -v lighthouse &> /dev/null
then
echo "❌ Error: Lighthouse is not installed. Install it using: npm install -g lighthouse"
exit 1
fi
# Validate input URL
if [ -z "$1" ]; then
echo "❌ Error: No URL provided. Usage: ./run_lighthouse.sh <URL> <CHANNEL>"
exit 1
fi
# Assign variables
URL=$1
CHANNEL=$2
echo "📊 Running Lighthouse audit on $URL using Chrome on port 9222..."
lighthouse "$URL" --preset="$CHANNEL" --port=9222 --output=json --output-path=target/lighthouse-report.json
echo "✅ Lighthouse audit completed. Results saved to target/lighthouse-report.json"
Step 3: Execute Lighthouse from Selenium Test
package com.anji.ui;
import com.anji.sel.PerformanceClient;
import com.anji.sel.annotation.CustomBrowserOptions;
import com.anji.sel.annotation.SeleniumWebDriver;
import com.anji.sel.light.LightHouse;
import com.anji.sel.pojo.metric.MetricDto;
import com.anji.sel.util.FilterUtil;
import com.anji.sel.util.TransformUtil;
import java.io.IOException;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
@SeleniumWebDriver(options = CustomBrowserOptions.class)
@Slf4j
class LightHouseMetricsTest {
private static final String HOST_BASE_URL = "https://automationteststore.com/";
private PerformanceClient performanceClient;
private WebDriver driver;
@BeforeEach
void setup(WebDriver driver) {
this.driver = driver;
performanceClient = new PerformanceClient();
}
@Test
void lightHouseTest() throws Exception {
this.driver.get(HOST_BASE_URL);
LightHouse.analyse(HOST_BASE_URL, "desktop");
Thread.sleep(10000);
}
@AfterEach
void flushMetrics() throws IOException {
List<MetricDto> metricDtoList = TransformUtil.transformFromLighthouse();
log.info(metricDtoList.toString());
performanceClient.sendMetrics(FilterUtil.filter(metricDtoList));
}
}
This ensures Selenium WebDriver and Lighthouse share the same Chrome session.
Extracting Real-Time Performance Metrics Using Selenium CDP
Lighthouse provides page-level metrics, but Selenium 4’s CDP allows us to capture real-time browser performance data.
Step1: Enable CDP In Selenium
package com.anji.ui;
import com.anji.sel.PerformanceClient;
import com.anji.sel.annotation.SeleniumWebDriver;
import com.anji.sel.pojo.metric.MetricDto;
import com.anji.sel.util.FilterUtil;
import com.anji.sel.util.TransformUtil;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.devtools.DevTools;
import org.openqa.selenium.devtools.v133.performance.Performance;
import org.openqa.selenium.devtools.v133.performance.model.Metric;
@SeleniumWebDriver
class UIPerformanceTest {
private static final String HOST_BASE_URL = "https://automationteststore.com/";
private PerformanceClient performanceClient;
private WebDriver driver;
private List<Metric> metricList;
@BeforeEach
void setup(WebDriver driver) {
this.driver = driver;
performanceClient = new PerformanceClient();
}
@Test
void networkTest() {
try (DevTools devTools = ((ChromeDriver) driver).getDevTools()) {
devTools.createSession();
devTools.send(Performance.enable(Optional.empty()));
driver.get(HOST_BASE_URL);
metricList = devTools.send(Performance.getMetrics());
}
}
@AfterEach
void flushMetrics() {
List<MetricDto> metricDtoList = TransformUtil.transform(HOST_BASE_URL, metricList);
performanceClient.sendMetrics(FilterUtil.filter(metricDtoList));
}
}
Step2: Extract Key CDP Metrics
- Script Duration → Measures JavaScript execution time
- JS Heap Used Size → Tracks JavaScript memory usage
- DOMContentLoaded → Identifies page readiness
- JS Heap Total Size → Tracks total memory occupied by JavaScript Objects
Persisting above metrics to visualise them on Grafana Dashboard:
I developed a service that accepts performance metrics via API, stores them in a database, and displays them in Grafana dashboards. This setup enables:
✅ Real-time performance monitoring with visual insights
✅ Historical performance trend analysis
✅ Threshold-based alerting for performance degradations
Setting Up Grafana Alerts:
By defining performance thresholds in Grafana, we can trigger alerts if performance degrades beyond acceptable limits. Whenever tests run in CI/CD pipelines, Grafana will automatically fetch the latest metrics from the database and alert teams in case of any degradation.
Example Metric Images on Grafana:
Source Code:
Metric Service and Selenium UI tests are available as separate maven multi modules in this project. Run the service using docker compose before running the tests in your local.
Note: You may need to tweak docker compose based on underlying OS.
Happy learning and testing :)