Loop Lessons

The Testing Academy · Masterclass · Loop Engineering

Loop Engineering lessons for QA.

Every module from the course overview, expanded into a practical lesson with a design rule, implementation steps, code, guardrails, and a QA drill.

Part 1 · 6 lessons Part 2 · 6 lessons Part 3 · 6 lessons Format · steps + drill

Part one · Foundations

Design the Control System

Start with authority, evidence, and stop rules. Tools come after the system has a trustworthy shape.

1.1

Loop Engineering in QA

Loop Engineering designs the control system around a maker: goal, evidence, bounded action, independent verification, persistent state, and an explicit decision.

  1. Choose a reversible artifact. Begin with a test, plan, case set, or report that can be restored and reviewed.
  2. State the authority boundary. The maker may propose or edit. It may not redefine acceptance, approve release risk, or deploy.
  3. Put verification outside the maker. Fixed checks must run independently and return structured results.
  4. Name the human owner. A person accepts, revises, rejects, or stops the candidate.
Excalidraw diagram of the six-part QA control loop from goal to human approval
THE VERIFIER, STATE, AND DECISION LIVE OUTSIDE THE MAKER Editable Excalidraw source
Core rule

The agent is not the authority. Trust comes from protected intent, constrained action, reproducible evidence, and a human-owned decision.

QA drill

Pick one repetitive QA task. Write one sentence for the maker's authority and one sentence for what remains human-owned.

1.2

Prompt, Context, Harness, Loop

These layers work together, but they solve different engineering problems. Mixing them produces systems that look autonomous without being controlled.

LayerQuestionQA example
PromptWhat should the model produce?Diagnose the failing locator and propose one correction.
ContextWhich facts should it see?Failure output, target test, DOM contract, and mutable paths.
HarnessWhich tools and permissions exist?Read access, one file allowlist, and fixed verifier commands.
WorkflowWhich predetermined steps run?Reset, execute, collect, report.
RetryShould the same operation run again?Repeat a failed request without changing the candidate.
LoopWhich verified result controls the next action?Diagnose, repair, verify, record, then stop or iterate.
False autonomy

A long prompt that says "keep fixing until green" is not a trustworthy loop. It lacks an external contract, protected oracle, evidence record, and bounded stop decision.

QA drill

Classify one current AI-assisted testing workflow into prompt, context, harness, workflow, retry, and loop. Mark every missing layer.

1.3

Goal and Observable Exit

A loop needs an outcome a verifier can observe. "Fix the test" is vague; "restore the login test without changing product behavior or weakening coverage" is enforceable.

  1. Describe the intended behavior, not the preferred code edit.
  2. Name every acceptance check and the required exit code.
  3. Define accepted, rejected, blocked, and escalated states.
  4. Set an attempt ceiling and a stagnation rule before execution.
goal-and-exit.json
{
  "goal": "Restore the login test without changing product behavior",
  "acceptWhen": [
    "policy.exitCode == 0",
    "targetTest.exitCode == 0",
    "immutableOracle.exitCode == 0"
  ],
  "acceptedState": "AWAITING_HUMAN_APPROVAL",
  "attemptCeiling": 3
}
QA drill

Rewrite "improve our flaky tests" as one observable goal, three acceptance checks, and two escalation conditions.

1.4

Evidence Before Action

Give the maker enough evidence to support a narrow correction, but no secrets, irrelevant repository history, or authority it does not need.

Minimum useful evidence bundle

  • The exact failing command, exit code, and focused output.
  • The mutable artifact and the smallest related source surface.
  • The protected behavior contract or acceptance oracle.
  • The allowlist, denylist, stop rules, and prior attempt result.
evidence-envelope.json
{
  "failureId": "target-test/attempt-01",
  "exitCode": 1,
  "signal": "button named Login was not found",
  "mutablePaths": ["tests/login.spec.ts"],
  "protectedIntent": "A valid user reaches Dashboard",
  "secretsPresent": false
}
Sanitize first

Never place production credentials, customer records, session tokens, private traces, or unrestricted logs into a maker context. Use synthetic fixtures and redact before persistence.

QA drill

Take one real failure and list the smallest evidence bundle that supports diagnosis. Remove every fact that does not change the decision.

1.5

The Bounded Maker

