From 955f13659447f536258b144f96f31f0277cf2a99 Mon Sep 17 00:00:00 2001
From: Pushpak Jaiswal <149317014+PUSHPAK-JAISWAL@users.noreply.github.com>
Date: Sat, 31 Oct 2026 11:43:48 +0530
Subject: [PATCH 1/3] feat(report): persist status and tag filters across
reloads (#215)
---
src/main/resources/report/report.js | 75 +++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/src/main/resources/report/report.js b/src/main/resources/report/report.js
index 81947ec..f6bcd85 100644
--- a/src/main/resources/report/report.js
+++ b/src/main/resources/report/report.js
@@ -118,12 +118,22 @@ function filterByTag(tagName, event) {
pill.style.display = 'none';
}
}
+ try {
+ if (activeTagFilter) {
+ localStorage.setItem('sb-tag-filter', tagName);
+ } else {
+ localStorage.removeItem('sb-tag-filter');
+ }
+ } catch (e) {}
applySearch();
}
function clearTagFilter() {
activeTagFilter = '';
const pill = document.getElementById('activeTagPill');
if (pill) pill.style.display = 'none';
+ try {
+ localStorage.removeItem('sb-tag-filter');
+ } catch (e) {}
applySearch();
}
@@ -133,6 +143,13 @@ function setFilter(status, btn) {
activeFilter = status;
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
+ try {
+ if (status === 'ALL') {
+ localStorage.removeItem('sb-status-filter');
+ } else {
+ localStorage.setItem('sb-status-filter', status);
+ }
+ } catch (e) {}
applySearch();
}
function applySearch() {
@@ -200,3 +217,61 @@ function copyFailure(btn, event) {
fallback();
}
}
+
+// ── Filter restoration on load ──
+(function() {
+ try {
+ // Restore status filter
+ const savedStatus = localStorage.getItem('sb-status-filter');
+ if (savedStatus && ['PASSED', 'FAILED', 'SKIPPED', 'FLAKY'].includes(savedStatus)) {
+ const btn = document.querySelector(".filter-btn[onclick*=\"'" + savedStatus + "'\"]");
+ if (btn) {
+ activeFilter = savedStatus;
+ document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+ }
+ } else if (savedStatus === 'ALL') {
+ const btn = document.querySelector(".filter-btn[onclick*=\"'ALL'\"]");
+ if (btn) {
+ activeFilter = 'ALL';
+ document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+ }
+ }
+
+ // Restore tag filter
+ const savedTag = localStorage.getItem('sb-tag-filter');
+ if (savedTag) {
+ const tagToMatch = savedTag.toLowerCase().trim();
+ if (tagToMatch) {
+ const rows = document.querySelectorAll('#testTable tbody tr.group-member');
+ let tagFound = false;
+ for (let i = 0; i < rows.length; i++) {
+ const rawTags = rows[i].getAttribute('data-tags');
+ if (rawTags) {
+ const tags = rawTags.split(',').map(t => t.trim().toLowerCase());
+ if (tags.includes(tagToMatch)) {
+ tagFound = true;
+ break;
+ }
+ }
+ }
+ if (tagFound) {
+ activeTagFilter = tagToMatch;
+ const pill = document.getElementById('activeTagPill');
+ const nameSpan = document.getElementById('activeTagName');
+ if (pill && nameSpan) {
+ nameSpan.textContent = 'Tag: ' + savedTag;
+ pill.style.display = 'inline-flex';
+ }
+ }
+ }
+ }
+
+ // Apply filters to table if any restored
+ if (activeFilter !== 'ALL' || activeTagFilter) {
+ applySearch();
+ }
+ } catch (e) {}
+})();
+
From 8e126dbebd0318ed0eec766ddf6320ca774dabe6 Mon Sep 17 00:00:00 2001
From: Pushpak Jaiswal <149317014+PUSHPAK-JAISWAL@users.noreply.github.com>
Date: Sat, 31 Oct 2026 11:44:01 +0530
Subject: [PATCH 2/3] test(report): verify status and tag filter persistence
across reloads (#215)
---
.../reporting/HtmlReportGeneratorTest.java | 110 ++++++++++++++++++
.../unit/ReportTabBehaviourTest.java | 8 ++
2 files changed, 118 insertions(+)
diff --git a/src/test/java/com/seleniumboot/reporting/HtmlReportGeneratorTest.java b/src/test/java/com/seleniumboot/reporting/HtmlReportGeneratorTest.java
index 16a3d5d..25e6606 100644
--- a/src/test/java/com/seleniumboot/reporting/HtmlReportGeneratorTest.java
+++ b/src/test/java/com/seleniumboot/reporting/HtmlReportGeneratorTest.java
@@ -300,4 +300,114 @@ public void copyFailureButton_onlyOnFailedTestsWithErrorDetail() throws Exceptio
assertTrue(reportHtml.contains("function copyFailure("));
assertTrue(reportHtml.contains("document.execCommand('copy')"), "file:// fallback present");
}
+
+ @Test
+ public void buildHtml_statusFilterPersistence_persistsAndRestoresFilter() throws Exception {
+ String reportHtml = html(1, 0);
+
+ // 1. Canonical storage key
+ assertTrue(reportHtml.contains("sb-status-filter"),
+ "Report must use the canonical 'sb-status-filter' storage key");
+
+ // 2. setFilter persistence and removal on 'ALL'
+ assertTrue(reportHtml.contains("localStorage.setItem('sb-status-filter'"),
+ "setFilter must persist non-ALL status filter to localStorage");
+ assertTrue(reportHtml.contains("localStorage.removeItem('sb-status-filter'"),
+ "setFilter must remove stored status filter when 'ALL' is selected");
+
+ // 3. Status filter restoration on page load
+ assertTrue(reportHtml.contains("localStorage.getItem('sb-status-filter'"),
+ "Report initialization must retrieve persisted status from localStorage");
+ assertTrue(reportHtml.contains("['PASSED', 'FAILED', 'SKIPPED', 'FLAKY']"),
+ "Status restoration must validate against allowed status values");
+ assertTrue(reportHtml.contains(".filter-btn"),
+ "Status restoration must update the active class on filter buttons");
+ assertTrue(reportHtml.contains("activeFilter"),
+ "Status restoration must set activeFilter variable");
+ }
+
+ @Test
+ public void buildHtml_tagFilterPersistence_persistsAndClearsFilter() throws Exception {
+ String reportHtml = html(1, 0);
+
+ // 1. Canonical storage key
+ assertTrue(reportHtml.contains("sb-tag-filter"),
+ "Report must use the canonical 'sb-tag-filter' storage key");
+
+ // 2. filterByTag persistence & clearTagFilter removal
+ assertTrue(reportHtml.contains("localStorage.setItem('sb-tag-filter'"),
+ "filterByTag must persist selected tag to localStorage");
+ assertTrue(reportHtml.contains("localStorage.removeItem('sb-tag-filter'"),
+ "clearTagFilter must remove stored tag filter from localStorage");
+
+ // 3. Tag filter restoration on page load
+ assertTrue(reportHtml.contains("localStorage.getItem('sb-tag-filter'"),
+ "Report initialization must retrieve persisted tag from localStorage");
+ assertTrue(reportHtml.contains("activeTagPill"),
+ "Tag restoration must manage activeTagPill visibility");
+ assertTrue(reportHtml.contains("activeTagName"),
+ "Tag restoration must update activeTagName display");
+ }
+
+ @Test
+ public void buildHtml_tagFilterRestoration_guardsAgainstMissingTags() throws Exception {
+ String reportHtml = html(1, 0);
+
+ // Verify missing tag validation inspects table rows before applying filter
+ assertTrue(reportHtml.contains("#testTable tbody tr.group-member"),
+ "Tag restoration must query test table member rows to locate matching tags");
+ assertTrue(reportHtml.contains("data-tags"),
+ "Tag restoration must inspect data-tags on table rows before activating filter");
+ assertTrue(reportHtml.contains("tagFound"),
+ "Tag restoration must track whether the stored tag actually exists in the report");
+ }
+
+ @Test
+ public void buildHtml_filterStorageAccess_isGuardedByTryCatch() throws Exception {
+ String reportHtml = html(1, 0);
+
+ int scriptStart = reportHtml.lastIndexOf("");
+ assertTrue(scriptStart > 0 && scriptEnd > scriptStart, "Generated HTML must contain embedded script block");
+ String js = reportHtml.substring(scriptStart, scriptEnd);
+
+ // General error guarding
+ assertTrue(js.contains("try {") || js.contains("try{"), "Storage access must use try blocks");
+ assertTrue(js.contains("catch (e)") || js.contains("catch(e)") || js.contains("catch (err)"),
+ "Storage access must catch storage exceptions");
+
+ // Specific function error handling checks
+ assertTrue(js.matches("(?s).*function setFilter[^{]*\\{.*?try\\s*\\{.*?localStorage\\.(setItem|removeItem)\\('sb-status-filter'.*?\\}\\s*catch.*"),
+ "setFilter localStorage writes must be enclosed in try/catch");
+ assertTrue(js.matches("(?s).*function filterByTag[^{]*\\{.*?try\\s*\\{.*?localStorage\\.(setItem|removeItem)\\('sb-tag-filter'.*?\\}\\s*catch.*"),
+ "filterByTag localStorage writes must be enclosed in try/catch");
+ assertTrue(js.matches("(?s).*function clearTagFilter[^{]*\\{.*?try\\s*\\{.*?localStorage\\.removeItem\\('sb-tag-filter'.*?\\}\\s*catch.*"),
+ "clearTagFilter localStorage removal must be enclosed in try/catch");
+ assertTrue(js.matches("(?s).*\\(function\\(\\)\\s*\\{.*?try\\s*\\{.*?sb-status-filter.*?sb-tag-filter.*?\\}\\s*catch.*"),
+ "Filter restoration routine on load must be enclosed in try/catch");
+ }
+
+ @Test
+ public void buildHtml_filterRestoration_preservesDashboardTabWithoutTabPersistence() throws Exception {
+ String reportHtml = html(1, 0);
+
+ // Default markup states
+ assertTrue(reportHtml.contains("id=\"tab-dashboard\" class=\"tab-pane active\""),
+ "Dashboard tab pane must be active by default");
+ assertTrue(reportHtml.contains("nav-item active\" onclick=\"showTab('dashboard', this)\""),
+ "Dashboard navigation item must be active by default");
+
+ // Must not introduce any tab persistence key
+ assertFalse(reportHtml.contains("sb-tab"),
+ "Generated report must not introduce sb-tab persistence keys");
+
+ // Filter restoration block must not switch tabs
+ int scriptStart = reportHtml.lastIndexOf("