您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

test-healing-patterns.md 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. # Test Healing Patterns
  2. ## Principle
  3. Common test failures follow predictable patterns (stale selectors, race conditions, dynamic data assertions, network errors, hard waits). **Automated healing** identifies failure signatures and applies pattern-based fixes. Manual healing captures these patterns for future automation.
  4. ## Rationale
  5. **The Problem**: Test failures waste developer time on repetitive debugging. Teams manually fix the same selector issues, timing bugs, and data mismatches repeatedly across test suites.
  6. **The Solution**: Catalog common failure patterns with diagnostic signatures and automated fixes. When a test fails, match the error message/stack trace against known patterns and apply the corresponding fix. This transforms test maintenance from reactive debugging to proactive pattern application.
  7. **Why This Matters**:
  8. - Reduces test maintenance time by 60-80% (pattern-based fixes vs manual debugging)
  9. - Prevents flakiness regression (same bug fixed once, applied everywhere)
  10. - Builds institutional knowledge (failure catalog grows over time)
  11. - Enables self-healing test suites (automate workflow validates and heals)
  12. ## Pattern Examples
  13. ### Example 1: Common Failure Pattern - Stale Selectors (Element Not Found)
  14. **Context**: Test fails with "Element not found" or "Locator resolved to 0 elements" errors
  15. **Diagnostic Signature**:
  16. ```typescript
  17. // src/testing/healing/selector-healing.ts
  18. export type SelectorFailure = {
  19. errorMessage: string;
  20. stackTrace: string;
  21. selector: string;
  22. testFile: string;
  23. lineNumber: number;
  24. };
  25. /**
  26. * Detect stale selector failures
  27. */
  28. export function isSelectorFailure(error: Error): boolean {
  29. const patterns = [
  30. /locator.*resolved to 0 elements/i,
  31. /element not found/i,
  32. /waiting for locator.*to be visible/i,
  33. /selector.*did not match any elements/i,
  34. /unable to find element/i,
  35. ];
  36. return patterns.some((pattern) => pattern.test(error.message));
  37. }
  38. /**
  39. * Extract selector from error message
  40. */
  41. export function extractSelector(errorMessage: string): string | null {
  42. // Playwright: "locator('button[type=\"submit\"]') resolved to 0 elements"
  43. const playwrightMatch = errorMessage.match(/locator\('([^']+)'\)/);
  44. if (playwrightMatch) return playwrightMatch[1];
  45. // Cypress: "Timed out retrying: Expected to find element: '.submit-button'"
  46. const cypressMatch = errorMessage.match(/Expected to find element: ['"]([^'"]+)['"]/i);
  47. if (cypressMatch) return cypressMatch[1];
  48. return null;
  49. }
  50. /**
  51. * Suggest better selector based on hierarchy
  52. */
  53. export function suggestBetterSelector(badSelector: string): string {
  54. // If using CSS class → suggest data-testid
  55. if (badSelector.startsWith('.') || badSelector.includes('class=')) {
  56. const elementName = badSelector.match(/class=["']([^"']+)["']/)?.[1] || badSelector.slice(1);
  57. return `page.getByTestId('${elementName}') // Prefer data-testid over CSS class`;
  58. }
  59. // If using ID → suggest data-testid
  60. if (badSelector.startsWith('#')) {
  61. return `page.getByTestId('${badSelector.slice(1)}') // Prefer data-testid over ID`;
  62. }
  63. // If using nth() → suggest filter() or more specific selector
  64. if (badSelector.includes('.nth(')) {
  65. return `page.locator('${badSelector.split('.nth(')[0]}').filter({ hasText: 'specific text' }) // Avoid brittle nth(), use filter()`;
  66. }
  67. // If using complex CSS → suggest ARIA role
  68. if (badSelector.includes('>') || badSelector.includes('+')) {
  69. return `page.getByRole('button', { name: 'Submit' }) // Prefer ARIA roles over complex CSS`;
  70. }
  71. return `page.getByTestId('...') // Add data-testid attribute to element`;
  72. }
  73. ```
  74. **Healing Implementation**:
  75. ```typescript
  76. // tests/healing/selector-healing.spec.ts
  77. import { test, expect } from '@playwright/test';
  78. import { isSelectorFailure, extractSelector, suggestBetterSelector } from '../../src/testing/healing/selector-healing';
  79. test('heal stale selector failures automatically', async ({ page }) => {
  80. await page.goto('/dashboard');
  81. try {
  82. // Original test with brittle CSS selector
  83. await page.locator('.btn-primary').click();
  84. } catch (error: any) {
  85. if (isSelectorFailure(error)) {
  86. const badSelector = extractSelector(error.message);
  87. const suggestion = badSelector ? suggestBetterSelector(badSelector) : null;
  88. console.log('HEALING SUGGESTION:', suggestion);
  89. // Apply healed selector
  90. await page.getByTestId('submit-button').click(); // Fixed!
  91. } else {
  92. throw error; // Not a selector issue, rethrow
  93. }
  94. }
  95. await expect(page.getByText('Success')).toBeVisible();
  96. });
  97. ```
  98. **Key Points**:
  99. - Diagnosis: Error message contains "locator resolved to 0 elements" or "element not found"
  100. - Fix: Replace brittle selector (CSS class, ID, nth) with robust alternative (data-testid, ARIA role)
  101. - Prevention: Follow selector hierarchy (data-testid > ARIA > text > CSS)
  102. - Automation: Pattern matching on error message + stack trace
  103. ---
  104. ### Example 2: Common Failure Pattern - Race Conditions (Timing Errors)
  105. **Context**: Test fails with "timeout waiting for element" or "element not visible" errors
  106. **Diagnostic Signature**:
  107. ```typescript
  108. // src/testing/healing/timing-healing.ts
  109. export type TimingFailure = {
  110. errorMessage: string;
  111. testFile: string;
  112. lineNumber: number;
  113. actionType: 'click' | 'fill' | 'waitFor' | 'expect';
  114. };
  115. /**
  116. * Detect race condition failures
  117. */
  118. export function isTimingFailure(error: Error): boolean {
  119. const patterns = [
  120. /timeout.*waiting for/i,
  121. /element is not visible/i,
  122. /element is not attached to the dom/i,
  123. /waiting for element to be visible.*exceeded/i,
  124. /timed out retrying/i,
  125. /waitForLoadState.*timeout/i,
  126. ];
  127. return patterns.some((pattern) => pattern.test(error.message));
  128. }
  129. /**
  130. * Detect hard wait anti-pattern
  131. */
  132. export function hasHardWait(testCode: string): boolean {
  133. const hardWaitPatterns = [/page\.waitForTimeout\(/, /cy\.wait\(\d+\)/, /await.*sleep\(/, /setTimeout\(/];
  134. return hardWaitPatterns.some((pattern) => pattern.test(testCode));
  135. }
  136. /**
  137. * Suggest deterministic wait replacement
  138. */
  139. export function suggestDeterministicWait(testCode: string): string {
  140. if (testCode.includes('page.waitForTimeout')) {
  141. return `
  142. // ❌ Bad: Hard wait (flaky)
  143. // await page.waitForTimeout(3000)
  144. // ✅ Good: Wait for network response
  145. await page.waitForResponse(resp => resp.url().includes('/api/data') && resp.status() === 200)
  146. // OR wait for element state
  147. await page.getByTestId('loading-spinner').waitFor({ state: 'detached' })
  148. `.trim();
  149. }
  150. if (testCode.includes('cy.wait(') && /cy\.wait\(\d+\)/.test(testCode)) {
  151. return `
  152. // ❌ Bad: Hard wait (flaky)
  153. // cy.wait(3000)
  154. // ✅ Good: Wait for aliased network request
  155. cy.intercept('GET', '/api/data').as('getData')
  156. cy.visit('/page')
  157. cy.wait('@getData')
  158. `.trim();
  159. }
  160. return `
  161. // Add network-first interception BEFORE navigation:
  162. await page.route('**/api/**', route => route.continue())
  163. const responsePromise = page.waitForResponse('**/api/data')
  164. await page.goto('/page')
  165. await responsePromise
  166. `.trim();
  167. }
  168. ```
  169. **Healing Implementation**:
  170. ```typescript
  171. // tests/healing/timing-healing.spec.ts
  172. import { test, expect } from '@playwright/test';
  173. import { isTimingFailure, hasHardWait, suggestDeterministicWait } from '../../src/testing/healing/timing-healing';
  174. test('heal race condition with network-first pattern', async ({ page, context }) => {
  175. // Setup interception BEFORE navigation (prevent race)
  176. await context.route('**/api/products', (route) => {
  177. route.fulfill({
  178. status: 200,
  179. body: JSON.stringify({ products: [{ id: 1, name: 'Product A' }] }),
  180. });
  181. });
  182. const responsePromise = page.waitForResponse('**/api/products');
  183. await page.goto('/products');
  184. await responsePromise; // Deterministic wait
  185. // Element now reliably visible (no race condition)
  186. await expect(page.getByText('Product A')).toBeVisible();
  187. });
  188. test('heal hard wait with event-based wait', async ({ page }) => {
  189. await page.goto('/dashboard');
  190. // ❌ Original (flaky): await page.waitForTimeout(3000)
  191. // ✅ Healed: Wait for spinner to disappear
  192. await page.getByTestId('loading-spinner').waitFor({ state: 'detached' });
  193. // Element now reliably visible
  194. await expect(page.getByText('Dashboard loaded')).toBeVisible();
  195. });
  196. ```
  197. **Key Points**:
  198. - Diagnosis: Error contains "timeout" or "not visible", often after navigation
  199. - Fix: Replace hard waits with network-first pattern or element state waits
  200. - Prevention: ALWAYS intercept before navigate, use waitForResponse()
  201. - Automation: Detect `page.waitForTimeout()` or `cy.wait(number)` in test code
  202. ---
  203. ### Example 3: Common Failure Pattern - Dynamic Data Assertions (Non-Deterministic IDs)
  204. **Context**: Test fails with "Expected 'User 123' but received 'User 456'" or timestamp mismatches
  205. **Diagnostic Signature**:
  206. ```typescript
  207. // src/testing/healing/data-healing.ts
  208. export type DataFailure = {
  209. errorMessage: string;
  210. expectedValue: string;
  211. actualValue: string;
  212. testFile: string;
  213. lineNumber: number;
  214. };
  215. /**
  216. * Detect dynamic data assertion failures
  217. */
  218. export function isDynamicDataFailure(error: Error): boolean {
  219. const patterns = [
  220. /expected.*\d+.*received.*\d+/i, // ID mismatches
  221. /expected.*\d{4}-\d{2}-\d{2}.*received/i, // Date mismatches
  222. /expected.*user.*\d+/i, // Dynamic user IDs
  223. /expected.*order.*\d+/i, // Dynamic order IDs
  224. /expected.*to.*contain.*\d+/i, // Numeric assertions
  225. ];
  226. return patterns.some((pattern) => pattern.test(error.message));
  227. }
  228. /**
  229. * Suggest flexible assertion pattern
  230. */
  231. export function suggestFlexibleAssertion(errorMessage: string): string {
  232. if (/expected.*user.*\d+/i.test(errorMessage)) {
  233. return `
  234. // ❌ Bad: Hardcoded ID
  235. // await expect(page.getByText('User 123')).toBeVisible()
  236. // ✅ Good: Regex pattern for any user ID
  237. await expect(page.getByText(/User \\d+/)).toBeVisible()
  238. // OR use partial match
  239. await expect(page.locator('[data-testid="user-name"]')).toContainText('User')
  240. `.trim();
  241. }
  242. if (/expected.*\d{4}-\d{2}-\d{2}/i.test(errorMessage)) {
  243. return `
  244. // ❌ Bad: Hardcoded date
  245. // await expect(page.getByText('2024-01-15')).toBeVisible()
  246. // ✅ Good: Dynamic date validation
  247. const today = new Date().toISOString().split('T')[0]
  248. await expect(page.getByTestId('created-date')).toHaveText(today)
  249. // OR use date format regex
  250. await expect(page.getByTestId('created-date')).toHaveText(/\\d{4}-\\d{2}-\\d{2}/)
  251. `.trim();
  252. }
  253. if (/expected.*order.*\d+/i.test(errorMessage)) {
  254. return `
  255. // ❌ Bad: Hardcoded order ID
  256. // const orderId = '12345'
  257. // ✅ Good: Capture dynamic order ID
  258. const orderText = await page.getByTestId('order-id').textContent()
  259. const orderId = orderText?.match(/Order #(\\d+)/)?.[1]
  260. expect(orderId).toBeTruthy()
  261. // Use captured ID in later assertions
  262. await expect(page.getByText(\`Order #\${orderId} confirmed\`)).toBeVisible()
  263. `.trim();
  264. }
  265. return `Use regex patterns, partial matching, or capture dynamic values instead of hardcoding`;
  266. }
  267. ```
  268. **Healing Implementation**:
  269. ```typescript
  270. // tests/healing/data-healing.spec.ts
  271. import { test, expect } from '@playwright/test';
  272. test('heal dynamic ID assertion with regex', async ({ page }) => {
  273. await page.goto('/users');
  274. // ❌ Original (fails with random IDs): await expect(page.getByText('User 123')).toBeVisible()
  275. // ✅ Healed: Regex pattern matches any user ID
  276. await expect(page.getByText(/User \d+/)).toBeVisible();
  277. });
  278. test('heal timestamp assertion with dynamic generation', async ({ page }) => {
  279. await page.goto('/dashboard');
  280. // ❌ Original (fails daily): await expect(page.getByText('2024-01-15')).toBeVisible()
  281. // ✅ Healed: Generate expected date dynamically
  282. const today = new Date().toISOString().split('T')[0];
  283. await expect(page.getByTestId('last-updated')).toContainText(today);
  284. });
  285. test('heal order ID assertion with capture', async ({ page, request }) => {
  286. // Create order via API (dynamic ID)
  287. const response = await request.post('/api/orders', {
  288. data: { productId: '123', quantity: 1 },
  289. });
  290. const { orderId } = await response.json();
  291. // ✅ Healed: Use captured dynamic ID
  292. await page.goto(`/orders/${orderId}`);
  293. await expect(page.getByText(`Order #${orderId}`)).toBeVisible();
  294. });
  295. ```
  296. **Key Points**:
  297. - Diagnosis: Error message shows expected vs actual value mismatch with IDs/timestamps
  298. - Fix: Use regex patterns (`/User \d+/`), partial matching, or capture dynamic values
  299. - Prevention: Never hardcode IDs, timestamps, or random data in assertions
  300. - Automation: Parse error message for expected/actual values, suggest regex patterns
  301. ---
  302. ### Example 4: Common Failure Pattern - Network Errors (Missing Route Interception)
  303. **Context**: Test fails with "API call failed" or "500 error" during test execution
  304. **Diagnostic Signature**:
  305. ```typescript
  306. // src/testing/healing/network-healing.ts
  307. export type NetworkFailure = {
  308. errorMessage: string;
  309. url: string;
  310. statusCode: number;
  311. method: string;
  312. };
  313. /**
  314. * Detect network failure
  315. */
  316. export function isNetworkFailure(error: Error): boolean {
  317. const patterns = [
  318. /api.*call.*failed/i,
  319. /request.*failed/i,
  320. /network.*error/i,
  321. /500.*internal server error/i,
  322. /503.*service unavailable/i,
  323. /fetch.*failed/i,
  324. ];
  325. return patterns.some((pattern) => pattern.test(error.message));
  326. }
  327. /**
  328. * Suggest route interception
  329. */
  330. export function suggestRouteInterception(url: string, method: string): string {
  331. return `
  332. // ❌ Bad: Real API call (unreliable, slow, external dependency)
  333. // ✅ Good: Mock API response with route interception
  334. await page.route('${url}', route => {
  335. route.fulfill({
  336. status: 200,
  337. contentType: 'application/json',
  338. body: JSON.stringify({
  339. // Mock response data
  340. id: 1,
  341. name: 'Test User',
  342. email: 'test@example.com'
  343. })
  344. })
  345. })
  346. // Then perform action
  347. await page.goto('/page')
  348. `.trim();
  349. }
  350. ```
  351. **Healing Implementation**:
  352. ```typescript
  353. // tests/healing/network-healing.spec.ts
  354. import { test, expect } from '@playwright/test';
  355. test('heal network failure with route mocking', async ({ page, context }) => {
  356. // ✅ Healed: Mock API to prevent real network calls
  357. await context.route('**/api/products', (route) => {
  358. route.fulfill({
  359. status: 200,
  360. contentType: 'application/json',
  361. body: JSON.stringify({
  362. products: [
  363. { id: 1, name: 'Product A', price: 29.99 },
  364. { id: 2, name: 'Product B', price: 49.99 },
  365. ],
  366. }),
  367. });
  368. });
  369. await page.goto('/products');
  370. // Test now reliable (no external API dependency)
  371. await expect(page.getByText('Product A')).toBeVisible();
  372. await expect(page.getByText('$29.99')).toBeVisible();
  373. });
  374. test('heal 500 error with error state mocking', async ({ page, context }) => {
  375. // Mock API failure scenario
  376. await context.route('**/api/products', (route) => {
  377. route.fulfill({ status: 500, body: JSON.stringify({ error: 'Internal Server Error' }) });
  378. });
  379. await page.goto('/products');
  380. // Verify error handling (not crash)
  381. await expect(page.getByText('Unable to load products')).toBeVisible();
  382. await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible();
  383. });
  384. ```
  385. **Key Points**:
  386. - Diagnosis: Error message contains "API call failed", "500 error", or network-related failures
  387. - Fix: Add `page.route()` or `cy.intercept()` to mock API responses
  388. - Prevention: Mock ALL external dependencies (APIs, third-party services)
  389. - Automation: Extract URL from error message, generate route interception code
  390. ---
  391. ### Example 5: Common Failure Pattern - Hard Waits (Unreliable Timing)
  392. **Context**: Test fails intermittently with "timeout exceeded" or passes/fails randomly
  393. **Diagnostic Signature**:
  394. ```typescript
  395. // src/testing/healing/hard-wait-healing.ts
  396. /**
  397. * Detect hard wait anti-pattern in test code
  398. */
  399. export function detectHardWaits(testCode: string): Array<{ line: number; code: string }> {
  400. const lines = testCode.split('\n');
  401. const violations: Array<{ line: number; code: string }> = [];
  402. lines.forEach((line, index) => {
  403. if (line.includes('page.waitForTimeout(') || /cy\.wait\(\d+\)/.test(line) || line.includes('sleep(') || line.includes('setTimeout(')) {
  404. violations.push({ line: index + 1, code: line.trim() });
  405. }
  406. });
  407. return violations;
  408. }
  409. /**
  410. * Suggest event-based wait replacement
  411. */
  412. export function suggestEventBasedWait(hardWaitLine: string): string {
  413. if (hardWaitLine.includes('page.waitForTimeout')) {
  414. return `
  415. // ❌ Bad: Hard wait (flaky)
  416. ${hardWaitLine}
  417. // ✅ Good: Wait for network response
  418. await page.waitForResponse(resp => resp.url().includes('/api/') && resp.ok())
  419. // OR wait for element state change
  420. await page.getByTestId('loading-spinner').waitFor({ state: 'detached' })
  421. await page.getByTestId('content').waitFor({ state: 'visible' })
  422. `.trim();
  423. }
  424. if (/cy\.wait\(\d+\)/.test(hardWaitLine)) {
  425. return `
  426. // ❌ Bad: Hard wait (flaky)
  427. ${hardWaitLine}
  428. // ✅ Good: Wait for aliased request
  429. cy.intercept('GET', '/api/data').as('getData')
  430. cy.visit('/page')
  431. cy.wait('@getData') // Deterministic
  432. `.trim();
  433. }
  434. return 'Replace hard waits with event-based waits (waitForResponse, waitFor state changes)';
  435. }
  436. ```
  437. **Healing Implementation**:
  438. ```typescript
  439. // tests/healing/hard-wait-healing.spec.ts
  440. import { test, expect } from '@playwright/test';
  441. test('heal hard wait with deterministic wait', async ({ page }) => {
  442. await page.goto('/dashboard');
  443. // ❌ Original (flaky): await page.waitForTimeout(3000)
  444. // ✅ Healed: Wait for loading spinner to disappear
  445. await page.getByTestId('loading-spinner').waitFor({ state: 'detached' });
  446. // OR wait for specific network response
  447. await page.waitForResponse((resp) => resp.url().includes('/api/dashboard') && resp.ok());
  448. await expect(page.getByText('Dashboard ready')).toBeVisible();
  449. });
  450. test('heal implicit wait with explicit network wait', async ({ page }) => {
  451. const responsePromise = page.waitForResponse('**/api/products');
  452. await page.goto('/products');
  453. // ❌ Original (race condition): await page.getByText('Product A').click()
  454. // ✅ Healed: Wait for network first
  455. await responsePromise;
  456. await page.getByText('Product A').click();
  457. await expect(page).toHaveURL(/\/products\/\d+/);
  458. });
  459. ```
  460. **Key Points**:
  461. - Diagnosis: Test code contains `page.waitForTimeout()` or `cy.wait(number)`
  462. - Fix: Replace with `waitForResponse()`, `waitFor({ state })`, or aliased intercepts
  463. - Prevention: NEVER use hard waits, always use event-based/response-based waits
  464. - Automation: Scan test code for hard wait patterns, suggest deterministic replacements
  465. ---
  466. ## Healing Pattern Catalog
  467. | Failure Type | Diagnostic Signature | Healing Strategy | Prevention Pattern |
  468. | -------------- | --------------------------------------------- | ------------------------------------- | ----------------------------------------- |
  469. | Stale Selector | "locator resolved to 0 elements" | Replace with data-testid or ARIA role | Selector hierarchy (testid > ARIA > text) |
  470. | Race Condition | "timeout waiting for element" | Add network-first interception | Intercept before navigate |
  471. | Dynamic Data | "Expected 'User 123' but got 'User 456'" | Use regex or capture dynamic values | Never hardcode IDs/timestamps |
  472. | Network Error | "API call failed", "500 error" | Add route mocking | Mock all external dependencies |
  473. | Hard Wait | Test contains `waitForTimeout()` or `wait(n)` | Replace with event-based waits | Always use deterministic waits |
  474. ## Healing Workflow
  475. 1. **Run test** → Capture failure
  476. 2. **Identify pattern** → Match error against diagnostic signatures
  477. 3. **Apply fix** → Use pattern-based healing strategy
  478. 4. **Re-run test** → Validate fix (max 3 iterations)
  479. 5. **Mark unfixable** → Use `test.fixme()` if healing fails after 3 attempts
  480. ## Healing Checklist
  481. Before enabling auto-healing in workflows:
  482. - [ ] **Failure catalog documented**: Common patterns identified (selectors, timing, data, network, hard waits)
  483. - [ ] **Diagnostic signatures defined**: Error message patterns for each failure type
  484. - [ ] **Healing strategies documented**: Fix patterns for each failure type
  485. - [ ] **Prevention patterns documented**: Best practices to avoid recurrence
  486. - [ ] **Healing iteration limit set**: Max 3 attempts before marking test.fixme()
  487. - [ ] **MCP integration optional**: Graceful degradation without Playwright MCP
  488. - [ ] **Pattern-based fallback**: Use knowledge base patterns when MCP unavailable
  489. - [ ] **Healing report generated**: Document what was healed and how
  490. ## Integration Points
  491. - **Used in workflows**: `*automate` (auto-healing after test generation), `*atdd` (optional healing for acceptance tests)
  492. - **Related fragments**: `selector-resilience.md` (selector debugging), `timing-debugging.md` (race condition fixes), `network-first.md` (interception patterns), `data-factories.md` (dynamic data handling)
  493. - **Tools**: Error message parsing, AST analysis for code patterns, Playwright MCP (optional), pattern matching
  494. _Source: Playwright test-healer patterns, production test failure analysis, common anti-patterns from test-resources-for-ai_