DEV Community

Cover image for What Should a Self-Healing Cypress Test Be Allowed to Change?
Raju Dandigam
Raju Dandigam

Posted on

What Should a Self-Healing Cypress Test Be Allowed to Change?

A checkout test can “heal” and still become wrong.

Imagine that [data-cy="purchase"] disappears. The page now contains two visible buttons labeled Continue: one requests a quote; the other places the order. An AI repair clicks the first match and the test passes because both flows eventually show “Thank you.”

The selector recovered. The purchase behavior is no longer tested.

This is why self-healing needs a change boundary.

I am a Cypress Ambassador. This article complements my CypressConf 2026 session, “When Tests Heal Themselves: Building Trust in AI-Generated Cypress Tests.”

Separate three kinds of change

Change Example Review level
Selector repair New role/name for the same action Focused review
Step rewrite New click, wait, and assertion sequence Full test review
Intent reinterpretation Different action judged “equivalent” Product decision

Calling all three “healing” hides the risk. A tool should be allowed to perform only the class of change the team intended.

A useful policy separates automatic analysis from automatic mutation:

Stage Agent may do Agent may not do
Diagnose Read failure evidence and rank causes Change source files
Propose Produce a bounded patch and evidence summary Merge or approve its own patch
Verify Run the affected spec and negative control Redefine the expected behavior
Release Attach review evidence Bypass branch protections

This preserves the speed benefit of AI-assisted triage without making “the suite is green” the only approval criterion.

Define the allowed envelope

A simple review policy can state:

const repairPolicy = {
  editableFiles: ['cypress/e2e/checkout.cy.ts'],
  allowed: ['selector-replacement', 'wait-for-observable-event'],
  forbidden: [
    'assertion-removal',
    'fixed-delay',
    'force-click',
    'skip-test',
  ],
  requiredPostconditions: [
    'POST /api/orders returns 201',
    'confirmation route reached',
    'persisted order status is confirmed',
  ],
  humanApproval: true,
}
Enter fullscreen mode Exit fullscreen mode

This is an application policy, not a built-in Cypress format. Its purpose is to make “fix the test” reviewable.

Protect intent outside the selector

A robust test uses semantic anchors that survive markup changes:

cy.intercept('POST', '/api/orders').as('createOrder')

cy.findByRole('button', { name: 'Place order' }).click()

cy.wait('@createOrder').then(({ request, response }) => {
  expect(request.body.items).to.have.length.greaterThan(0)
  expect(response?.statusCode).to.eq(201)
})

cy.location('pathname').should('match', /\/orders\/[^/]+\/confirmation$/)
Enter fullscreen mode Exit fullscreen mode

Now replacing the selector with Request a quote does not satisfy the route and request postconditions.

Useful anchors include:

  • accessible role and name;
  • destination route;
  • API operation and payload;
  • durable server state;
  • audit event;
  • permission check;
  • absence of a forbidden side effect.

Choose the smallest combination that uniquely defines the behavior.

Reject suspicious repair patterns

AI-generated diffs deserve extra scrutiny when they add:

cy.wait(5000)
cy.get('button').first().click({ force: true })
it.skip('places an order', () => { /* ... */ })
Enter fullscreen mode Exit fullscreen mode

Other warning signs are removed assertions, broader selectors, increased timeouts, or replacing a domain assertion with visibility alone.

The repair should provide observable evidence:

Failure: old selector no longer matched
Replacement: role=button, name="Place order"
Changed: selector only
Preserved: order request, confirmation route, persisted status
Not changed: assertions, timeout, force options
Enter fullscreen mode Exit fullscreen mode

Treat the source diff and rerun as authoritative. The summary is a review aid, not proof.

Include an environment fingerprint in that evidence: Cypress, Node, browser, application commit, viewport, and CI image or Docker digest. A repair should first reproduce in the original pinned environment. Otherwise an unreviewed browser or image upgrade can be misclassified as a broken selector—and the agent may rewrite a valid test to accommodate runner drift.

Keep the candidate patch narrow enough for a reviewer to understand in one pass. If the proposed fix crosses application source, fixtures, and assertions, split the work: first repair or clarify the product, then update the test against the reviewed behavior. A large cross-layer patch can make a failing product and a weakened assertion cancel each other out.

Run a negative control

After the repaired test passes, introduce a controlled defect that violates the requirement. For example, stub order creation to fail:

cy.intercept('POST', '/api/orders', {
  statusCode: 409,
  body: { code: 'ORDER_NOT_CREATED' },
})
Enter fullscreen mode Exit fullscreen mode

The purchase test must now fail. If it remains green, the repair weakened the intent.

Negative controls are especially important after step regeneration. They answer the question a successful rerun cannot: Can this test still detect the defect it exists to prevent?

Know the Cypress workflow you are using

cy.prompt() supports generating code that you export and commit, as well as continuous self-healing at runtime. Generated-once code fits ordinary review more naturally because the diff exists before merge. Runtime adaptation needs recorded evidence and tighter restrictions on consequential flows.

Use continuous adaptation cautiously for low-risk presentation changes. Payments, permissions, account deletion, privacy controls, and regulated workflows should normally require an explicit reviewed patch because their business meaning cannot be inferred safely from a nearby passing path.

Cypress currently lists cy.prompt() as Generally Available and Studio AI recommendations as Beta. Release maturity matters, but neither status removes the team's responsibility to define test intent.

The approval rule

Allow automatic repair only when:

  1. the change is inside the declared envelope;
  2. independent postconditions preserve the original behavior;
  3. the code and evidence are reviewable;
  4. a relevant negative control still fails.

Otherwise, escalate instead of healing.

What is the most consequential behavior in your suite that a “helpful” broad selector could accidentally replace?

References

Top comments (0)