The maker proposes the smallest correction inside an explicit surface. The controller, policy, oracle, and reviewer remain outside its authority.

  1. Allow mutation only where a correction is expected.
  2. Require a diagnosis, changed-path list, and rationale.
  3. Reject any observed path outside the allowlist.
  4. Restore from baseline before a new exercise or after rejection.
maker-boundary.json
{
  "mutablePaths": [
    "tests/login.spec.ts",
    "pages/"
  ],
  "protectedPaths": [
    "app/",
    "tests/oracle/",
    "loop/contract.json",
    "scripts/"
  ],
  "mergeAuthority": false,
  "deploymentAuthority": false
}
Minimal means semantic

A one-line change can still weaken coverage. The policy must test meaning: assertions preserved, credentials unchanged, no skipped tests, and no oracle edits.

QA drill

Create a mutable-path allowlist for a locator repair. Then list the files a false-green candidate would try to change.

1.6

State and Stop Decisions

Conversation memory is not control state. Persist attempts, verifier results, evidence paths, and the current decision in a file or database the controller owns.

state-machine.txt
READY
  -> OBSERVING
  -> MAKING
  -> VERIFYING
  -> RECORDING
  -> ITERATE | ESCALATE | AWAITING_HUMAN_APPROVAL

Stop when:
  acceptance passes
  attempt ceiling is reached
  identical failure repeats
  scope or policy is violated
  required evidence is unavailable

Keep a compact state record with the goal, current status, attempt entries, failed checks, maker response, reviewer verdict, and pending human decision. A resumed process reads that record rather than trusting chat history.

QA drill

Draw the state machine for a test-case generation loop. Include one success state, two escalation states, and one policy-violation state.

Part two · Trust

Build Trustworthy Verification

The maker may be probabilistic. The acceptance path should be fixed, inspectable, and harder to manipulate than the artifact it evaluates.

2.1

Machine-Readable Contract

The contract is the controller's source of truth. Prompts may explain it, but only structured fields should drive permissions, checks, and stop decisions.

loop/contract.json
{
  "schemaVersion": 1,
  "goal": "Restore the login test without changing product behavior or weakening coverage.",
  "mutablePaths": ["tests/login.spec.ts", "pages/"],
  "protectedPaths": ["app/", "tests/oracle/", "loop/", "scripts/"],
  "checks": [
    { "id": "policy", "command": "node", "args": ["scripts/check-policy.mjs"] },
    { "id": "target-test", "command": "npx", "args": ["--no-install", "playwright", "test", "tests/login.spec.ts", "--repeat-each=2"] },
    { "id": "immutable-oracle", "command": "npx", "args": ["--no-install", "playwright", "test", "tests/oracle/login-contract.spec.ts"] }
  ],
  "attemptCeiling": 3,
  "successState": "AWAITING_HUMAN_APPROVAL"
}

Contract review

  • Every path is repository-relative and normalized.
  • Commands and arguments are arrays, not concatenated shell strings.
  • The success state requests review; it does not merge or deploy.
  • The controller rejects unknown schema versions and missing fields.
QA drill

Write a contract for improving one API test without changing its endpoint, expected status, or response assertions.

2.2

Mutable and Protected Paths

An allowlist controls where a candidate may change files. A sealed baseline proves that protected files still match their original content.

  1. Resolve every observed path against the repository root.
  2. Reject traversal, symlink escape, and ambiguous path prefixes.
  3. Hash protected files after reset and before acceptance.
  4. Fail closed when a protected file is missing, new, or changed.
baseline-shape.json
{
  "algorithm": "sha256",
  "files": {
    "app/login.html": "<sealed-hash>",
    "tests/oracle/login-contract.spec.ts": "<sealed-hash>",
    "loop/contract.json": "<sealed-hash>",
    "scripts/check-policy.mjs": "<sealed-hash>"
  }
}
Prefix trap

A naive check that accepts paths beginning with pages/ can be bypassed by unnormalized values. Resolve, normalize, then compare path segments.

QA drill

List the protected files for a Selenium Page Object repair. Explain why the test oracle and build script belong outside the maker surface.

2.3

Deterministic Verifier

A verifier runs fixed commands, captures outputs and exit codes, and emits a structured manifest. It does not ask the maker whether the work passed.

  1. Load checks from the contract.
  2. Spawn each command directly with an argument array.
  3. Capture standard output, standard error, exit code, and signal.
  4. Write one immutable log per check and a manifest for the attempt.
  5. Return pass only when every required check passes.
