Site icon QATechTools

Chrome Extension DOM Change Evidence for QA Bug Reports

Chrome Extension DOM Change Evidence for QA Bug Reports featured image

Some UI bugs are hard to explain because the important evidence is not the final screen. The evidence is the change: a field becomes disabled, a validation message appears, a button label changes, a modal is injected, or a row disappears after an action. Screenshots help, but they often miss the exact DOM signal that changed between the tester action and the bug report.

This tutorial shows a practical way to build a small Manifest V3 Chrome extension that captures DOM change evidence for QA bug reports. The goal is not to replace Playwright, Selenium, accessibility tools, or DevTools. The goal is to give manual testers, SDETs, and automation engineers a lightweight evidence helper that records observable page changes, page URL metadata, and tester notes after a deliberate user action.

What This Extension Should Capture

The queue focus for this article is Chrome extension DOM change evidence. The useful QA angle is simple: when a tester starts recording, interacts with the application, and then stops recording, the extension should produce a compact evidence bundle that can be pasted into Jira, Azure DevOps, GitHub Issues, or a bug report template.

A practical first version should capture:

Keep the scope intentionally narrow. A Chrome extension content script can inspect the page DOM, but it should not claim access to private framework state, server-side state, browser accessibility tree internals, or every visual change. For example, canvas rendering, CSS-only animations, and shadow DOM details may need extra handling. Treat this helper as evidence support, not as a complete testing oracle.

Official Source Notes Used for This Workflow

This workflow is based on current Chrome for Developers extension documentation and primary browser API documentation reviewed on 2026-07-04. Chrome extension documentation describes manifest.json as the required root file for extension metadata and permissions. Chrome content scripts can run JavaScript in web pages, activeTab grants temporary access to the active tab after a user gesture, the scripting API can inject functions or files into a target tab, messaging supports JSON-serializable communication between extension contexts, storage persists JSON-serializable extension state asynchronously, and the side panel API can host a persistent extension UI beside the inspected page. MDN documents MutationObserver as the Web API for watching DOM tree changes.

Reference URLs used for the article:

Step 1: Create the Manifest

Create a folder named qa-dom-change-evidence. Add this manifest.json file. It uses a toolbar action, a service worker, the activeTab permission, the scripting API, and extension storage.

{
  "manifest_version": 3,
  "name": "QA DOM Change Evidence",
  "version": "1.0.0",
  "description": "Capture tester-triggered DOM change evidence for QA bug reports.",
  "permissions": ["activeTab", "scripting", "storage"],
  "action": {
    "default_title": "Capture DOM evidence",
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "service-worker.js"
  }
}

This keeps permissions modest. activeTab is a good fit for a tester-clicked capture flow because the tester explicitly chooses the page and starts the extension. If your team later needs always-on capture across specific internal domains, review host permissions carefully and explain why they are needed.

Step 2: Build a Small Popup UI

The popup lets a tester start recording, stop recording, add notes, and copy the evidence. Start with a minimal UI.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>QA DOM Evidence</title>
    <style>
      body { font: 13px system-ui, sans-serif; width: 340px; margin: 12px; }
      button, textarea { width: 100%; margin-top: 8px; }
      textarea { min-height: 80px; }
      pre { white-space: pre-wrap; max-height: 240px; overflow: auto; }
    </style>
  </head>
  <body>
    <button id="start">Start capture</button>
    <button id="stop">Stop capture</button>
    <textarea id="notes" placeholder="Tester notes: action, expected result, actual result"></textarea>
    <button id="copy">Copy bug evidence</button>
    <pre id="output"></pre>
    <script src="popup.js"></script>
  </body>
</html>

A side panel is often better for a mature version because it stays visible while the tester works through the scenario. For a first tutorial, the popup keeps setup simple and screenshot-friendly.

Step 3: Inject a DOM Observer

The content-side code uses MutationObserver to watch for added nodes, removed nodes, and selected attribute changes. Keep the evidence compact so the bug report stays readable.

function describeElement(node) {
  if (!node || node.nodeType !== Node.ELEMENT_NODE) return null;

  const element = node;
  const tag = element.tagName.toLowerCase();
  const id = element.id ? `#${element.id}` : "";
  const classes = Array.from(element.classList || []).slice(0, 3).map((name) => `.${name}`).join("");
  const role = element.getAttribute("role");
  const label = element.getAttribute("aria-label") || element.getAttribute("name");
  const text = (element.innerText || element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120);

  return {
    selectorHint: `${tag}${id}${classes}`,
    role: role || undefined,
    label: label || undefined,
    text: text || undefined
  };
}

window.__qaDomEvidence = {
  startedAt: new Date().toISOString(),
  page: {
    url: location.href,
    title: document.title,
    viewport: `${window.innerWidth}x${window.innerHeight}`
  },
  changes: []
};

