BXUP Track progress & test yourself — free
QA & Testing · Hands-on lab · ~5 min read
QA & TESTING · HANDS-ON LAB

Lab 05 · Self-Healing Governance

Configure locator self-healing on a working suite, then ship two seeded UI changes: one where the heal is correct, and one where the heal quietly hides a real regression. You build the audit workflow that catches the second — and the lab is failed if the wrong heal lands.

Vikram's team adopted a self-healing tool after a locator-churn quarter, and within a month a broken "Apply discount" button rode a heal into production behind a green suite. That is the signature failure mode of the entire category: the healer's objective is a passing test, and a passing test is not the same thing as a working feature. In this lab you run a minimal self-healing shim for Playwright — the same fallback-matching idea Healenium and cy.prompt implement, small enough to read in two minutes — apply two UI changes, and operate every heal as a proposed change through an audit queue. Teams running Healenium can substitute its Docker setup in step 2; the governance workflow is identical, and the workflow is the lab.

Time, tier & cost

~90 minutes hands-on · Core tier (maps to module M3 and playbooks PB-17/PB-18). Cost: $0.

Setup

mkdir -p qa-lab-05 && cd qa-lab-05 && npm init -y
npm install express@4 && npm install --save-dev @playwright/test
npx playwright install chromium

shop.js — the app, version 1:

const express = require("express");
const app = express();
app.use(express.urlencoded({ extended: true }));
let cart = { items: 2, subtotal: 800, discount: 0 };
app.get("/", (req, res) => {
  res.send(`<!doctype html><html><head><title>MiniShop</title></head><body>
  <h1>Checkout</h1>
  <p id="subtotal">Subtotal: ₹${cart.subtotal}</p>
  <p id="discount">Discount: ₹${cart.discount}</p>
  <form method="POST" action="/discount">
    <button id="apply-discount" name="apply">Apply 10% discount</button>
  </form>
  <form method="POST" action="/pay">
    <button id="pay-now">Pay ₹${cart.subtotal - cart.discount}</button>
  </form></body></html>`);
});
app.post("/discount", (req, res) => {
  cart.discount = Math.round(cart.subtotal * 0.1);
  res.redirect("/");
});
app.post("/pay", (req, res) => {
  res.send("<p id='paid'>Payment accepted</p>");
});
app.listen(3000);

heal.js — the self-healing shim (read it; auditing a tool you understand is the point):

// Minimal healing locator: try primary selector; on failure, score all
// candidates of the same tag by attribute/text similarity and log a
// proposed heal to heals.log instead of silently passing.
const fs = require("fs");
async function healingClick(page, primary, expectedText) {
  try {
    await page.locator(primary).click({ timeout: 2000 });
    return primary;
  } catch {
    const buttons = await page.locator("button").all();
    let best = null, bestScore = -1;
    for (const b of buttons) {
      const t = (await b.textContent()) || "";
      const overlap = t.split(" ").filter((w) => expectedText.includes(w)).length;
      if (overlap > bestScore) { bestScore = overlap; best = { b, t }; }
    }
    fs.appendFileSync("heals.log",
      `HEAL PROPOSED: ${primary} -> button "${best.t.trim()}"\n`);
    await best.b.click();
    return `button:has-text("${best.t.trim()}")`;
  }
}
module.exports = { healingClick };

tests/checkout.spec.js — the suite:

const { test, expect } = require("@playwright/test");
const { healingClick } = require("../heal");
test("discount reduces the payable amount", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await healingClick(page, "#apply-discount", "Apply 10% discount");
  await expect(page.locator("#discount")).toHaveText("Discount: ₹80");
  await expect(page.getByRole("button", { name: /Pay/ })).toContainText("₹720");
});
test("payment completes", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await healingClick(page, "#pay-now", "Pay");
  await expect(page.locator("#paid")).toHaveText("Payment accepted");
});

Run node shop.js, then npx playwright test — green baseline. git init && git add -A && git commit -m baseline.

