mirror of
https://github.com/DeNNiiInc/Web-Page-Performance-Test.git
synced 2026-04-18 20:16:00 +00:00
Implement Core Performance Testing Engine (Phase 1 & 2)
- Added Lighthouse and Chrome Launcher dependencies - Created lib/runner.js test runner service - Implemented filesystem storage for test results - Added API endpoints: POST /api/run-test and GET /api/history
This commit is contained in:
113
lib/runner.js
Normal file
113
lib/runner.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
const lighthouse = require('lighthouse');
|
||||||
|
const chromeLauncher = require('chrome-launcher');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a performance test using Lighthouse
|
||||||
|
* @param {string} url - The URL to test
|
||||||
|
* @param {object} options - Configuration options (mobile vs desktop, etc.)
|
||||||
|
* @returns {Promise<object>} - Test results
|
||||||
|
*/
|
||||||
|
async function runTest(url, options = {}) {
|
||||||
|
const isMobile = options.isMobile ?? true; // Default to mobile
|
||||||
|
const reportDir = path.join(__dirname, '..', 'reports');
|
||||||
|
|
||||||
|
// Ensure reports directory exists
|
||||||
|
if (!fs.existsSync(reportDir)) {
|
||||||
|
fs.mkdirSync(reportDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch Chrome
|
||||||
|
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
|
||||||
|
|
||||||
|
// Lighthouse Config
|
||||||
|
const port = chrome.port;
|
||||||
|
const config = {
|
||||||
|
extends: 'lighthouse:default',
|
||||||
|
settings: {
|
||||||
|
onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'],
|
||||||
|
formFactor: isMobile ? 'mobile' : 'desktop',
|
||||||
|
screenEmulation: isMobile
|
||||||
|
? undefined // Uses default mobile emulation
|
||||||
|
: { mobile: false, width: 1350, height: 940, deviceScaleFactor: 1, disabled: false },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const runnerOptions = {
|
||||||
|
logLevel: 'info',
|
||||||
|
output: 'html',
|
||||||
|
onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'],
|
||||||
|
port: port,
|
||||||
|
};
|
||||||
|
|
||||||
|
const testId = uuidv4();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const runnerResult = await lighthouse(url, runnerOptions, config);
|
||||||
|
|
||||||
|
// Save HTML Report
|
||||||
|
const reportHtml = runnerResult.report;
|
||||||
|
const reportPath = path.join(reportDir, `${testId}.html`);
|
||||||
|
fs.writeFileSync(reportPath, reportHtml);
|
||||||
|
|
||||||
|
// Prepare JSON Summary
|
||||||
|
const lhr = runnerResult.lhr;
|
||||||
|
const summary = {
|
||||||
|
id: testId,
|
||||||
|
url: lhr.finalUrl,
|
||||||
|
timestamp: lhr.fetchTime,
|
||||||
|
scores: {
|
||||||
|
performance: lhr.categories.performance.score * 100,
|
||||||
|
accessibility: lhr.categories.accessibility.score * 100,
|
||||||
|
bestPractices: lhr.categories['best-practices'].score * 100,
|
||||||
|
seo: lhr.categories.seo.score * 100,
|
||||||
|
},
|
||||||
|
metrics: {
|
||||||
|
lcp: lhr.audits['largest-contentful-paint'].numericValue,
|
||||||
|
cls: lhr.audits['cumulative-layout-shift'].numericValue,
|
||||||
|
tbt: lhr.audits['total-blocking-time'].numericValue,
|
||||||
|
},
|
||||||
|
userAgent: lhr.userAgent,
|
||||||
|
isMobile: isMobile
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save JSON Summary
|
||||||
|
const jsonPath = path.join(reportDir, `${testId}.json`);
|
||||||
|
fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2));
|
||||||
|
|
||||||
|
await chrome.kill();
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (chrome) await chrome.kill();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of all test results
|
||||||
|
*/
|
||||||
|
function getHistory() {
|
||||||
|
const reportDir = path.join(__dirname, '..', 'reports');
|
||||||
|
if (!fs.existsSync(reportDir)) return [];
|
||||||
|
|
||||||
|
const files = fs.readdirSync(reportDir).filter(f => f.endsWith('.json'));
|
||||||
|
const history = files.map(file => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(path.join(reportDir, file), 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
// Sort by newest first
|
||||||
|
return history.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
runTest,
|
||||||
|
getHistory
|
||||||
|
};
|
||||||
3758
package-lock.json
generated
Normal file
3758
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,10 @@
|
|||||||
"author": "Beyond Cloud Technology",
|
"author": "Beyond Cloud Technology",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2"
|
"chrome-launcher": "^1.2.1",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"lighthouse": "^13.0.1",
|
||||||
|
"uuid": "^13.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.1"
|
"nodemon": "^3.0.1"
|
||||||
|
|||||||
27
server.js
27
server.js
@@ -2,14 +2,17 @@ const express = require("express");
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const { exec } = require("child_process");
|
const { exec } = require("child_process");
|
||||||
|
const runner = require("./lib/runner");
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
// Serve static files
|
// Middleware
|
||||||
|
app.use(express.json());
|
||||||
app.use(express.static(__dirname));
|
app.use(express.static(__dirname));
|
||||||
|
app.use('/reports', express.static(path.join(__dirname, 'reports')));
|
||||||
|
|
||||||
// API endpoint to get Git commit info
|
// API Endpoint: Git Info
|
||||||
app.get("/api/git-info", (req, res) => {
|
app.get("/api/git-info", (req, res) => {
|
||||||
exec('git log -1 --format="%H|%cr"', (error, stdout, stderr) => {
|
exec('git log -1 --format="%H|%cr"', (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -30,6 +33,26 @@ app.get("/api/git-info", (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// API Endpoint: Run Test
|
||||||
|
app.post("/api/run-test", async (req, res) => {
|
||||||
|
const { url, isMobile } = req.body;
|
||||||
|
if (!url) return res.status(400).json({ error: "URL is required" });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await runner.runTest(url, { isMobile });
|
||||||
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Test failed:", error);
|
||||||
|
res.status(500).json({ error: "Test failed", details: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// API Endpoint: History
|
||||||
|
app.get("/api/history", (req, res) => {
|
||||||
|
const history = runner.getHistory();
|
||||||
|
res.json(history);
|
||||||
|
});
|
||||||
|
|
||||||
// Serve index.html for all other routes
|
// Serve index.html for all other routes
|
||||||
app.get("*", (req, res) => {
|
app.get("*", (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, "index.html"));
|
res.sendFile(path.join(__dirname, "index.html"));
|
||||||
|
|||||||
Reference in New Issue
Block a user