diff --git a/CHANGELOG.md b/CHANGELOG.md index 93a0d4c..cdd755c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ All notable changes to ReproCase are documented here. Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [Semantic Versioning](https://semver.org/). +## [0.7.1] — 2026-11-12 + +Special-audit follow-up to v0.7.0: one accuracy fix found by the A/B/C reverse audit. + +### Fixed + +- **Client-initiated fetch aborts are no longer reported** (accuracy / false positives): + SPAs cancel in-flight API requests constantly (stale queries, route changes, timeouts) via + AbortController. The `net::ERR_ABORTED` failure of a cancelled fetch/XHR is application + behaviour, not a page fault. Real network failures on fetch/XHR (connection refused, DNS, + reset, destroyed sockets) are still reported, and aborted subresources (images, scripts) + keep their existing coverage. + +### Also + +- Corrected the verified/suspected wording in issues.md (suspected covers both + not-yet-proven-reproducible and reproducible-but-informational console errors). +- CI: Chromium install step retries once on CDN connection drops. + +### Verification + +186/186 tests (25 files) incl. two new regression tests: client abort not reported + +socket-destroyed fetch still reported; real-site audit scan 9→8 findings with all planted +scenarios intact; npm audit 0. + ## [0.7.0] — 2026-11-08 Real-world reliability & adoption release. Validated against an independent matrix of diff --git a/package-lock.json b/package-lock.json index f51f6f5..b95f64c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "reprocase", - "version": "0.7.0", + "version": "0.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "reprocase", - "version": "0.7.0", + "version": "0.7.1", "license": "MIT", "dependencies": { "@playwright/test": "^1.49.0", diff --git a/package.json b/package.json index b8bfcf1..db969ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reprocase", - "version": "0.7.0", + "version": "0.7.1", "description": "Automated website fault detection & reproduction. Scans a site in a real browser, verifies each fault independently, and emits reproducible Playwright regression tests plus human- and CI-friendly reports.", "type": "module", "license": "MIT", diff --git a/src/scanner.ts b/src/scanner.ts index e9cfd21..6a808a3 100644 --- a/src/scanner.ts +++ b/src/scanner.ts @@ -403,10 +403,18 @@ export class Scanner { const u = req.url(); if (FAVICON_RE.test(new URL(u, page.url()).pathname)) return; if (!this.isFirstParty(u)) return; + // Client-initiated cancellation of an API request (AbortController, + // stale-query cancel, route change) is application behaviour, not a page + // fault: SPAs abort in-flight fetches constantly. Only the abort error + // code is filtered — real network failures on fetch/XHR (connection + // refused, DNS, reset) are still reported (v0.7.1 accuracy fix). + const rt = req.resourceType(); + const err = req.failure()?.errorText ?? ''; + if ((rt === 'xhr' || rt === 'fetch') && err.includes('ERR_ABORTED')) return; void obs({ type: 'resource-load-failure', url: page.url(), - evidence: { resourceUrl: u, failureText: req.failure()?.errorText ?? 'request failed' }, + evidence: { resourceUrl: u, failureText: err || 'request failed' }, steps: [...stepsBox.steps] }); }); diff --git a/tests/integration/api-noise.test.ts b/tests/integration/api-noise.test.ts index ef774c5..2040324 100644 --- a/tests/integration/api-noise.test.ts +++ b/tests/integration/api-noise.test.ts @@ -47,4 +47,82 @@ describe('API business statuses are not resource faults (v0.7.0 accuracy fix)', }, 120_000 ); + + it( + 'does not report a client-initiated fetch abort (stale-request cancel), but keeps real network failures', + async () => { + // Custom server (not the static fixture): needs a slow endpoint so the + // page's AbortController reliably cancels the request mid-flight, and a + // socket-killing endpoint to prove real network failures stay reported. + const http = await import('node:http'); + const srv = http.createServer((req, res) => { + if ((req.url || '').startsWith('/api/slow')) { + setTimeout(() => { + res.writeHead(200, { 'content-type': 'application/json' }); + res.end('{"status":"slow-ok"}'); + }, 1500); + return; + } + if ((req.url || '').startsWith('/api/kill-socket')) { + // Destroy the connection without a response: a real network failure + // (net::ERR_EMPTY_RESPONSE on the client) that must stay reported. + req.socket.destroy(); + return; + } + if ((req.url || '').startsWith('/refused')) { + res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); + res.end( + `
pending
` + ); + return; + } + res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); + res.end( + `pending
next` + ); + }); + await new Promise