Steps

  1. Write the governance policy before any heal exists. In heal-policy.md, write four rules you will operate by: (1) every heal is a proposed change, never an auto-merge; (2) a heal is reviewed as a diff — old locator, new locator, and the assertions that ran after it; (3) a heal accompanied by any assertion failure, weakened assertion, or behavior change escalates to human investigation as a suspected product bug; (4) heals are batched and audited within 24h, with an owner. Everything after this step is you obeying your own policy.
  2. Ship UI change #1 — the honest rename. In shop.js, change id="apply-discount" to id="promo-apply" (button text unchanged). Restart the app, run the suite. The shim fails on #apply-discount, scores candidates, clicks the right button, logs HEAL PROPOSED — and both downstream assertions still pass (discount ₹80, pay ₹720). Test green, heal logged.
  3. Audit heal #1 through the queue. Open heals.log. In heal-audit.md, record: primary locator, healed target, downstream assertions status (all passed), verdict ACCEPT, and the follow-up action — commit the new stable locator into the test (#promo-apply) so the heal doesn't re-fire every run. A heal you accept but never bake into the code is permanent runtime magic; kill it at the source.
  4. Ship UI change #2 — the trap. Two edits together, as happens in real releases: change the discount handler to cart.discount = 0; (the regression — the feature is now broken) and change the button to id="promo-btn" with text "Apply discount code" (the locator churn that summons the healer). Restart. Run the suite.
  5. Watch the heal — and read past the green click. The shim heals the locator (it finds "Apply discount code" as the best text match, logs the proposal, clicks it). Then the assertion Discount: ₹80 fails, because the feature is genuinely broken. This is the correct system behavior: heal succeeded, test failed. Record in heal-audit.md: heal proposed, click landed, downstream assertions FAILED — per policy rule 3, escalate as suspected product bug, verdict REJECT the green path.
  6. Simulate the tool that optimizes for green. Real healing tools with looser assertions do land this. Demonstrate it: temporarily weaken the test — replace the two assertions with await expect(page.locator("#discount")).toBeVisible() — rerun, and watch the suite go fully green against a broken feature. Screenshot or copy the passing output into heal-audit.md under the heading "what the wrong heal looks like when it lands." Then revert the weakening immediately.
  7. Verify and file the masked bug. Reproduce manually: click the discount button, observe Discount stays ₹0 and Pay stays ₹800. File bug-report.md citing the handler line. Fix the regression (cart.discount = Math.round(cart.subtotal * 0.1);), bake the new locator into the test, run to green — a green that now means something.
  8. Close the audit log. Finish heal-audit.md with the summary table: two heals, one accepted (locator-only, assertions green), one escalated (assertions red → product bug found), zero heals landed with weakened assertions. Add one paragraph: which of your four policy rules did the actual catching, and what would have happened without it.
Warning

The dangerous artifact in any self-healing rollout is the fully green run after a heal. Green-after-heal has exactly two explanations — the UI merely moved, or your assertions are too weak to notice the feature died — and only the audit queue can tell you which one you are looking at.

Verify

  • heal-policy.md written before the first heal, with all four rules.
  • heals.log contains both proposed heals.
  • heal-audit.md shows heal #1 ACCEPTED with assertions-passed evidence and the locator baked back into the test; heal #2 ESCALATED with the failing assertions quoted.
  • The "wrong heal landed" demonstration exists in the audit log and was reverted.
  • bug-report.md cites the broken handler; final suite is green with original-strength assertions and the real fix.

Self-grading rubric (pass = 7/10):

Dimension012
Policy-first disciplinePolicy written after the factWritten first, ignored onceWritten first, followed throughout
Correct heal handlingAuto-trustedAccepted, not baked inAccepted + locator committed to source
Wrong heal caughtLanded for realCaught, no manual reproCaught, repro'd, bug filed with line cited
Assertion integrityWeakened assertions surviveRestored lateWeakening only as demo, immediately reverted
Audit trailNonePartialComplete log with verdicts + reasoning
▸Answer key — both seeded changes, expected verdicts, and how the wrong heal hidesoptional deep dive — click to expand

Seeded change #1 (correct heal). #apply-discount → #promo-apply, text unchanged. Expected: shim fails primary, text-similarity match finds the same button, both downstream assertions pass (₹80 / ₹720). Verdict ACCEPT. Full credit requires the follow-up: updating the test to the new stable locator, because an accepted heal left in runtime is unaudited drift. This is the benign case self-healing vendors demo.

Seeded change #2 (the wrong-heal trap). Two stacked edits: cart.discount = 0 (regression) + button renamed to #promo-btn / "Apply discount code" (churn). Expected mechanics: the healer legitimately re-finds the button — the heal itself is not the defect. The defect is downstream: Discount: ₹80 and Pay ₹720 both fail. Correct verdict: escalate as product bug; the heal may be fine but nothing lands until the red is explained. The lab is failed if any suite state passes with the regression present — that includes the step-6 weakened version if you forgot to revert it.

How the wrong heal lands in real tools. Three routes, all worth naming in your closing paragraph: (a) the suite's assertions were already weak (visibility checks, no value checks), so heal + green happens with zero alarms — the step-6 demonstration; (b) an LLM-based healer "fixes" the assertion along with the locator, reasoning the expected text changed; (c) a bulk heal-approval UI trains humans to click approve on a queue of 40 heals, and the one masking a bug rides through in the batch. Your policy rule 3 (any heal + assertion anomaly = suspected product bug) is the countermeasure to all three.

Expected audit-log shape. Heal 1: locator-only diff, assertions green, ACCEPT, baked in. Heal 2: locator diff green, assertions red, ESCALATE → manual repro → bug-report.md citing cart.discount = 0 → fix → restore → green. Scoring: any accepted heal without recorded downstream-assertion status scores 0 on audit quality — assertion status is the entire information content of a heal review.

Key Takeaway:

Self-healing removes locator churn, not judgment. Operate every heal as a proposed change with the downstream assertions as evidence: locator-only heal with green assertions gets accepted and baked into source; any heal wearing a red assertion is a product bug filing itself — and a tool that turns that red into green has hidden a regression, not healed a test.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in