verifier-result.json
{
  "id": "target-test",
  "command": "npx",
  "args": ["--no-install", "playwright", "test", "tests/login.spec.ts", "--repeat-each=2"],
  "exitCode": 0,
  "signal": null,
  "passed": true,
  "logPath": "loop/evidence/attempt-02/target-test.log",
  "logSha256": "<hash>"
}
Why arrays matter

Structured commands avoid shell interpolation and make the exact executable and arguments reviewable in the contract and manifest.

QA drill

Define three verifier checks for a REST API test repair: policy, focused test, and independent contract check.

2.4

Immutable Oracle

The target test can be wrong. The oracle independently preserves product intent and stays outside the maker's editable surface.

tests/oracle/login-contract.spec.ts
import { expect, test } from "@playwright/test";
import { loadLoginFixture } from "../support/load-login";

test("the product exposes its intended login contract", async ({ page }) => {
  await loadLoginFixture(page);

  await expect(page.getByLabel("Email")).toBeVisible();
  await expect(page.getByLabel("Password")).toBeVisible();
  await expect(page.getByRole("button", { name: "Sign in" })).toBeVisible();
});

This oracle does not duplicate every target-test step. It protects the disputed behavior: the product's accessible sign-in contract. The candidate must satisfy both the user journey and this independent acceptance fact.

Oracle strength

An oracle is useful only when it is more trustworthy than the candidate it evaluates. Protect its source, baseline hash, data, and execution command.

QA drill

Design one immutable oracle for a checkout test. Protect a business outcome that a locator-only candidate cannot redefine.

2.5

Evidence Hashes and Manifests

A green console line is not enough. Each claim should point to preserved output with a hash, and each attempt should point to its complete result set.

attempt manifest
{
  "schemaVersion": 1,
  "attempt": "02",
  "contractSha256": "<contract-hash>",
  "passed": true,
  "results": [
    { "id": "policy", "exitCode": 0, "passed": true, "logSha256": "<hash>" },
    { "id": "target-test", "exitCode": 0, "passed": true, "logSha256": "<hash>" },
    { "id": "immutable-oracle", "exitCode": 0, "passed": true, "logSha256": "<hash>" }
  ]
}

Evidence chain

  • Check log hash proves which output the manifest describes.
  • Attempt-manifest hash proves which result set the index describes.
  • Contract hash proves which acceptance rules controlled the run.
  • State file links the decision to the exact attempt manifest.
QA drill

Trace a pass claim from state to attempt manifest to log. Identify which hash would expose a changed log.

2.6

Independent Review and Human Gate

After checks pass, a separate reviewer challenges the result. Automation may recommend acceptance; a person owns the final risk decision.

Reviewer questions

  1. Did every required check pass with matching evidence hashes?
  2. Did a genuine failing attempt precede the candidate?
  3. Did any protected file, assertion, credential, or test count change?
  4. Does the correction address the observed cause without hiding risk?
  5. Is the evidence complete enough for a human to reproduce the claim?
Excalidraw diagram showing the mutable maker zone and protected QA verification zone
ONE MUTABLE SURFACE · THREE PROTECTED CHECKS · HUMAN-OWNED DECISION Desktop source Mobile source
decision rule
if (allChecksPass && hashesMatch && policyIntact && redBeforeGreen) {
  state = "AWAITING_HUMAN_APPROVAL";
} else {
  state = "ESCALATED";
}
No autonomous release

Passing this loop does not prove complete product correctness. It proves that one bounded candidate met the declared checks and is ready for human review.

QA drill

Write an acceptance checklist for a QA lead reviewing a locator repair candidate and its evidence package.

Part three · Project

Run the QA Repair Loop

Use the included synthetic Playwright project to prove the complete path from intentional failure to a reviewable candidate.

3.1

The Synthetic Login Failure

The product correctly exposes a button named "Sign in." The target test is stale and asks for "Login." The exercise repairs the test without touching product behavior.

Synthetic login page with email, password, and Sign in controls
THE FIXTURE IS SYNTHETIC AND REMAINS OUTSIDE THE MAKER'S MUTABLE PATHS
tests/login.spec.ts · intentional failure
test("valid user reaches the dashboard", async ({ page }) => {
  await loadLoginFixture(page);
  await page.getByLabel("Email").fill("[email protected]");
  await page.getByLabel("Password").fill("correct-password");
  await page.getByRole("button", { name: "Login" }).click();

  await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
});
QA drill

