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.