@muuktest/amikoo-playwright 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/capture.cjs CHANGED
@@ -40,11 +40,8 @@ async function captureFailure(page, testInfo, consoleLogs, networkFailures) {
40
40
  try {
41
41
  const testName = testInfo.title.replace(/\s+/g, "_").toLowerCase();
42
42
  const { jsonPath, screenshotPath } = await (0, import_dom_extractor.extractAndSaveElements)(page, `${testName}_failure`);
43
- await testInfo.attach("dom-elements", { path: jsonPath, contentType: "application/json" });
44
- await testInfo.attach("failure-screenshot", { path: screenshotPath, contentType: "image/png" });
45
43
  const failureInfoPath = import_path.default.join(import_path.default.dirname(jsonPath), "failure_info.json");
46
44
  await (0, import_failure_analyzer.analyzeFailure)(page, testInfo, consoleLogs, networkFailures, { jsonPath, screenshotPath, failureInfoPath });
47
- await testInfo.attach("failure-info", { path: failureInfoPath, contentType: "application/json" });
48
45
  console.log("[Test Failure] DOM capture completed successfully");
49
46
  } catch (error) {
50
47
  console.error("[Test Failure] Error capturing DOM state:", error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/capture.ts"],"sourcesContent":["import type { Page, TestInfo } from '@playwright/test';\nimport path from 'path';\nimport { extractAndSaveElements } from './helpers/dom-extractor.js';\nimport { analyzeFailure } from './helpers/failure-analyzer.js';\nimport type { ConsoleEntry, NetworkFailure } from './helpers/failure-analyzer.js';\n\nexport async function captureFailure(\n page: Page,\n testInfo: TestInfo,\n consoleLogs: ConsoleEntry[],\n networkFailures: NetworkFailure[]\n): Promise<void> {\n if (testInfo.status === testInfo.expectedStatus) return;\n console.log('\\n[Test Failure] Capturing DOM state before trace...');\n try {\n const testName = testInfo.title.replace(/\\s+/g, '_').toLowerCase();\n const { jsonPath, screenshotPath } = await extractAndSaveElements(page, `${testName}_failure`);\n await testInfo.attach('dom-elements', { path: jsonPath, contentType: 'application/json' });\n await testInfo.attach('failure-screenshot', { path: screenshotPath, contentType: 'image/png' });\n const failureInfoPath = path.join(path.dirname(jsonPath), 'failure_info.json');\n await analyzeFailure(page, testInfo, consoleLogs, networkFailures, { jsonPath, screenshotPath, failureInfoPath });\n await testInfo.attach('failure-info', { path: failureInfoPath, contentType: 'application/json' });\n console.log('[Test Failure] DOM capture completed successfully');\n } catch (error) {\n console.error('[Test Failure] Error capturing DOM state:', error);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAAiB;AACjB,2BAAuC;AACvC,8BAA+B;AAG/B,eAAsB,eACpB,MACA,UACA,aACA,iBACe;AACf,MAAI,SAAS,WAAW,SAAS,eAAgB;AACjD,UAAQ,IAAI,sDAAsD;AAClE,MAAI;AACF,UAAM,WAAW,SAAS,MAAM,QAAQ,QAAQ,GAAG,EAAE,YAAY;AACjE,UAAM,EAAE,UAAU,eAAe,IAAI,UAAM,6CAAuB,MAAM,GAAG,QAAQ,UAAU;AAC7F,UAAM,SAAS,OAAO,gBAAgB,EAAE,MAAM,UAAU,aAAa,mBAAmB,CAAC;AACzF,UAAM,SAAS,OAAO,sBAAsB,EAAE,MAAM,gBAAgB,aAAa,YAAY,CAAC;AAC9F,UAAM,kBAAkB,YAAAA,QAAK,KAAK,YAAAA,QAAK,QAAQ,QAAQ,GAAG,mBAAmB;AAC7E,cAAM,wCAAe,MAAM,UAAU,aAAa,iBAAiB,EAAE,UAAU,gBAAgB,gBAAgB,CAAC;AAChH,UAAM,SAAS,OAAO,gBAAgB,EAAE,MAAM,iBAAiB,aAAa,mBAAmB,CAAC;AAChG,YAAQ,IAAI,mDAAmD;AAAA,EACjE,SAAS,OAAO;AACd,YAAQ,MAAM,6CAA6C,KAAK;AAAA,EAClE;AACF;","names":["path"]}
1
+ {"version":3,"sources":["../src/capture.ts"],"sourcesContent":["import type { Page, TestInfo } from '@playwright/test';\nimport path from 'path';\nimport { extractAndSaveElements } from './helpers/dom-extractor.js';\nimport { analyzeFailure } from './helpers/failure-analyzer.js';\nimport type { ConsoleEntry, NetworkFailure } from './helpers/failure-analyzer.js';\n\nexport async function captureFailure(\n page: Page,\n testInfo: TestInfo,\n consoleLogs: ConsoleEntry[],\n networkFailures: NetworkFailure[]\n): Promise<void> {\n if (testInfo.status === testInfo.expectedStatus) return;\n console.log('\\n[Test Failure] Capturing DOM state before trace...');\n try {\n const testName = testInfo.title.replace(/\\s+/g, '_').toLowerCase();\n const { jsonPath, screenshotPath } = await extractAndSaveElements(page, `${testName}_failure`);\n const failureInfoPath = path.join(path.dirname(jsonPath), 'failure_info.json');\n await analyzeFailure(page, testInfo, consoleLogs, networkFailures, { jsonPath, screenshotPath, failureInfoPath });\n console.log('[Test Failure] DOM capture completed successfully');\n } catch (error) {\n console.error('[Test Failure] Error capturing DOM state:', error);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAAiB;AACjB,2BAAuC;AACvC,8BAA+B;AAG/B,eAAsB,eACpB,MACA,UACA,aACA,iBACe;AACf,MAAI,SAAS,WAAW,SAAS,eAAgB;AACjD,UAAQ,IAAI,sDAAsD;AAClE,MAAI;AACF,UAAM,WAAW,SAAS,MAAM,QAAQ,QAAQ,GAAG,EAAE,YAAY;AACjE,UAAM,EAAE,UAAU,eAAe,IAAI,UAAM,6CAAuB,MAAM,GAAG,QAAQ,UAAU;AAC7F,UAAM,kBAAkB,YAAAA,QAAK,KAAK,YAAAA,QAAK,QAAQ,QAAQ,GAAG,mBAAmB;AAC7E,cAAM,wCAAe,MAAM,UAAU,aAAa,iBAAiB,EAAE,UAAU,gBAAgB,gBAAgB,CAAC;AAChH,YAAQ,IAAI,mDAAmD;AAAA,EACjE,SAAS,OAAO;AACd,YAAQ,MAAM,6CAA6C,KAAK;AAAA,EAClE;AACF;","names":["path"]}
package/dist/capture.js CHANGED
@@ -7,11 +7,8 @@ async function captureFailure(page, testInfo, consoleLogs, networkFailures) {
7
7
  try {
8
8
  const testName = testInfo.title.replace(/\s+/g, "_").toLowerCase();
9
9
  const { jsonPath, screenshotPath } = await extractAndSaveElements(page, `${testName}_failure`);
10
- await testInfo.attach("dom-elements", { path: jsonPath, contentType: "application/json" });
11
- await testInfo.attach("failure-screenshot", { path: screenshotPath, contentType: "image/png" });
12
10
  const failureInfoPath = path.join(path.dirname(jsonPath), "failure_info.json");
13
11
  await analyzeFailure(page, testInfo, consoleLogs, networkFailures, { jsonPath, screenshotPath, failureInfoPath });
14
- await testInfo.attach("failure-info", { path: failureInfoPath, contentType: "application/json" });
15
12
  console.log("[Test Failure] DOM capture completed successfully");
16
13
  } catch (error) {
17
14
  console.error("[Test Failure] Error capturing DOM state:", error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/capture.ts"],"sourcesContent":["import type { Page, TestInfo } from '@playwright/test';\nimport path from 'path';\nimport { extractAndSaveElements } from './helpers/dom-extractor.js';\nimport { analyzeFailure } from './helpers/failure-analyzer.js';\nimport type { ConsoleEntry, NetworkFailure } from './helpers/failure-analyzer.js';\n\nexport async function captureFailure(\n page: Page,\n testInfo: TestInfo,\n consoleLogs: ConsoleEntry[],\n networkFailures: NetworkFailure[]\n): Promise<void> {\n if (testInfo.status === testInfo.expectedStatus) return;\n console.log('\\n[Test Failure] Capturing DOM state before trace...');\n try {\n const testName = testInfo.title.replace(/\\s+/g, '_').toLowerCase();\n const { jsonPath, screenshotPath } = await extractAndSaveElements(page, `${testName}_failure`);\n await testInfo.attach('dom-elements', { path: jsonPath, contentType: 'application/json' });\n await testInfo.attach('failure-screenshot', { path: screenshotPath, contentType: 'image/png' });\n const failureInfoPath = path.join(path.dirname(jsonPath), 'failure_info.json');\n await analyzeFailure(page, testInfo, consoleLogs, networkFailures, { jsonPath, screenshotPath, failureInfoPath });\n await testInfo.attach('failure-info', { path: failureInfoPath, contentType: 'application/json' });\n console.log('[Test Failure] DOM capture completed successfully');\n } catch (error) {\n console.error('[Test Failure] Error capturing DOM state:', error);\n }\n}\n"],"mappings":"AACA,OAAO,UAAU;AACjB,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAG/B,eAAsB,eACpB,MACA,UACA,aACA,iBACe;AACf,MAAI,SAAS,WAAW,SAAS,eAAgB;AACjD,UAAQ,IAAI,sDAAsD;AAClE,MAAI;AACF,UAAM,WAAW,SAAS,MAAM,QAAQ,QAAQ,GAAG,EAAE,YAAY;AACjE,UAAM,EAAE,UAAU,eAAe,IAAI,MAAM,uBAAuB,MAAM,GAAG,QAAQ,UAAU;AAC7F,UAAM,SAAS,OAAO,gBAAgB,EAAE,MAAM,UAAU,aAAa,mBAAmB,CAAC;AACzF,UAAM,SAAS,OAAO,sBAAsB,EAAE,MAAM,gBAAgB,aAAa,YAAY,CAAC;AAC9F,UAAM,kBAAkB,KAAK,KAAK,KAAK,QAAQ,QAAQ,GAAG,mBAAmB;AAC7E,UAAM,eAAe,MAAM,UAAU,aAAa,iBAAiB,EAAE,UAAU,gBAAgB,gBAAgB,CAAC;AAChH,UAAM,SAAS,OAAO,gBAAgB,EAAE,MAAM,iBAAiB,aAAa,mBAAmB,CAAC;AAChG,YAAQ,IAAI,mDAAmD;AAAA,EACjE,SAAS,OAAO;AACd,YAAQ,MAAM,6CAA6C,KAAK;AAAA,EAClE;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/capture.ts"],"sourcesContent":["import type { Page, TestInfo } from '@playwright/test';\nimport path from 'path';\nimport { extractAndSaveElements } from './helpers/dom-extractor.js';\nimport { analyzeFailure } from './helpers/failure-analyzer.js';\nimport type { ConsoleEntry, NetworkFailure } from './helpers/failure-analyzer.js';\n\nexport async function captureFailure(\n page: Page,\n testInfo: TestInfo,\n consoleLogs: ConsoleEntry[],\n networkFailures: NetworkFailure[]\n): Promise<void> {\n if (testInfo.status === testInfo.expectedStatus) return;\n console.log('\\n[Test Failure] Capturing DOM state before trace...');\n try {\n const testName = testInfo.title.replace(/\\s+/g, '_').toLowerCase();\n const { jsonPath, screenshotPath } = await extractAndSaveElements(page, `${testName}_failure`);\n const failureInfoPath = path.join(path.dirname(jsonPath), 'failure_info.json');\n await analyzeFailure(page, testInfo, consoleLogs, networkFailures, { jsonPath, screenshotPath, failureInfoPath });\n console.log('[Test Failure] DOM capture completed successfully');\n } catch (error) {\n console.error('[Test Failure] Error capturing DOM state:', error);\n }\n}\n"],"mappings":"AACA,OAAO,UAAU;AACjB,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAG/B,eAAsB,eACpB,MACA,UACA,aACA,iBACe;AACf,MAAI,SAAS,WAAW,SAAS,eAAgB;AACjD,UAAQ,IAAI,sDAAsD;AAClE,MAAI;AACF,UAAM,WAAW,SAAS,MAAM,QAAQ,QAAQ,GAAG,EAAE,YAAY;AACjE,UAAM,EAAE,UAAU,eAAe,IAAI,MAAM,uBAAuB,MAAM,GAAG,QAAQ,UAAU;AAC7F,UAAM,kBAAkB,KAAK,KAAK,KAAK,QAAQ,QAAQ,GAAG,mBAAmB;AAC7E,UAAM,eAAe,MAAM,UAAU,aAAa,iBAAiB,EAAE,UAAU,gBAAgB,gBAAgB,CAAC;AAChH,YAAQ,IAAI,mDAAmD;AAAA,EACjE,SAAS,OAAO;AACd,YAAQ,MAAM,6CAA6C,KAAK;AAAA,EAClE;AACF;","names":[]}
@@ -126,6 +126,7 @@ async function analyzeFailure(page, testInfo, consoleLogs, networkFailures, arti
126
126
  error: errorFirstLine,
127
127
  location,
128
128
  context,
129
+ pw_console: snippet,
129
130
  // v3 enriched
130
131
  test: {
131
132
  title: testInfo.title,
@@ -154,7 +155,6 @@ async function analyzeFailure(page, testInfo, consoleLogs, networkFailures, arti
154
155
  errorDetails: {
155
156
  message: errorMessage,
156
157
  stack: errorStack,
157
- pw_console: snippet,
158
158
  isTimeout,
159
159
  timeoutMs,
160
160
  causes
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/helpers/failure-analyzer.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport type { Page, TestInfo } from '@playwright/test';\n\n// ─── Exported Interfaces ───────────────────────────────────────────────────\n\nexport interface ConsoleEntry {\n type: string;\n text: string;\n timestamp: string;\n location?: { url?: string; lineNumber?: number; columnNumber?: number };\n}\n\nexport interface NetworkFailure {\n url: string;\n method: string;\n status: number;\n statusText: string;\n resourceType: string;\n timestamp: string;\n}\n\nexport interface FailureSelector {\n raw: string;\n method: string;\n value: string;\n}\n\nexport interface ErrorCause {\n message: string;\n stack: string;\n}\n\nexport interface ErrorEntry {\n message: string;\n stack: string;\n pw_console: string | null;\n isTimeout: boolean;\n timeoutMs: number | null;\n causes: ErrorCause[];\n}\n\nexport interface FailureAnalysis {\n // Backward-compatible original 5 fields\n selector: string;\n action: string;\n error: string;\n location: string;\n context: string;\n\n // Enriched fields (v2+)\n test: {\n title: string;\n titlePath: string[];\n file: string;\n line: number | null;\n project: string;\n retries: number;\n retry: number;\n duration: number;\n status: string | undefined;\n expectedStatus: string;\n tags: string[];\n };\n page: {\n url: string;\n title: string | null;\n viewport: { width: number; height: number } | null;\n };\n selectorDetails: FailureSelector | null;\n consoleLogs: ConsoleEntry[];\n consoleErrors: ConsoleEntry[];\n networkFailures: NetworkFailure[];\n errorDetails: {\n message: string;\n stack: string;\n pw_console: string | null;\n isTimeout: boolean;\n timeoutMs: number | null;\n causes: ErrorCause[];\n };\n errors: ErrorEntry[];\n timing: {\n testStart: string;\n failureCapture: string;\n duration: number;\n };\n artifacts: {\n domJsonPath: string;\n screenshotPath: string;\n failureInfoPath: string;\n };\n _version: 3;\n}\n\n// ─── Page Listener Setup ───────────────────────────────────────────────────\n\nexport function setupPageListeners(page: Page): {\n consoleLogs: ConsoleEntry[];\n networkFailures: NetworkFailure[];\n} {\n const consoleLogs: ConsoleEntry[] = [];\n const networkFailures: NetworkFailure[] = [];\n const MAX_CONSOLE_ENTRIES = 500;\n\n page.on('console', (msg) => {\n if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;\n const loc = msg.location();\n consoleLogs.push({\n type: msg.type(),\n text: msg.text(),\n timestamp: new Date().toISOString(),\n location: {\n url: loc.url,\n lineNumber: loc.lineNumber,\n columnNumber: loc.columnNumber,\n },\n });\n });\n\n page.on('pageerror', (err) => {\n if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;\n consoleLogs.push({\n type: 'pageerror',\n text: err.message,\n timestamp: new Date().toISOString(),\n });\n });\n\n page.on('response', (response) => {\n if (response.status() >= 400) {\n networkFailures.push({\n url: response.url(),\n method: response.request().method(),\n status: response.status(),\n statusText: response.statusText(),\n resourceType: response.request().resourceType(),\n timestamp: new Date().toISOString(),\n });\n }\n });\n\n page.on('requestfailed', (request) => {\n networkFailures.push({\n url: request.url(),\n method: request.method(),\n status: 0,\n statusText: request.failure()?.errorText ?? 'Request failed',\n resourceType: request.resourceType(),\n timestamp: new Date().toISOString(),\n });\n });\n\n return { consoleLogs, networkFailures };\n}\n\n// ─── Failure Analysis ──────────────────────────────────────────────────────\n\nexport async function analyzeFailure(\n page: Page,\n testInfo: TestInfo,\n consoleLogs: ConsoleEntry[],\n networkFailures: NetworkFailure[],\n artifacts: { jsonPath: string; screenshotPath: string; failureInfoPath: string }\n): Promise<FailureAnalysis> {\n const error = (testInfo.error ?? testInfo.errors?.[0] ?? {}) as { message?: string; stack?: string };\n const errorMessage = error.message ?? '';\n const errorStack = error.stack ?? '';\n\n const now = new Date();\n const duration = testInfo.duration ?? 0;\n const testStart = new Date(now.getTime() - duration).toISOString();\n const failureCapture = now.toISOString();\n\n // Original 5 fields (backward compat)\n const selector = extractSelector(errorMessage);\n const action = extractAction(errorMessage);\n const errorFirstLine = errorMessage.split('\\n')[0];\n const location = extractLocation(errorStack, testInfo);\n const context = errorMessage;\n\n // Page state\n let pageTitle: string | null = null;\n try {\n pageTitle = await page.title();\n } catch {\n // page may be closed\n }\n\n // Timeout detection\n const isTimeout = /timeout/i.test(errorMessage);\n const timeoutMs = extractTimeoutMs(errorMessage);\n\n // Snippet & cause chain for primary error\n const snippet = generateSnippet(errorStack, testInfo);\n const causes = extractCauseChain(error);\n\n // Build errors array from all test errors\n const rawErrors = testInfo.errors ?? [];\n const errors: ErrorEntry[] = rawErrors.map((err) => {\n const msg = (err as { message?: string }).message ?? '';\n const stk = (err as { stack?: string }).stack ?? '';\n return {\n message: msg,\n stack: stk,\n pw_console: generateSnippet(stk, testInfo),\n isTimeout: /timeout/i.test(msg),\n timeoutMs: extractTimeoutMs(msg),\n causes: extractCauseChain(err),\n };\n });\n\n const analysis: FailureAnalysis = {\n // Backward-compatible\n selector,\n action,\n error: errorFirstLine,\n location,\n context,\n\n // v3 enriched\n test: {\n title: testInfo.title,\n titlePath: testInfo.titlePath,\n file: testInfo.file,\n line: (testInfo as unknown as { line?: number }).line ?? null,\n project: testInfo.project.name,\n retries: testInfo.project.retries,\n retry: testInfo.retry,\n duration,\n status: testInfo.status,\n expectedStatus: testInfo.expectedStatus,\n tags: (testInfo as unknown as { tags?: string[] }).tags ?? [],\n },\n page: {\n url: page.url(),\n title: pageTitle,\n viewport: page.viewportSize(),\n },\n selectorDetails: extractSelectorDetails(errorMessage),\n consoleLogs,\n consoleErrors: consoleLogs.filter(\n (e) => e.type === 'error' || e.type === 'pageerror'\n ),\n networkFailures,\n errorDetails: {\n message: errorMessage,\n stack: errorStack,\n pw_console: snippet,\n isTimeout,\n timeoutMs,\n causes,\n },\n errors,\n timing: {\n testStart,\n failureCapture,\n duration,\n },\n artifacts: {\n domJsonPath: artifacts.jsonPath,\n screenshotPath: artifacts.screenshotPath,\n failureInfoPath: artifacts.failureInfoPath,\n },\n _version: 3,\n };\n\n fs.writeFileSync(artifacts.failureInfoPath, JSON.stringify(analysis, null, 2));\n\n return analysis;\n}\n\n// ─── Snippet / Cause / Timeout Helpers ────────────────────────────────────\n\nfunction generateSnippet(stack: string, testInfo: TestInfo): string | null {\n const escaped = testInfo.file.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = stack.match(new RegExp(`${escaped}:(\\\\d+):(\\\\d+)`));\n if (!match) return null;\n\n const lineNumber = parseInt(match[1], 10);\n let lines: string[];\n try {\n lines = fs.readFileSync(testInfo.file, 'utf-8').split('\\n');\n } catch {\n return null;\n }\n\n const start = Math.max(0, lineNumber - 5); // 4 lines before (0-indexed: lineNumber-1 is the error line)\n const end = Math.min(lines.length, lineNumber + 3); // 3 lines after\n const slice = lines.slice(start, end);\n\n const maxLineNum = end;\n const gutterWidth = String(maxLineNum).length;\n\n return slice\n .map((text, i) => {\n const num = start + i + 1;\n const marker = num === lineNumber ? '>' : ' ';\n const padded = String(num).padStart(gutterWidth);\n return `${marker} ${padded} | ${text}`;\n })\n .join('\\n');\n}\n\nfunction extractCauseChain(error: unknown): ErrorCause[] {\n const causes: ErrorCause[] = [];\n const seen = new WeakSet<object>();\n let current = error;\n\n for (let depth = 0; depth < 10; depth++) {\n if (current == null || typeof current !== 'object') break;\n const cause = (current as { cause?: unknown }).cause;\n if (cause == null || typeof cause !== 'object') break;\n if (seen.has(cause as object)) break;\n seen.add(cause as object);\n\n const causeErr = cause as { message?: string; stack?: string };\n causes.push({\n message: causeErr.message ?? String(cause),\n stack: causeErr.stack ?? '',\n });\n current = cause;\n }\n\n return causes;\n}\n\nfunction extractTimeoutMs(msg: string): number | null {\n if (!/timeout/i.test(msg)) return null;\n const match = msg.match(/(\\d+)\\s*ms/);\n return match ? parseInt(match[1], 10) : null;\n}\n\n// ─── Private Helpers ───────────────────────────────────────────────────────\n\nfunction extractSelector(msg: string): string {\n const match =\n msg.match(/locator\\(['\"](.+?)['\"]\\)/) ||\n msg.match(/getByRole\\(['\"](.+?)['\"]\\)/) ||\n msg.match(/getByText\\(['\"](.+?)['\"]\\)/);\n return match ? match[1] : '';\n}\n\nfunction extractAction(msg: string): string {\n if (msg.includes('.click')) return 'click';\n if (msg.includes('.fill')) return 'fill';\n if (msg.includes('.type')) return 'type';\n if (msg.includes('.hover')) return 'hover';\n if (msg.includes('.check')) return 'check';\n if (msg.includes('.uncheck')) return 'uncheck';\n if (msg.includes('.select')) return 'select';\n if (msg.includes('.press')) return 'press';\n if (msg.includes('.scroll')) return 'scroll';\n if (msg.includes('.drag')) return 'drag';\n if (/timeout/i.test(msg)) return 'wait';\n return '';\n}\n\nfunction extractLocation(stack: string, testInfo: TestInfo): string {\n const escaped = testInfo.file.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = stack.match(new RegExp(`${escaped}:(\\\\d+):(\\\\d+)`));\n if (match) {\n return `${path.basename(testInfo.file)}:${match[1]}`;\n }\n return '';\n}\n\nfunction extractSelectorDetails(msg: string): FailureSelector | null {\n const patterns: Array<{ method: string; regex: RegExp }> = [\n { method: 'locator', regex: /locator\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByRole', regex: /getByRole\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByText', regex: /getByText\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByTestId', regex: /getByTestId\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByLabel', regex: /getByLabel\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByPlaceholder', regex: /getByPlaceholder\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByAltText', regex: /getByAltText\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByTitle', regex: /getByTitle\\(['\"](.+?)['\"]\\)/ },\n { method: 'frameLocator', regex: /frameLocator\\(['\"](.+?)['\"]\\)/ },\n ];\n\n for (const { method, regex } of patterns) {\n const match = msg.match(regex);\n if (match) {\n return { raw: match[0], method, value: match[1] };\n }\n }\n return null;\n}\n"],"mappings":"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","names":["fs","path"]}
1
+ {"version":3,"sources":["../../src/helpers/failure-analyzer.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport type { Page, TestInfo } from '@playwright/test';\n\n// ─── Exported Interfaces ───────────────────────────────────────────────────\n\nexport interface ConsoleEntry {\n type: string;\n text: string;\n timestamp: string;\n location?: { url?: string; lineNumber?: number; columnNumber?: number };\n}\n\nexport interface NetworkFailure {\n url: string;\n method: string;\n status: number;\n statusText: string;\n resourceType: string;\n timestamp: string;\n}\n\nexport interface FailureSelector {\n raw: string;\n method: string;\n value: string;\n}\n\nexport interface ErrorCause {\n message: string;\n stack: string;\n}\n\nexport interface ErrorEntry {\n message: string;\n stack: string;\n pw_console: string | null;\n isTimeout: boolean;\n timeoutMs: number | null;\n causes: ErrorCause[];\n}\n\nexport interface FailureAnalysis {\n // Backward-compatible original 5 fields\n selector: string;\n action: string;\n error: string;\n location: string;\n context: string;\n pw_console: string | null;\n\n // Enriched fields (v2+)\n test: {\n title: string;\n titlePath: string[];\n file: string;\n line: number | null;\n project: string;\n retries: number;\n retry: number;\n duration: number;\n status: string | undefined;\n expectedStatus: string;\n tags: string[];\n };\n page: {\n url: string;\n title: string | null;\n viewport: { width: number; height: number } | null;\n };\n selectorDetails: FailureSelector | null;\n consoleLogs: ConsoleEntry[];\n consoleErrors: ConsoleEntry[];\n networkFailures: NetworkFailure[];\n errorDetails: {\n message: string;\n stack: string;\n isTimeout: boolean;\n timeoutMs: number | null;\n causes: ErrorCause[];\n };\n errors: ErrorEntry[];\n timing: {\n testStart: string;\n failureCapture: string;\n duration: number;\n };\n artifacts: {\n domJsonPath: string;\n screenshotPath: string;\n failureInfoPath: string;\n };\n _version: 3;\n}\n\n// ─── Page Listener Setup ───────────────────────────────────────────────────\n\nexport function setupPageListeners(page: Page): {\n consoleLogs: ConsoleEntry[];\n networkFailures: NetworkFailure[];\n} {\n const consoleLogs: ConsoleEntry[] = [];\n const networkFailures: NetworkFailure[] = [];\n const MAX_CONSOLE_ENTRIES = 500;\n\n page.on('console', (msg) => {\n if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;\n const loc = msg.location();\n consoleLogs.push({\n type: msg.type(),\n text: msg.text(),\n timestamp: new Date().toISOString(),\n location: {\n url: loc.url,\n lineNumber: loc.lineNumber,\n columnNumber: loc.columnNumber,\n },\n });\n });\n\n page.on('pageerror', (err) => {\n if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;\n consoleLogs.push({\n type: 'pageerror',\n text: err.message,\n timestamp: new Date().toISOString(),\n });\n });\n\n page.on('response', (response) => {\n if (response.status() >= 400) {\n networkFailures.push({\n url: response.url(),\n method: response.request().method(),\n status: response.status(),\n statusText: response.statusText(),\n resourceType: response.request().resourceType(),\n timestamp: new Date().toISOString(),\n });\n }\n });\n\n page.on('requestfailed', (request) => {\n networkFailures.push({\n url: request.url(),\n method: request.method(),\n status: 0,\n statusText: request.failure()?.errorText ?? 'Request failed',\n resourceType: request.resourceType(),\n timestamp: new Date().toISOString(),\n });\n });\n\n return { consoleLogs, networkFailures };\n}\n\n// ─── Failure Analysis ──────────────────────────────────────────────────────\n\nexport async function analyzeFailure(\n page: Page,\n testInfo: TestInfo,\n consoleLogs: ConsoleEntry[],\n networkFailures: NetworkFailure[],\n artifacts: { jsonPath: string; screenshotPath: string; failureInfoPath: string }\n): Promise<FailureAnalysis> {\n const error = (testInfo.error ?? testInfo.errors?.[0] ?? {}) as { message?: string; stack?: string };\n const errorMessage = error.message ?? '';\n const errorStack = error.stack ?? '';\n\n const now = new Date();\n const duration = testInfo.duration ?? 0;\n const testStart = new Date(now.getTime() - duration).toISOString();\n const failureCapture = now.toISOString();\n\n // Original 5 fields (backward compat)\n const selector = extractSelector(errorMessage);\n const action = extractAction(errorMessage);\n const errorFirstLine = errorMessage.split('\\n')[0];\n const location = extractLocation(errorStack, testInfo);\n const context = errorMessage;\n\n // Page state\n let pageTitle: string | null = null;\n try {\n pageTitle = await page.title();\n } catch {\n // page may be closed\n }\n\n // Timeout detection\n const isTimeout = /timeout/i.test(errorMessage);\n const timeoutMs = extractTimeoutMs(errorMessage);\n\n // Snippet & cause chain for primary error\n const snippet = generateSnippet(errorStack, testInfo);\n const causes = extractCauseChain(error);\n\n // Build errors array from all test errors\n const rawErrors = testInfo.errors ?? [];\n const errors: ErrorEntry[] = rawErrors.map((err) => {\n const msg = (err as { message?: string }).message ?? '';\n const stk = (err as { stack?: string }).stack ?? '';\n return {\n message: msg,\n stack: stk,\n pw_console: generateSnippet(stk, testInfo),\n isTimeout: /timeout/i.test(msg),\n timeoutMs: extractTimeoutMs(msg),\n causes: extractCauseChain(err),\n };\n });\n\n const analysis: FailureAnalysis = {\n // Backward-compatible\n selector,\n action,\n error: errorFirstLine,\n location,\n context,\n pw_console: snippet,\n\n // v3 enriched\n test: {\n title: testInfo.title,\n titlePath: testInfo.titlePath,\n file: testInfo.file,\n line: (testInfo as unknown as { line?: number }).line ?? null,\n project: testInfo.project.name,\n retries: testInfo.project.retries,\n retry: testInfo.retry,\n duration,\n status: testInfo.status,\n expectedStatus: testInfo.expectedStatus,\n tags: (testInfo as unknown as { tags?: string[] }).tags ?? [],\n },\n page: {\n url: page.url(),\n title: pageTitle,\n viewport: page.viewportSize(),\n },\n selectorDetails: extractSelectorDetails(errorMessage),\n consoleLogs,\n consoleErrors: consoleLogs.filter(\n (e) => e.type === 'error' || e.type === 'pageerror'\n ),\n networkFailures,\n errorDetails: {\n message: errorMessage,\n stack: errorStack,\n isTimeout,\n timeoutMs,\n causes,\n },\n errors,\n timing: {\n testStart,\n failureCapture,\n duration,\n },\n artifacts: {\n domJsonPath: artifacts.jsonPath,\n screenshotPath: artifacts.screenshotPath,\n failureInfoPath: artifacts.failureInfoPath,\n },\n _version: 3,\n };\n\n fs.writeFileSync(artifacts.failureInfoPath, JSON.stringify(analysis, null, 2));\n\n return analysis;\n}\n\n// ─── Snippet / Cause / Timeout Helpers ────────────────────────────────────\n\nfunction generateSnippet(stack: string, testInfo: TestInfo): string | null {\n const escaped = testInfo.file.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = stack.match(new RegExp(`${escaped}:(\\\\d+):(\\\\d+)`));\n if (!match) return null;\n\n const lineNumber = parseInt(match[1], 10);\n let lines: string[];\n try {\n lines = fs.readFileSync(testInfo.file, 'utf-8').split('\\n');\n } catch {\n return null;\n }\n\n const start = Math.max(0, lineNumber - 5); // 4 lines before (0-indexed: lineNumber-1 is the error line)\n const end = Math.min(lines.length, lineNumber + 3); // 3 lines after\n const slice = lines.slice(start, end);\n\n const maxLineNum = end;\n const gutterWidth = String(maxLineNum).length;\n\n return slice\n .map((text, i) => {\n const num = start + i + 1;\n const marker = num === lineNumber ? '>' : ' ';\n const padded = String(num).padStart(gutterWidth);\n return `${marker} ${padded} | ${text}`;\n })\n .join('\\n');\n}\n\nfunction extractCauseChain(error: unknown): ErrorCause[] {\n const causes: ErrorCause[] = [];\n const seen = new WeakSet<object>();\n let current = error;\n\n for (let depth = 0; depth < 10; depth++) {\n if (current == null || typeof current !== 'object') break;\n const cause = (current as { cause?: unknown }).cause;\n if (cause == null || typeof cause !== 'object') break;\n if (seen.has(cause as object)) break;\n seen.add(cause as object);\n\n const causeErr = cause as { message?: string; stack?: string };\n causes.push({\n message: causeErr.message ?? String(cause),\n stack: causeErr.stack ?? '',\n });\n current = cause;\n }\n\n return causes;\n}\n\nfunction extractTimeoutMs(msg: string): number | null {\n if (!/timeout/i.test(msg)) return null;\n const match = msg.match(/(\\d+)\\s*ms/);\n return match ? parseInt(match[1], 10) : null;\n}\n\n// ─── Private Helpers ───────────────────────────────────────────────────────\n\nfunction extractSelector(msg: string): string {\n const match =\n msg.match(/locator\\(['\"](.+?)['\"]\\)/) ||\n msg.match(/getByRole\\(['\"](.+?)['\"]\\)/) ||\n msg.match(/getByText\\(['\"](.+?)['\"]\\)/);\n return match ? match[1] : '';\n}\n\nfunction extractAction(msg: string): string {\n if (msg.includes('.click')) return 'click';\n if (msg.includes('.fill')) return 'fill';\n if (msg.includes('.type')) return 'type';\n if (msg.includes('.hover')) return 'hover';\n if (msg.includes('.check')) return 'check';\n if (msg.includes('.uncheck')) return 'uncheck';\n if (msg.includes('.select')) return 'select';\n if (msg.includes('.press')) return 'press';\n if (msg.includes('.scroll')) return 'scroll';\n if (msg.includes('.drag')) return 'drag';\n if (/timeout/i.test(msg)) return 'wait';\n return '';\n}\n\nfunction extractLocation(stack: string, testInfo: TestInfo): string {\n const escaped = testInfo.file.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = stack.match(new RegExp(`${escaped}:(\\\\d+):(\\\\d+)`));\n if (match) {\n return `${path.basename(testInfo.file)}:${match[1]}`;\n }\n return '';\n}\n\nfunction extractSelectorDetails(msg: string): FailureSelector | null {\n const patterns: Array<{ method: string; regex: RegExp }> = [\n { method: 'locator', regex: /locator\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByRole', regex: /getByRole\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByText', regex: /getByText\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByTestId', regex: /getByTestId\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByLabel', regex: /getByLabel\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByPlaceholder', regex: /getByPlaceholder\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByAltText', regex: /getByAltText\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByTitle', regex: /getByTitle\\(['\"](.+?)['\"]\\)/ },\n { method: 'frameLocator', regex: /frameLocator\\(['\"](.+?)['\"]\\)/ },\n ];\n\n for (const { method, regex } of patterns) {\n const match = msg.match(regex);\n if (match) {\n return { raw: match[0], method, value: match[1] };\n }\n }\n return null;\n}\n"],"mappings":"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","names":["fs","path"]}
@@ -41,6 +41,7 @@ interface FailureAnalysis {
41
41
  error: string;
42
42
  location: string;
43
43
  context: string;
44
+ pw_console: string | null;
44
45
  test: {
45
46
  title: string;
46
47
  titlePath: string[];
@@ -69,7 +70,6 @@ interface FailureAnalysis {
69
70
  errorDetails: {
70
71
  message: string;
71
72
  stack: string;
72
- pw_console: string | null;
73
73
  isTimeout: boolean;
74
74
  timeoutMs: number | null;
75
75
  causes: ErrorCause[];
@@ -41,6 +41,7 @@ interface FailureAnalysis {
41
41
  error: string;
42
42
  location: string;
43
43
  context: string;
44
+ pw_console: string | null;
44
45
  test: {
45
46
  title: string;
46
47
  titlePath: string[];
@@ -69,7 +70,6 @@ interface FailureAnalysis {
69
70
  errorDetails: {
70
71
  message: string;
71
72
  stack: string;
72
- pw_console: string | null;
73
73
  isTimeout: boolean;
74
74
  timeoutMs: number | null;
75
75
  causes: ErrorCause[];
@@ -92,6 +92,7 @@ async function analyzeFailure(page, testInfo, consoleLogs, networkFailures, arti
92
92
  error: errorFirstLine,
93
93
  location,
94
94
  context,
95
+ pw_console: snippet,
95
96
  // v3 enriched
96
97
  test: {
97
98
  title: testInfo.title,
@@ -120,7 +121,6 @@ async function analyzeFailure(page, testInfo, consoleLogs, networkFailures, arti
120
121
  errorDetails: {
121
122
  message: errorMessage,
122
123
  stack: errorStack,
123
- pw_console: snippet,
124
124
  isTimeout,
125
125
  timeoutMs,
126
126
  causes
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/helpers/failure-analyzer.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport type { Page, TestInfo } from '@playwright/test';\n\n// ─── Exported Interfaces ───────────────────────────────────────────────────\n\nexport interface ConsoleEntry {\n type: string;\n text: string;\n timestamp: string;\n location?: { url?: string; lineNumber?: number; columnNumber?: number };\n}\n\nexport interface NetworkFailure {\n url: string;\n method: string;\n status: number;\n statusText: string;\n resourceType: string;\n timestamp: string;\n}\n\nexport interface FailureSelector {\n raw: string;\n method: string;\n value: string;\n}\n\nexport interface ErrorCause {\n message: string;\n stack: string;\n}\n\nexport interface ErrorEntry {\n message: string;\n stack: string;\n pw_console: string | null;\n isTimeout: boolean;\n timeoutMs: number | null;\n causes: ErrorCause[];\n}\n\nexport interface FailureAnalysis {\n // Backward-compatible original 5 fields\n selector: string;\n action: string;\n error: string;\n location: string;\n context: string;\n\n // Enriched fields (v2+)\n test: {\n title: string;\n titlePath: string[];\n file: string;\n line: number | null;\n project: string;\n retries: number;\n retry: number;\n duration: number;\n status: string | undefined;\n expectedStatus: string;\n tags: string[];\n };\n page: {\n url: string;\n title: string | null;\n viewport: { width: number; height: number } | null;\n };\n selectorDetails: FailureSelector | null;\n consoleLogs: ConsoleEntry[];\n consoleErrors: ConsoleEntry[];\n networkFailures: NetworkFailure[];\n errorDetails: {\n message: string;\n stack: string;\n pw_console: string | null;\n isTimeout: boolean;\n timeoutMs: number | null;\n causes: ErrorCause[];\n };\n errors: ErrorEntry[];\n timing: {\n testStart: string;\n failureCapture: string;\n duration: number;\n };\n artifacts: {\n domJsonPath: string;\n screenshotPath: string;\n failureInfoPath: string;\n };\n _version: 3;\n}\n\n// ─── Page Listener Setup ───────────────────────────────────────────────────\n\nexport function setupPageListeners(page: Page): {\n consoleLogs: ConsoleEntry[];\n networkFailures: NetworkFailure[];\n} {\n const consoleLogs: ConsoleEntry[] = [];\n const networkFailures: NetworkFailure[] = [];\n const MAX_CONSOLE_ENTRIES = 500;\n\n page.on('console', (msg) => {\n if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;\n const loc = msg.location();\n consoleLogs.push({\n type: msg.type(),\n text: msg.text(),\n timestamp: new Date().toISOString(),\n location: {\n url: loc.url,\n lineNumber: loc.lineNumber,\n columnNumber: loc.columnNumber,\n },\n });\n });\n\n page.on('pageerror', (err) => {\n if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;\n consoleLogs.push({\n type: 'pageerror',\n text: err.message,\n timestamp: new Date().toISOString(),\n });\n });\n\n page.on('response', (response) => {\n if (response.status() >= 400) {\n networkFailures.push({\n url: response.url(),\n method: response.request().method(),\n status: response.status(),\n statusText: response.statusText(),\n resourceType: response.request().resourceType(),\n timestamp: new Date().toISOString(),\n });\n }\n });\n\n page.on('requestfailed', (request) => {\n networkFailures.push({\n url: request.url(),\n method: request.method(),\n status: 0,\n statusText: request.failure()?.errorText ?? 'Request failed',\n resourceType: request.resourceType(),\n timestamp: new Date().toISOString(),\n });\n });\n\n return { consoleLogs, networkFailures };\n}\n\n// ─── Failure Analysis ──────────────────────────────────────────────────────\n\nexport async function analyzeFailure(\n page: Page,\n testInfo: TestInfo,\n consoleLogs: ConsoleEntry[],\n networkFailures: NetworkFailure[],\n artifacts: { jsonPath: string; screenshotPath: string; failureInfoPath: string }\n): Promise<FailureAnalysis> {\n const error = (testInfo.error ?? testInfo.errors?.[0] ?? {}) as { message?: string; stack?: string };\n const errorMessage = error.message ?? '';\n const errorStack = error.stack ?? '';\n\n const now = new Date();\n const duration = testInfo.duration ?? 0;\n const testStart = new Date(now.getTime() - duration).toISOString();\n const failureCapture = now.toISOString();\n\n // Original 5 fields (backward compat)\n const selector = extractSelector(errorMessage);\n const action = extractAction(errorMessage);\n const errorFirstLine = errorMessage.split('\\n')[0];\n const location = extractLocation(errorStack, testInfo);\n const context = errorMessage;\n\n // Page state\n let pageTitle: string | null = null;\n try {\n pageTitle = await page.title();\n } catch {\n // page may be closed\n }\n\n // Timeout detection\n const isTimeout = /timeout/i.test(errorMessage);\n const timeoutMs = extractTimeoutMs(errorMessage);\n\n // Snippet & cause chain for primary error\n const snippet = generateSnippet(errorStack, testInfo);\n const causes = extractCauseChain(error);\n\n // Build errors array from all test errors\n const rawErrors = testInfo.errors ?? [];\n const errors: ErrorEntry[] = rawErrors.map((err) => {\n const msg = (err as { message?: string }).message ?? '';\n const stk = (err as { stack?: string }).stack ?? '';\n return {\n message: msg,\n stack: stk,\n pw_console: generateSnippet(stk, testInfo),\n isTimeout: /timeout/i.test(msg),\n timeoutMs: extractTimeoutMs(msg),\n causes: extractCauseChain(err),\n };\n });\n\n const analysis: FailureAnalysis = {\n // Backward-compatible\n selector,\n action,\n error: errorFirstLine,\n location,\n context,\n\n // v3 enriched\n test: {\n title: testInfo.title,\n titlePath: testInfo.titlePath,\n file: testInfo.file,\n line: (testInfo as unknown as { line?: number }).line ?? null,\n project: testInfo.project.name,\n retries: testInfo.project.retries,\n retry: testInfo.retry,\n duration,\n status: testInfo.status,\n expectedStatus: testInfo.expectedStatus,\n tags: (testInfo as unknown as { tags?: string[] }).tags ?? [],\n },\n page: {\n url: page.url(),\n title: pageTitle,\n viewport: page.viewportSize(),\n },\n selectorDetails: extractSelectorDetails(errorMessage),\n consoleLogs,\n consoleErrors: consoleLogs.filter(\n (e) => e.type === 'error' || e.type === 'pageerror'\n ),\n networkFailures,\n errorDetails: {\n message: errorMessage,\n stack: errorStack,\n pw_console: snippet,\n isTimeout,\n timeoutMs,\n causes,\n },\n errors,\n timing: {\n testStart,\n failureCapture,\n duration,\n },\n artifacts: {\n domJsonPath: artifacts.jsonPath,\n screenshotPath: artifacts.screenshotPath,\n failureInfoPath: artifacts.failureInfoPath,\n },\n _version: 3,\n };\n\n fs.writeFileSync(artifacts.failureInfoPath, JSON.stringify(analysis, null, 2));\n\n return analysis;\n}\n\n// ─── Snippet / Cause / Timeout Helpers ────────────────────────────────────\n\nfunction generateSnippet(stack: string, testInfo: TestInfo): string | null {\n const escaped = testInfo.file.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = stack.match(new RegExp(`${escaped}:(\\\\d+):(\\\\d+)`));\n if (!match) return null;\n\n const lineNumber = parseInt(match[1], 10);\n let lines: string[];\n try {\n lines = fs.readFileSync(testInfo.file, 'utf-8').split('\\n');\n } catch {\n return null;\n }\n\n const start = Math.max(0, lineNumber - 5); // 4 lines before (0-indexed: lineNumber-1 is the error line)\n const end = Math.min(lines.length, lineNumber + 3); // 3 lines after\n const slice = lines.slice(start, end);\n\n const maxLineNum = end;\n const gutterWidth = String(maxLineNum).length;\n\n return slice\n .map((text, i) => {\n const num = start + i + 1;\n const marker = num === lineNumber ? '>' : ' ';\n const padded = String(num).padStart(gutterWidth);\n return `${marker} ${padded} | ${text}`;\n })\n .join('\\n');\n}\n\nfunction extractCauseChain(error: unknown): ErrorCause[] {\n const causes: ErrorCause[] = [];\n const seen = new WeakSet<object>();\n let current = error;\n\n for (let depth = 0; depth < 10; depth++) {\n if (current == null || typeof current !== 'object') break;\n const cause = (current as { cause?: unknown }).cause;\n if (cause == null || typeof cause !== 'object') break;\n if (seen.has(cause as object)) break;\n seen.add(cause as object);\n\n const causeErr = cause as { message?: string; stack?: string };\n causes.push({\n message: causeErr.message ?? String(cause),\n stack: causeErr.stack ?? '',\n });\n current = cause;\n }\n\n return causes;\n}\n\nfunction extractTimeoutMs(msg: string): number | null {\n if (!/timeout/i.test(msg)) return null;\n const match = msg.match(/(\\d+)\\s*ms/);\n return match ? parseInt(match[1], 10) : null;\n}\n\n// ─── Private Helpers ───────────────────────────────────────────────────────\n\nfunction extractSelector(msg: string): string {\n const match =\n msg.match(/locator\\(['\"](.+?)['\"]\\)/) ||\n msg.match(/getByRole\\(['\"](.+?)['\"]\\)/) ||\n msg.match(/getByText\\(['\"](.+?)['\"]\\)/);\n return match ? match[1] : '';\n}\n\nfunction extractAction(msg: string): string {\n if (msg.includes('.click')) return 'click';\n if (msg.includes('.fill')) return 'fill';\n if (msg.includes('.type')) return 'type';\n if (msg.includes('.hover')) return 'hover';\n if (msg.includes('.check')) return 'check';\n if (msg.includes('.uncheck')) return 'uncheck';\n if (msg.includes('.select')) return 'select';\n if (msg.includes('.press')) return 'press';\n if (msg.includes('.scroll')) return 'scroll';\n if (msg.includes('.drag')) return 'drag';\n if (/timeout/i.test(msg)) return 'wait';\n return '';\n}\n\nfunction extractLocation(stack: string, testInfo: TestInfo): string {\n const escaped = testInfo.file.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = stack.match(new RegExp(`${escaped}:(\\\\d+):(\\\\d+)`));\n if (match) {\n return `${path.basename(testInfo.file)}:${match[1]}`;\n }\n return '';\n}\n\nfunction extractSelectorDetails(msg: string): FailureSelector | null {\n const patterns: Array<{ method: string; regex: RegExp }> = [\n { method: 'locator', regex: /locator\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByRole', regex: /getByRole\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByText', regex: /getByText\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByTestId', regex: /getByTestId\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByLabel', regex: /getByLabel\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByPlaceholder', regex: /getByPlaceholder\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByAltText', regex: /getByAltText\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByTitle', regex: /getByTitle\\(['\"](.+?)['\"]\\)/ },\n { method: 'frameLocator', regex: /frameLocator\\(['\"](.+?)['\"]\\)/ },\n ];\n\n for (const { method, regex } of patterns) {\n const match = msg.match(regex);\n if (match) {\n return { raw: match[0], method, value: match[1] };\n }\n }\n return null;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/helpers/failure-analyzer.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport type { Page, TestInfo } from '@playwright/test';\n\n// ─── Exported Interfaces ───────────────────────────────────────────────────\n\nexport interface ConsoleEntry {\n type: string;\n text: string;\n timestamp: string;\n location?: { url?: string; lineNumber?: number; columnNumber?: number };\n}\n\nexport interface NetworkFailure {\n url: string;\n method: string;\n status: number;\n statusText: string;\n resourceType: string;\n timestamp: string;\n}\n\nexport interface FailureSelector {\n raw: string;\n method: string;\n value: string;\n}\n\nexport interface ErrorCause {\n message: string;\n stack: string;\n}\n\nexport interface ErrorEntry {\n message: string;\n stack: string;\n pw_console: string | null;\n isTimeout: boolean;\n timeoutMs: number | null;\n causes: ErrorCause[];\n}\n\nexport interface FailureAnalysis {\n // Backward-compatible original 5 fields\n selector: string;\n action: string;\n error: string;\n location: string;\n context: string;\n pw_console: string | null;\n\n // Enriched fields (v2+)\n test: {\n title: string;\n titlePath: string[];\n file: string;\n line: number | null;\n project: string;\n retries: number;\n retry: number;\n duration: number;\n status: string | undefined;\n expectedStatus: string;\n tags: string[];\n };\n page: {\n url: string;\n title: string | null;\n viewport: { width: number; height: number } | null;\n };\n selectorDetails: FailureSelector | null;\n consoleLogs: ConsoleEntry[];\n consoleErrors: ConsoleEntry[];\n networkFailures: NetworkFailure[];\n errorDetails: {\n message: string;\n stack: string;\n isTimeout: boolean;\n timeoutMs: number | null;\n causes: ErrorCause[];\n };\n errors: ErrorEntry[];\n timing: {\n testStart: string;\n failureCapture: string;\n duration: number;\n };\n artifacts: {\n domJsonPath: string;\n screenshotPath: string;\n failureInfoPath: string;\n };\n _version: 3;\n}\n\n// ─── Page Listener Setup ───────────────────────────────────────────────────\n\nexport function setupPageListeners(page: Page): {\n consoleLogs: ConsoleEntry[];\n networkFailures: NetworkFailure[];\n} {\n const consoleLogs: ConsoleEntry[] = [];\n const networkFailures: NetworkFailure[] = [];\n const MAX_CONSOLE_ENTRIES = 500;\n\n page.on('console', (msg) => {\n if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;\n const loc = msg.location();\n consoleLogs.push({\n type: msg.type(),\n text: msg.text(),\n timestamp: new Date().toISOString(),\n location: {\n url: loc.url,\n lineNumber: loc.lineNumber,\n columnNumber: loc.columnNumber,\n },\n });\n });\n\n page.on('pageerror', (err) => {\n if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;\n consoleLogs.push({\n type: 'pageerror',\n text: err.message,\n timestamp: new Date().toISOString(),\n });\n });\n\n page.on('response', (response) => {\n if (response.status() >= 400) {\n networkFailures.push({\n url: response.url(),\n method: response.request().method(),\n status: response.status(),\n statusText: response.statusText(),\n resourceType: response.request().resourceType(),\n timestamp: new Date().toISOString(),\n });\n }\n });\n\n page.on('requestfailed', (request) => {\n networkFailures.push({\n url: request.url(),\n method: request.method(),\n status: 0,\n statusText: request.failure()?.errorText ?? 'Request failed',\n resourceType: request.resourceType(),\n timestamp: new Date().toISOString(),\n });\n });\n\n return { consoleLogs, networkFailures };\n}\n\n// ─── Failure Analysis ──────────────────────────────────────────────────────\n\nexport async function analyzeFailure(\n page: Page,\n testInfo: TestInfo,\n consoleLogs: ConsoleEntry[],\n networkFailures: NetworkFailure[],\n artifacts: { jsonPath: string; screenshotPath: string; failureInfoPath: string }\n): Promise<FailureAnalysis> {\n const error = (testInfo.error ?? testInfo.errors?.[0] ?? {}) as { message?: string; stack?: string };\n const errorMessage = error.message ?? '';\n const errorStack = error.stack ?? '';\n\n const now = new Date();\n const duration = testInfo.duration ?? 0;\n const testStart = new Date(now.getTime() - duration).toISOString();\n const failureCapture = now.toISOString();\n\n // Original 5 fields (backward compat)\n const selector = extractSelector(errorMessage);\n const action = extractAction(errorMessage);\n const errorFirstLine = errorMessage.split('\\n')[0];\n const location = extractLocation(errorStack, testInfo);\n const context = errorMessage;\n\n // Page state\n let pageTitle: string | null = null;\n try {\n pageTitle = await page.title();\n } catch {\n // page may be closed\n }\n\n // Timeout detection\n const isTimeout = /timeout/i.test(errorMessage);\n const timeoutMs = extractTimeoutMs(errorMessage);\n\n // Snippet & cause chain for primary error\n const snippet = generateSnippet(errorStack, testInfo);\n const causes = extractCauseChain(error);\n\n // Build errors array from all test errors\n const rawErrors = testInfo.errors ?? [];\n const errors: ErrorEntry[] = rawErrors.map((err) => {\n const msg = (err as { message?: string }).message ?? '';\n const stk = (err as { stack?: string }).stack ?? '';\n return {\n message: msg,\n stack: stk,\n pw_console: generateSnippet(stk, testInfo),\n isTimeout: /timeout/i.test(msg),\n timeoutMs: extractTimeoutMs(msg),\n causes: extractCauseChain(err),\n };\n });\n\n const analysis: FailureAnalysis = {\n // Backward-compatible\n selector,\n action,\n error: errorFirstLine,\n location,\n context,\n pw_console: snippet,\n\n // v3 enriched\n test: {\n title: testInfo.title,\n titlePath: testInfo.titlePath,\n file: testInfo.file,\n line: (testInfo as unknown as { line?: number }).line ?? null,\n project: testInfo.project.name,\n retries: testInfo.project.retries,\n retry: testInfo.retry,\n duration,\n status: testInfo.status,\n expectedStatus: testInfo.expectedStatus,\n tags: (testInfo as unknown as { tags?: string[] }).tags ?? [],\n },\n page: {\n url: page.url(),\n title: pageTitle,\n viewport: page.viewportSize(),\n },\n selectorDetails: extractSelectorDetails(errorMessage),\n consoleLogs,\n consoleErrors: consoleLogs.filter(\n (e) => e.type === 'error' || e.type === 'pageerror'\n ),\n networkFailures,\n errorDetails: {\n message: errorMessage,\n stack: errorStack,\n isTimeout,\n timeoutMs,\n causes,\n },\n errors,\n timing: {\n testStart,\n failureCapture,\n duration,\n },\n artifacts: {\n domJsonPath: artifacts.jsonPath,\n screenshotPath: artifacts.screenshotPath,\n failureInfoPath: artifacts.failureInfoPath,\n },\n _version: 3,\n };\n\n fs.writeFileSync(artifacts.failureInfoPath, JSON.stringify(analysis, null, 2));\n\n return analysis;\n}\n\n// ─── Snippet / Cause / Timeout Helpers ────────────────────────────────────\n\nfunction generateSnippet(stack: string, testInfo: TestInfo): string | null {\n const escaped = testInfo.file.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = stack.match(new RegExp(`${escaped}:(\\\\d+):(\\\\d+)`));\n if (!match) return null;\n\n const lineNumber = parseInt(match[1], 10);\n let lines: string[];\n try {\n lines = fs.readFileSync(testInfo.file, 'utf-8').split('\\n');\n } catch {\n return null;\n }\n\n const start = Math.max(0, lineNumber - 5); // 4 lines before (0-indexed: lineNumber-1 is the error line)\n const end = Math.min(lines.length, lineNumber + 3); // 3 lines after\n const slice = lines.slice(start, end);\n\n const maxLineNum = end;\n const gutterWidth = String(maxLineNum).length;\n\n return slice\n .map((text, i) => {\n const num = start + i + 1;\n const marker = num === lineNumber ? '>' : ' ';\n const padded = String(num).padStart(gutterWidth);\n return `${marker} ${padded} | ${text}`;\n })\n .join('\\n');\n}\n\nfunction extractCauseChain(error: unknown): ErrorCause[] {\n const causes: ErrorCause[] = [];\n const seen = new WeakSet<object>();\n let current = error;\n\n for (let depth = 0; depth < 10; depth++) {\n if (current == null || typeof current !== 'object') break;\n const cause = (current as { cause?: unknown }).cause;\n if (cause == null || typeof cause !== 'object') break;\n if (seen.has(cause as object)) break;\n seen.add(cause as object);\n\n const causeErr = cause as { message?: string; stack?: string };\n causes.push({\n message: causeErr.message ?? String(cause),\n stack: causeErr.stack ?? '',\n });\n current = cause;\n }\n\n return causes;\n}\n\nfunction extractTimeoutMs(msg: string): number | null {\n if (!/timeout/i.test(msg)) return null;\n const match = msg.match(/(\\d+)\\s*ms/);\n return match ? parseInt(match[1], 10) : null;\n}\n\n// ─── Private Helpers ───────────────────────────────────────────────────────\n\nfunction extractSelector(msg: string): string {\n const match =\n msg.match(/locator\\(['\"](.+?)['\"]\\)/) ||\n msg.match(/getByRole\\(['\"](.+?)['\"]\\)/) ||\n msg.match(/getByText\\(['\"](.+?)['\"]\\)/);\n return match ? match[1] : '';\n}\n\nfunction extractAction(msg: string): string {\n if (msg.includes('.click')) return 'click';\n if (msg.includes('.fill')) return 'fill';\n if (msg.includes('.type')) return 'type';\n if (msg.includes('.hover')) return 'hover';\n if (msg.includes('.check')) return 'check';\n if (msg.includes('.uncheck')) return 'uncheck';\n if (msg.includes('.select')) return 'select';\n if (msg.includes('.press')) return 'press';\n if (msg.includes('.scroll')) return 'scroll';\n if (msg.includes('.drag')) return 'drag';\n if (/timeout/i.test(msg)) return 'wait';\n return '';\n}\n\nfunction extractLocation(stack: string, testInfo: TestInfo): string {\n const escaped = testInfo.file.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = stack.match(new RegExp(`${escaped}:(\\\\d+):(\\\\d+)`));\n if (match) {\n return `${path.basename(testInfo.file)}:${match[1]}`;\n }\n return '';\n}\n\nfunction extractSelectorDetails(msg: string): FailureSelector | null {\n const patterns: Array<{ method: string; regex: RegExp }> = [\n { method: 'locator', regex: /locator\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByRole', regex: /getByRole\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByText', regex: /getByText\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByTestId', regex: /getByTestId\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByLabel', regex: /getByLabel\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByPlaceholder', regex: /getByPlaceholder\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByAltText', regex: /getByAltText\\(['\"](.+?)['\"]\\)/ },\n { method: 'getByTitle', regex: /getByTitle\\(['\"](.+?)['\"]\\)/ },\n { method: 'frameLocator', regex: /frameLocator\\(['\"](.+?)['\"]\\)/ },\n ];\n\n for (const { method, regex } of patterns) {\n const match = msg.match(regex);\n if (match) {\n return { raw: match[0], method, value: match[1] };\n }\n }\n return null;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muuktest/amikoo-playwright",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Playwright DOM failure capture library. Automatically captures DOM state and screenshots when tests fail.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",