Before execution, predict which line fails, why the product oracle stays green, and which single path the maker needs.

3.2

Seal the Baseline

Setup restores the intentional failure, clears generated evidence, and hashes every protected tutorial file before the loop can act.

project setup
cd examples/qa-loop-demo
npm install
npx playwright install chromium
npm run setup

The expected setup result reports that the intentional failing test was restored, generated evidence was cleared, and the immutable file set was sealed. Read loop/baseline.json before proceeding.

Safe target only

Run this exercise against the included fixture. Do not point the controller at production, customer data, payment flows, or a shared test environment.

QA drill

Inspect the baseline and group protected files into product, oracle, controller, verifier, policy, prompt, and documentation.

3.3

Prove Red Before Green

Run the protected oracle separately, then run the target baseline. This distinguishes a stale test from a broken product and proves the exercise begins in the intended state.

pre-loop proof
npm run oracle
# 1 passed

npm run baseline
# expected failure:
# waiting for getByRole('button', { name: 'Login' })

If the oracle fails, stop: the product fixture no longer satisfies its own contract. If the target fails for another reason, stop: the exercise evidence does not support the planned correction.

Failure taxonomy

Classify the signal before repair: product defect, test defect, environment defect, data defect, or requirement ambiguity. The loop contract should allow only the intended class.

QA drill

Write the stop message for an oracle failure and the separate stop message for an unexpected target-test failure.

3.4

Diagnose and Repair Narrowly

The failure names the missing accessible role and name. The oracle proves the intended name is "Sign in." The candidate changes only that stale locator.

candidate diff
- await page.getByRole("button", { name: "Login" }).click();
+ await page.getByRole("button", { name: "Sign in" }).click();

What remains unchanged

  • The email and password inputs.
  • The synthetic credentials.
  • The dashboard assertion and test count.
  • The product fixture, oracle, policy, controller, and verifier.
Reject false green

Do not replace the click with a forced navigation, weaken the dashboard assertion, skip the test, broaden the locator to any button, or edit the product to match stale test text.

QA drill

List three alternative changes that make the test green but violate the protected intent or coverage policy.

3.5

Verify Repeatedly

Run the controller. It records a failed attempt, applies the bounded correction, then requires policy, repeated target checks, and the immutable oracle to pass.

verified controller output
npm run loop

[verify:01] policy: PASS
[verify:01] target-test: FAIL
[verify:01] immutable-oracle: PASS
[verify:02] policy: PASS
[verify:02] target-test: PASS
[verify:02] immutable-oracle: PASS
[loop] AWAITING_HUMAN_APPROVAL

Inspect loop/LOOP_STATE.md, each attempt manifest, each command log, the evidence index, and the candidate file. Recompute hashes before trusting a pass claim.

Excalidraw diagram of the red-to-green QA evidence chain across two repair attempts
GENUINE RED · BOUNDED CHANGE · REPEATED GREEN · HASHED EVIDENCE Editable Excalidraw source
AttemptPolicyTargetOracleDecision
01PASSFAILPASSIterate
02PASSPASSPASSHuman review
QA drill

Audit attempt 02 from the state file to its manifest and logs. Explain why a reviewer still needs attempt 01.

3.6

Adapt the Pattern

Keep the same control shape while changing the artifact, verifier, oracle, and human owner for each QA domain.

DomainMutable artifactVerifier and oracleHuman gate
Test planStructured planSchema, traceability, critical-risk coverageQA lead
Test casesCase recordsSchema, unique IDs, requirement and boundary coverageTester
STLCOne stage artifactStage-specific policy and exit criteriaStage owner
SeleniumTest or Page ObjectFocused suite, contract test, assertion policyAutomation engineer
AI evalPrompt or policy candidateFrozen dataset, deterministic checks, independent judge setAI quality owner

Adaptation checklist

  1. Replace the goal with a domain-observable outcome.
  2. Keep one narrow mutable artifact.
  3. Protect source requirements, datasets, policies, and acceptance oracles.
  4. Require structured results and evidence hashes.
  5. Escalate ambiguity, repeated failure, scope violation, and high-impact decisions.
Portfolio challenge

Fork the synthetic demo into one Selenium or API example. Preserve the same contract fields, red-before-green proof, evidence chain, independent review, and human approval state.

QA drill

Create a one-page loop contract for the QA domain you use most. Review it for false-green paths before adding any agent.