pdf-reportify
Version:
A Node.js CLI tool to generate PDF reports from test screenshots.
137 lines (111 loc) • 5.24 kB
JavaScript
const { PDFDocument } = require('pdf-lib');
const { promises: fs } = require('fs');
const path = require('path');
const { glob } = require('glob');
/**
* Creates a PDF from a series of images.
* @param {string[]} imagePaths - An array of paths to the images.
* @returns {Promise<Uint8Array>} The PDF content as bytes.
*/
async function createPdfFromImages(imagePaths) {
const pdfDoc = await PDFDocument.create();
for (const imagePath of imagePaths) {
const imageBytes = await fs.readFile(imagePath);
let image;
if (imagePath.endsWith('.png')) {
image = await pdfDoc.embedPng(imageBytes);
} else if (imagePath.endsWith('.jpg') || imagePath.endsWith('.jpeg')) {
image = await pdfDoc.embedJpg(imageBytes);
} else {
console.warn(`Unsupported image format: ${imagePath}`);
continue;
}
const page = pdfDoc.addPage();
const { width, height } = page.getSize();
// Define margens para evitar que as imagens saiam da página
const margin = 20;
const availableWidth = width - (margin * 2);
const availableHeight = height - (margin * 2);
// Redimensiona a imagem para ocupar 100% da largura disponível, mantendo a proporção
const imageDims = image.scaleToFit(availableWidth, availableHeight);
page.drawImage(image, {
x: margin,
y: page.getHeight() - imageDims.height - margin,
width: imageDims.width,
height: imageDims.height,
});
}
return pdfDoc.save();
}
/**
* Generates PDF files for each test case in a screenshots directory.
* @param {string} screenshotsDir - The path to the root screenshots directory.
* @param {{ verbose?: boolean }} options - Optional settings.
*/
async function generateTestReports(screenshotsDir, options = {}) {
const verbose = Boolean(options.verbose);
const vLog = (...args) => { if (verbose) console.log(...args); };
try {
// List test directories (like login.cy.js, signup.cy.js, etc.)
const dirEntries = await fs.readdir(screenshotsDir, { withFileTypes: true });
const testDirs = dirEntries
.filter((entry) => entry.isDirectory())
.map((entry) => path.join(screenshotsDir, entry.name));
vLog(`Found ${testDirs.length} test directories in: ${screenshotsDir}`);
for (const testDir of testDirs) {
const testName = path.basename(path.resolve(testDir));
vLog(`Processing test directory: ${testName}`);
// List all directories within each test directory
const testDirEntries = await fs.readdir(testDir, { withFileTypes: true });
const potentialTestCaseDirs = testDirEntries
.filter((entry) => entry.isDirectory())
.map((entry) => path.join(testDir, entry.name));
vLog(`Found ${potentialTestCaseDirs.length} potential test case directories in ${testName}`);
for (const testCaseDir of potentialTestCaseDirs) {
const testCaseName = path.basename(path.resolve(testCaseDir));
// Check if this directory contains any PNG files
const testCaseEntries = await fs.readdir(testCaseDir, { withFileTypes: true });
const hasPngFiles = testCaseEntries.some(entry =>
!entry.isDirectory() && entry.name.toLowerCase().endsWith('.png')
);
if (!hasPngFiles) {
vLog(`Directory '${testCaseName}' does not contain PNG files, skipping...`);
continue;
}
vLog(`Directory '${testCaseName}' contains PNG files, processing as test case...`);
// Normalize to POSIX path for glob on Windows
const posixDir = testCaseDir.replace(/\\/g, '/');
const imagePattern = `${posixDir}/**/*.{png,jpg,jpeg,PNG,JPG,JPEG}`;
const imagePaths = await glob(imagePattern, { nocase: true, windowsPathsNoEscape: true });
vLog(`Test case '${testCaseName}' pattern: ${imagePattern}`);
vLog(`Found ${imagePaths.length} images in '${testCaseName}'`);
if (imagePaths.length === 0) {
console.warn(`No screenshots found for test case: ${testCaseName}`);
continue;
}
// Order images by file creation time (birthtime)
const filesWithStats = await Promise.all(
imagePaths.map(async (imagePath) => ({
imagePath,
stat: await fs.stat(imagePath),
}))
);
filesWithStats.sort((a, b) => a.stat.birthtimeMs - b.stat.birthtimeMs);
const orderedImagePaths = filesWithStats.map((f) => f.imagePath);
vLog(`Ordering by creation time (oldest first):`, orderedImagePaths.map(p => path.basename(p)).join(', '));
console.log(`Generating report for test case: ${testCaseName}`);
const pdfBytes = await createPdfFromImages(orderedImagePaths);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
const outputFilePath = path.join(testCaseDir, `${testCaseName}_report_${timestamp}.pdf`);
await fs.writeFile(outputFilePath, pdfBytes);
console.log(`Report saved to: ${outputFilePath}`);
}
}
console.log('\nReport generation complete!');
} catch (error) {
console.error('An error occurred during report generation:', error);
}
}
module.exports = {
generateTestReports,
};