Writing Own Custom Reporter with custom execution of Playwright Script
Custom execution of Playwright script can also be done one step at a time using async handler of express by passing one step at a time in a loop. This allows to use a completely independent custom reporter. Below is an example:
CustomReporter.js
class CustomReporter {
constructor() {
this.results = [];
}
logStep(stepIndex, command, status, duration, errorMessage = null) {
const stepResult = {
stepNumber: stepIndex + 1,
stepCommand: command,
status,
duration: `${duration}ms`,
timestamp: new Date().toISOString(),
errorMessage: errorMessage,
};
this.results.push(stepResult);
console.log(`[Step ${stepIndex + 1}] ${status.toUpperCase()}: ${command} (Duration: ${duration}ms)`);
}
saveReport() {
// Save or send the report as needed
console.log("Final Test Report:", JSON.stringify(this.results, null, 2));
}
}
export default CustomReporter;
Â
playwrightCustomExecution.js
import asyncHandler from "express-async-handler";
import {chromium} from "playwright";
import CustomReporter from "./customReporter.js";
// Custom Reporter
// @route POST /api/pw/exe
// @access Public
const execution = asyncHandler(async (req, res) => {
const {playwright_test_steps} = req.body;
if (playwright_test_steps) {
const reporter = new CustomReporter(); // Initialize reporter
let browser;
try {
(async () => {
browser = await chromium.launch({
headless: false,
});
const context = await browser.newContext();
const page = await context.newPage();
for (let i = 0; i < playwright_test_steps.length; i++) {
const command = playwright_test_steps[i];
const startTime = Date.now(); // Start time for execution
try {
await eval(command.replace("page", "page").replace("await ", ""));
const duration = Date.now() - startTime; // Calculate duration
reporter.logStep(i, command, "passed", duration);
} catch (error) {
const duration = Date.now() - startTime; // Calculate duration
reporter.logStep(i, command, "failed", duration, error.message);
await page.screenshot({path: `error-${i}.png`});
reporter.saveReport();
throw new Error(`Error executing test step ${i}: ${error.message}`);
}
}
await context.close();
await browser.close();
// Save the final report
reporter.saveReport();
res.json({
message: "Test steps executed successfully",
report: reporter.results,
});
})();
} catch (error) {
console.log("Error executing test steps", error.message);
if (browser) await browser.close();
// res.status(500).json({
// message: "Error executing test steps",
// error: error.message,
// report: reporter.results,
// });
}
} else {
res.status(401);
throw new Error("Invalid Script");
}
});
export {execution};
Â
testResults.json
[
{
"stepNumber": 1,
"stepCommand": "await page.goto('https://sandbox.mabl.com/');",
"status": "passed",
"duration": "4231ms",
"timestamp": "2024-08-21T15:08:25.121Z",
"errorMessage": null
},
{
"stepNumber": 2,
"stepCommand": "await page.getByRole('button', { name: 'alert' }).click();",
"status": "passed",
"duration": "88ms",
"timestamp": "2024-08-21T15:08:25.210Z",
"errorMessage": null
},
{
"stepNumber": 3,
"stepCommand": "page.once('dialog', (dialog) => { console.log(`Dialog message: ${dialog.message()}`); dialog.dismiss().catch(() => {}); });",
"status": "passed",
"duration": "1ms",
"timestamp": "2024-08-21T15:08:25.211Z",
"errorMessage": null
},
{
"stepNumber": 4,
"stepCommand": "await page.getByRole('button', { name: 'open alert' }).click();",
"status": "passed",
"duration": "55ms",
"timestamp": "2024-08-21T15:08:25.270Z",
"errorMessage": null
},
{
"stepNumber": 5,
"stepCommand": "await page.getByRole('link', { name: 'mabl' }).click();",
"status": "passed",
"duration": "198ms",
"timestamp": "2024-08-21T15:08:25.468Z",
"errorMessage": null
},
{
"stepNumber": 6,
"stepCommand": "await page.getByRole('button', { name: 'dropdowns' }).click();",
"status": "passed",
"duration": "140ms",
"timestamp": "2024-08-21T15:08:25.608Z",
"errorMessage": null
},
{
"stepNumber": 7,
"stepCommand": "await page.getByLabel('Dropdown select').locator('div').first().click();",
"status": "passed",
"duration": "160ms",
"timestamp": "2024-08-21T15:08:25.768Z",
"errorMessage": null
},
{
"stepNumber": 8,
"stepCommand": "await page.getByLabel('george costanza').click();",
"status": "passed",
"duration": "95ms",
"timestamp": "2024-08-21T15:08:25.864Z",
"errorMessage": null
},
{
"stepNumber": 9,
"stepCommand": "await page.getByRole('link', { name: 'mabl' }).click();",
"status": "passed",
"duration": "69ms",
"timestamp": "2024-08-21T15:08:25.934Z",
"errorMessage": null
}
]
Â
Advantages:
Full Flexibility: Complete control over every aspect of the report, from data collection to formatting, allowing you to meet highly specific requirements.
Tailored Solutions: You can create a reporting solution that perfectly aligns with your organization's needs, preferences, and standards.
Unique Features: Ability to add unique features, visualizations, or integrations that aren't available in built-in or third-party options.
Disadvantages:
Time-Consuming: Developing a custom reporting solution from scratch can be labor-intensive and time-consuming.
Maintenance Overhead: You'll be responsible for maintaining and updating the custom reporting tool, which can add to the project's technical debt.
Requires Expertise: Building a custom reporting API requires in-depth knowledge of both Playwright and general software development practices, potentially increasing the complexity for less experienced teams.