window.__qaDomObserver = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    const entry = {
      time: new Date().toISOString(),
      type: mutation.type,
      target: describeElement(mutation.target),
      added: Array.from(mutation.addedNodes || []).map(describeElement).filter(Boolean).slice(0, 5),
      removed: Array.from(mutation.removedNodes || []).map(describeElement).filter(Boolean).slice(0, 5),
      attributeName: mutation.attributeName || undefined
    };

    if (entry.added.length || entry.removed.length || entry.attributeName) {
      window.__qaDomEvidence.changes.push(entry);
      window.__qaDomEvidence.changes = window.__qaDomEvidence.changes.slice(-50);
    }
  }
});

window.__qaDomObserver.observe(document.documentElement, {
  childList: true,
  subtree: true,
  attributes: true,
  attributeFilter: ["class", "hidden", "disabled", "aria-expanded", "aria-invalid", "aria-busy"]
});

This example intentionally limits stored changes to the latest 50 entries. That prevents a noisy page from producing an unusable report. You can tune the limit for your product, but do not store unlimited DOM evidence.

Step 4: Control Capture from the Popup

The popup can inject the observer, request the captured evidence, combine it with tester notes, and copy it. This example uses the scripting API to execute small functions in the active tab.

async function getActiveTab() {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  return tab;
}

async function runInTab(func) {
  const tab = await getActiveTab();
  const [result] = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func });
  return result?.result;
}

document.getElementById("start").addEventListener("click", async () => {
  const tab = await getActiveTab();
  await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ["observer.js"] });
  document.getElementById("output").textContent = "DOM capture started. Reproduce the issue now.";
});

document.getElementById("stop").addEventListener("click", async () => {
  const evidence = await runInTab(() => {
    window.__qaDomObserver?.disconnect();
    return window.__qaDomEvidence || null;
  });

  const notes = document.getElementById("notes").value.trim();
  const report = { ...evidence, testerNotes: notes };
  await chrome.storage.local.set({ lastDomEvidence: report });
  document.getElementById("output").textContent = JSON.stringify(report, null, 2);
});

document.getElementById("copy").addEventListener("click", async () => {
  const { lastDomEvidence } = await chrome.storage.local.get("lastDomEvidence");
  const text = JSON.stringify(lastDomEvidence || {}, null, 2);
  await navigator.clipboard.writeText(text);
  document.getElementById("output").textContent = "Copied evidence to clipboard.";
});

Save the observer code as observer.js and the popup logic as popup.js. Add an empty service-worker.js if you do not need background behavior yet.

Step 5: Use It in a QA Workflow

Install the unpacked extension from chrome://extensions with Developer mode enabled. Then use this workflow during testing:

  1. Open the page where the UI issue can be reproduced.
  2. Click the extension and choose Start capture.
  3. Perform the exact tester action, such as submitting an invalid form or expanding a menu.
  4. Click Stop capture.
  5. Add notes describing expected and actual behavior.
  6. Copy the evidence into the bug report and attach screenshots or video separately.

The copied evidence should make the report easier to triage. For example, a developer can see that the submit button gained disabled, the validation container was added, or the error banner was removed unexpectedly after an API response.

Screenshot Checklist

Capture these screens while following the workflow:

Common Mistakes to Avoid

Best Practices for QA Teams

Use a simple bug report template so DOM evidence has a consistent place. A useful format is:

Observed DOM change evidence:
- Page:
- Tester action:
- Expected result:
- Actual result:
- Key DOM changes:
- Screenshot or video link:
- Environment:

For automation teams, this evidence can also seed better Playwright or Selenium assertions. If the extension shows that an error banner is added with a stable role or label, turn that into a deterministic assertion in the automated test. If the evidence is noisy or unstable, treat it as a signal to improve testability attributes in the application.

FAQ

Can this extension replace DevTools or Playwright traces?

No. It is a lightweight evidence helper. DevTools, Playwright traces, network logs, screenshots, and videos still provide stronger diagnostic context for many bugs.

Will MutationObserver capture every UI change?

No. It captures observable DOM mutations based on the observer configuration. Canvas drawing, CSS-only animations, browser accessibility tree behavior, and private framework state may not appear.

Is it safe to attach DOM evidence to every bug report?

Only after reviewing it. DOM text can contain customer names, emails, tokens, or internal data. Mask sensitive values before sharing.

Why use activeTab instead of host permissions?

activeTab fits a tester-initiated capture flow because access is granted after a user gesture on the active page. Broad host permissions may be valid for internal tools, but they require stricter review.

Conclusion

A Chrome extension can make UI bug reports more precise by recording the DOM changes that happen during a tester action. Keep the first version small: start capture, reproduce the issue, stop capture, add notes, and paste a compact evidence summary into the bug report. That gives QA engineers better handoff data without pretending the extension replaces real test execution, human review, or deeper debugging tools.


Exit mobile version