From 3d07e0b6a6f0e32b4bd47e6750e0211a257c0302 Mon Sep 17 00:00:00 2001 From: Mohammad Saddam Hossain Date: Wed, 11 Nov 2026 08:12:52 +0530 Subject: [PATCH] fix(report): stop tag pills throwing in single-test exports Exported files kept the tag pills' filterByTag handler, which only exists in the full report, so clicking a pill threw a ReferenceError. Pills now just stop propagation, so they also no longer collapse the detail row. Also drops a test that only wrote a sample report into target/, and documents the "Download this test" button. Follow-up to #248. Co-Authored-By: Claude Opus 5.5 --- docs-site/docs/reporting/html-report.md | 10 +++ src/main/resources/report/report.js | 14 +++ .../reporting/HtmlReportGeneratorTest.java | 88 ++----------------- 3 files changed, 29 insertions(+), 83 deletions(-) diff --git a/docs-site/docs/reporting/html-report.md b/docs-site/docs/reporting/html-report.md index 5fd9f24..96ce397 100644 --- a/docs-site/docs/reporting/html-report.md +++ b/docs-site/docs/reporting/html-report.md @@ -202,6 +202,16 @@ All screenshots are Base64-encoded and embedded inline. The report is a single f No images folder, no asset references, no server needed. +### Share a single test + +The full report can run to several megabytes. To share one test, expand it and click +**Download this test** in its detail panel. You get a standalone `.html` file with +that test's status, error, stack trace, steps and screenshots. It opens offline in any +browser, keeps the report's current light or dark theme, and is small enough to attach +to a Jira ticket, GitHub issue or Slack thread. + +The file is built in your browser when you click, so it adds nothing to the report size. + --- ## Step timeline diff --git a/src/main/resources/report/report.js b/src/main/resources/report/report.js index 12915e8..91ea442 100644 --- a/src/main/resources/report/report.js +++ b/src/main/resources/report/report.js @@ -455,6 +455,17 @@ function escapeHtmlForExport(str) { .replace(/'/g, '''); } +// Tag pills filter the full report; the standalone export has nothing to filter, +// so neutralise their handlers instead of shipping calls to undefined functions. +// They still stop propagation, so clicking a pill doesn't collapse the detail row. +function stripReportOnlyHandlers(root) { + root.querySelectorAll('[onclick^="filterByTag"]').forEach(function(el) { + el.setAttribute('onclick', 'event.stopPropagation()'); + el.removeAttribute('title'); + el.style.cursor = 'default'; + }); +} + function buildStandaloneTestHtml(testId, testRow, panel, activeTheme) { const styleTag = document.querySelector('style'); const css = styleTag ? styleTag.textContent : ''; @@ -484,6 +495,7 @@ function buildStandaloneTestHtml(testId, testRow, panel, activeTheme) { if (icon) { icon.classList.add('open'); } + stripReportOnlyHandlers(testRowClone); testRowHtml = testRowClone.outerHTML; } @@ -498,10 +510,12 @@ function buildStandaloneTestHtml(testId, testRow, panel, activeTheme) { if (panelClone) { panelClone.querySelectorAll('.copy-failure-btn, .download-test-btn').forEach(function(b) { b.remove(); }); } + stripReportOnlyHandlers(detailRowClone); detailRowHtml = detailRowClone.outerHTML; } else if (panel) { const panelClone = panel.cloneNode(true); panelClone.querySelectorAll('.copy-failure-btn, .download-test-btn').forEach(function(b) { b.remove(); }); + stripReportOnlyHandlers(panelClone); detailRowHtml = '' + '
' + panelClone.innerHTML + '
' + ''; diff --git a/src/test/java/com/seleniumboot/reporting/HtmlReportGeneratorTest.java b/src/test/java/com/seleniumboot/reporting/HtmlReportGeneratorTest.java index c6db472..e3c7dbe 100644 --- a/src/test/java/com/seleniumboot/reporting/HtmlReportGeneratorTest.java +++ b/src/test/java/com/seleniumboot/reporting/HtmlReportGeneratorTest.java @@ -738,6 +738,11 @@ public void downloadTestScript_containsStandaloneExportLogic() throws Exception // Self-contained lightbox and offline script assertTrue(reportHtml.contains("function openLightbox("), "offline script must define lightbox support"); assertTrue(reportHtml.contains("function toggleDark("), "offline script must define theme toggle"); + + // Tag pills call filterByTag, which the standalone file doesn't define + assertTrue(reportHtml.contains("function stripReportOnlyHandlers("), "export must neutralise report-only handlers"); + assertTrue(reportHtml.contains("stripReportOnlyHandlers(testRowClone)"), "test row clone must be stripped"); + assertTrue(reportHtml.contains("stripReportOnlyHandlers(detailRowClone)"), "detail row clone must be stripped"); } @Test @@ -808,87 +813,4 @@ public void downloadTest_standaloneExport_isFullySelfContainedAndHasNoExternalFe assertTrue(js.contains("new Blob("), "Export must generate file in-memory using client-side Blob"); assertTrue(js.contains("buildStandaloneTestHtml"), "Export must include standalone HTML builder"); } - - @Test - public void generate_writesSampleReportToTargetForManualVerification() throws Exception { - java.nio.file.Path targetDir = java.nio.file.Paths.get("target"); - if (!java.nio.file.Files.exists(targetDir)) { - java.nio.file.Files.createDirectories(targetDir); - } - String pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="; - java.nio.file.Path sampleScreenshot = targetDir.resolve("sample-fail.png"); - java.nio.file.Files.write(sampleScreenshot, java.util.Base64.getDecoder().decode(pngBase64)); - - String json = String.format(""" - { - "totalTests": 3, - "passedTests": 1, - "failedTests": 1, - "skippedTests": 1, - "passRate": 33.3, - "suiteDurationMs": 5200, - "tests": [ - { - "testId": "com.example.cart.CheckoutTest.successfulCheckout", - "testClassName": "CheckoutTest", - "status": "PASSED", - "browser": "chrome", - "tags": ["smoke", "checkout"], - "testLogicMs": 1400, - "totalMs": 2100, - "description": "Verify checkout completes with valid credentials", - "steps": [ - {"name": "Open login page", "status": "PASS", "offsetMs": 200}, - {"name": "Submit credentials", "status": "PASS", "offsetMs": 800}, - {"name": "Place order", "status": "PASS", "offsetMs": 1400} - ] - }, - { - "testId": "com.example.auth.LoginTest.invalidPasswordShowsError", - "testClassName": "LoginTest", - "status": "FAILED", - "browser": "firefox", - "tags": ["regression", "auth"], - "testLogicMs": 850, - "totalMs": 1600, - "description": "Verify alert banner is displayed when password is wrong", - "screenshotPath": "%s", - "errorMessage": "Expected error banner not visible after 5000ms", - "stackTrace": "org.openqa.selenium.TimeoutException: Expected condition failed: waiting for visibility of element located by By.id: alert-banner\\n\\tat com.example.auth.LoginTest.invalidPasswordShowsError(LoginTest.java:42)", - "failureCategory": "TIMEOUT", - "failureType": "PRODUCT_BUG", - "suggestedAction": "Check login service latency or increase banner wait timeout", - "failureDetail": { - "failingLocator": "By.id: alert-banner", - "applicationFrame": "LoginTest.java:42", - "lastSuccessfulStep": "Enter password", - "pageUrl": "https://example.com/login", - "pageTitle": "Login - Example App", - "consoleErrors": ["401 Unauthorized: Invalid credentials"] - }, - "steps": [ - {"name": "Navigate to /login", "status": "PASS", "offsetMs": 150}, - {"name": "Enter wrong password", "status": "PASS", "offsetMs": 400}, - {"name": "Wait for banner", "status": "FAIL", "offsetMs": 850, "screenshotBase64": "%s"} - ] - }, - { - "testId": "com.example.auth.LoginTest.ssoLoginBypassed", - "testClassName": "LoginTest", - "status": "SKIPPED", - "browser": "chrome", - "tags": ["sso"], - "testLogicMs": 0, - "totalMs": 10, - "errorMessage": "Skipped: SSO identity provider is temporarily disabled in test environment" - } - ] - } - """, sampleScreenshot.toString().replace("\\", "\\\\"), pngBase64); - - String reportHtml = HtmlReportGenerator.buildHtml(new ObjectMapper().readTree(json)); - java.nio.file.Path sampleReportPath = targetDir.resolve("sample-verification-report.html"); - java.nio.file.Files.writeString(sampleReportPath, reportHtml); - assertTrue(java.nio.file.Files.exists(sampleReportPath), "Sample verification report must be written to target"); - } }