@ia-qa/qa-discovery 0.1.0
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/README.md +378 -0
- package/ROADMAP.md +321 -0
- package/dist/ai/classify.d.ts +170 -0
- package/dist/ai/classify.js +431 -0
- package/dist/ai/classify.js.map +1 -0
- package/dist/browser/extractPage.d.ts +41 -0
- package/dist/browser/extractPage.js +427 -0
- package/dist/browser/extractPage.js.map +1 -0
- package/dist/browser/openables.d.ts +19 -0
- package/dist/browser/openables.js +127 -0
- package/dist/browser/openables.js.map +1 -0
- package/dist/capture/page.d.ts +128 -0
- package/dist/capture/page.js +78 -0
- package/dist/capture/page.js.map +1 -0
- package/dist/citations.d.ts +80 -0
- package/dist/citations.js +197 -0
- package/dist/citations.js.map +1 -0
- package/dist/classificationView.d.ts +118 -0
- package/dist/classificationView.js +178 -0
- package/dist/classificationView.js.map +1 -0
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.js +38 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/history.d.ts +1 -0
- package/dist/cli/history.js +35 -0
- package/dist/cli/history.js.map +1 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +172 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/login.d.ts +29 -0
- package/dist/cli/login.js +179 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/scan.d.ts +11 -0
- package/dist/cli/scan.js +246 -0
- package/dist/cli/scan.js.map +1 -0
- package/dist/cli-ai/index.d.ts +7 -0
- package/dist/cli-ai/index.js +360 -0
- package/dist/cli-ai/index.js.map +1 -0
- package/dist/config.d.ts +112 -0
- package/dist/config.js +178 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +12 -0
- package/dist/diff.js +43 -0
- package/dist/diff.js.map +1 -0
- package/dist/explore.d.ts +59 -0
- package/dist/explore.js +119 -0
- package/dist/explore.js.map +1 -0
- package/dist/healingLink.d.ts +30 -0
- package/dist/healingLink.js +127 -0
- package/dist/healingLink.js.map +1 -0
- package/dist/history.d.ts +38 -0
- package/dist/history.js +120 -0
- package/dist/history.js.map +1 -0
- package/dist/htmlReport.d.ts +2 -0
- package/dist/htmlReport.js +371 -0
- package/dist/htmlReport.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/sandbox.d.ts +9 -0
- package/dist/mcp/sandbox.js +82 -0
- package/dist/mcp/sandbox.js.map +1 -0
- package/dist/mcp/server.d.ts +120 -0
- package/dist/mcp/server.js +315 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/network.d.ts +60 -0
- package/dist/network.js +99 -0
- package/dist/network.js.map +1 -0
- package/dist/overview.d.ts +40 -0
- package/dist/overview.js +370 -0
- package/dist/overview.js.map +1 -0
- package/dist/overviewFile.d.ts +31 -0
- package/dist/overviewFile.js +101 -0
- package/dist/overviewFile.js.map +1 -0
- package/dist/scan.d.ts +159 -0
- package/dist/scan.js +402 -0
- package/dist/scan.js.map +1 -0
- package/dist/sharedCalls.d.ts +28 -0
- package/dist/sharedCalls.js +61 -0
- package/dist/sharedCalls.js.map +1 -0
- package/dist/taxonomy.d.ts +77 -0
- package/dist/taxonomy.js +153 -0
- package/dist/taxonomy.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* Passive network observation โ `page.on('response')`, never `page.route()`.
|
|
4
|
+
*
|
|
5
|
+
* `page.route()` is an interception API: every matched request must be resolved
|
|
6
|
+
* with continue()/fulfill()/abort(), or it hangs. `@ia-qa/self-healing`'s
|
|
7
|
+
* `explore.ts` already uses it for exactly one purpose โ actively ABORTING every
|
|
8
|
+
* non-GET request as a mutation-safety guard during `--deep` exploration. Reusing
|
|
9
|
+
* that API here, next to the one place in the ecosystem it means "block", would
|
|
10
|
+
* invite the exact footgun a passive listener architecturally cannot have: F1
|
|
11
|
+
* must never stall, drop, or mutate a request, only observe it.
|
|
12
|
+
*/
|
|
13
|
+
export interface CapturedResponse {
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
status: number;
|
|
17
|
+
resourceType: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ApiCall {
|
|
20
|
+
method: string;
|
|
21
|
+
urlPattern: string;
|
|
22
|
+
sameOrigin: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The call's own origin, verbatim. `sameOrigin` alone is not enough to act
|
|
25
|
+
* on: an app whose API lives on its own separate host (a Heroku backend, an
|
|
26
|
+
* api.* subdomain) has every one of its OWN calls marked cross-origin, in the
|
|
27
|
+
* same bucket as an ad tracker. Recording the origin lets a later stage group
|
|
28
|
+
* by it and decide which are first-party โ a judgment F1 does not make,
|
|
29
|
+
* because nothing in the DOM states it.
|
|
30
|
+
*/
|
|
31
|
+
origin: string;
|
|
32
|
+
status: number;
|
|
33
|
+
resourceType: string;
|
|
34
|
+
count: number;
|
|
35
|
+
}
|
|
36
|
+
export declare const DEFAULT_SAFE_QUERY_PARAMS: string[];
|
|
37
|
+
/**
|
|
38
|
+
* Attach a passive response listener to `page`. Returns a detach function โ
|
|
39
|
+
* call it as soon as this page's capture completes, so the listener never
|
|
40
|
+
* leaks into the next page's navigation (self-healing's `map` reuses one
|
|
41
|
+
* `Page` across every target in a run; this package does the same).
|
|
42
|
+
*/
|
|
43
|
+
export declare function attachNetworkListener(page: Page, onResponse: (r: CapturedResponse) => void): () => void;
|
|
44
|
+
export interface NormalizedUrl {
|
|
45
|
+
urlPattern: string;
|
|
46
|
+
sameOrigin: boolean;
|
|
47
|
+
origin: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Normalize a captured URL into a stable pattern: numeric/UUID path segments
|
|
51
|
+
* collapse to `:id`, query params are sorted, and a param's value is kept only
|
|
52
|
+
* when its key is in `safeParams` AND does not match `SENSITIVE_KEY_RE` โ every
|
|
53
|
+
* other key keeps just its name. Same-origin URLs return a path (`/api/cart?page=`);
|
|
54
|
+
* cross-origin URLs keep their origin (`https://analytics.example.com/collect`).
|
|
55
|
+
*/
|
|
56
|
+
export declare function normalizeApiUrl(rawUrl: string, pageOrigin: string, safeParams?: string[]): NormalizedUrl;
|
|
57
|
+
/** Fingerprint used to de-dupe within a page AND to detect shared calls across pages (see sharedCalls.ts). */
|
|
58
|
+
export declare function apiCallFingerprint(call: Pick<ApiCall, 'method' | 'urlPattern' | 'sameOrigin'>): string;
|
|
59
|
+
/** Aggregate raw captured responses for one page into de-duplicated, normalized API calls. */
|
|
60
|
+
export declare function aggregateApiCalls(responses: CapturedResponse[], pageOrigin: string, safeParams?: string[]): ApiCall[];
|
package/dist/network.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_SAFE_QUERY_PARAMS = void 0;
|
|
4
|
+
exports.attachNetworkListener = attachNetworkListener;
|
|
5
|
+
exports.normalizeApiUrl = normalizeApiUrl;
|
|
6
|
+
exports.apiCallFingerprint = apiCallFingerprint;
|
|
7
|
+
exports.aggregateApiCalls = aggregateApiCalls;
|
|
8
|
+
/** Only these resource types are "API calls" โ document/script/style/image loads are not. */
|
|
9
|
+
const API_RESOURCE_TYPES = new Set(['fetch', 'xhr']);
|
|
10
|
+
exports.DEFAULT_SAFE_QUERY_PARAMS = [
|
|
11
|
+
'page', 'sort', 'order', 'limit', 'offset', 'q', 'filter',
|
|
12
|
+
'lang', 'locale', 'tab', 'view', 'category', 'type', 'status',
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Hard deny-override, checked before the allowlist. Defense in depth: an
|
|
16
|
+
* allowlist edit that adds a sensitive-looking key must not leak its value into
|
|
17
|
+
* a file meant to be committed and versioned.
|
|
18
|
+
*/
|
|
19
|
+
const SENSITIVE_KEY_RE = /token|key|secret|password|pass|auth|session|jwt|email|phone|ssn|card|cvv/i;
|
|
20
|
+
const NUMERIC_ID_RE = /^\d+$/;
|
|
21
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
22
|
+
/**
|
|
23
|
+
* Attach a passive response listener to `page`. Returns a detach function โ
|
|
24
|
+
* call it as soon as this page's capture completes, so the listener never
|
|
25
|
+
* leaks into the next page's navigation (self-healing's `map` reuses one
|
|
26
|
+
* `Page` across every target in a run; this package does the same).
|
|
27
|
+
*/
|
|
28
|
+
function attachNetworkListener(page, onResponse) {
|
|
29
|
+
const handler = (response) => {
|
|
30
|
+
const request = response.request();
|
|
31
|
+
onResponse({
|
|
32
|
+
method: request.method(),
|
|
33
|
+
url: response.url(),
|
|
34
|
+
status: response.status(),
|
|
35
|
+
resourceType: request.resourceType(),
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
page.on('response', handler);
|
|
39
|
+
return () => page.off('response', handler);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Normalize a captured URL into a stable pattern: numeric/UUID path segments
|
|
43
|
+
* collapse to `:id`, query params are sorted, and a param's value is kept only
|
|
44
|
+
* when its key is in `safeParams` AND does not match `SENSITIVE_KEY_RE` โ every
|
|
45
|
+
* other key keeps just its name. Same-origin URLs return a path (`/api/cart?page=`);
|
|
46
|
+
* cross-origin URLs keep their origin (`https://analytics.example.com/collect`).
|
|
47
|
+
*/
|
|
48
|
+
function normalizeApiUrl(rawUrl, pageOrigin, safeParams = exports.DEFAULT_SAFE_QUERY_PARAMS) {
|
|
49
|
+
let url;
|
|
50
|
+
try {
|
|
51
|
+
url = new URL(rawUrl);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { urlPattern: rawUrl, sameOrigin: true, origin: pageOrigin };
|
|
55
|
+
}
|
|
56
|
+
const sameOrigin = url.origin === pageOrigin;
|
|
57
|
+
const path = url.pathname
|
|
58
|
+
.split('/')
|
|
59
|
+
.map((seg) => (seg !== '' && (NUMERIC_ID_RE.test(seg) || UUID_RE.test(seg)) ? ':id' : seg))
|
|
60
|
+
.join('/');
|
|
61
|
+
const safe = new Set(safeParams.map((p) => p.toLowerCase()));
|
|
62
|
+
const parts = [];
|
|
63
|
+
for (const key of Array.from(url.searchParams.keys()).sort()) {
|
|
64
|
+
const keepValue = safe.has(key.toLowerCase()) && !SENSITIVE_KEY_RE.test(key);
|
|
65
|
+
if (keepValue) {
|
|
66
|
+
for (const v of url.searchParams.getAll(key))
|
|
67
|
+
parts.push(`${key}=${v}`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
parts.push(`${key}=`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const query = parts.length > 0 ? `?${parts.join('&')}` : '';
|
|
74
|
+
return { urlPattern: (sameOrigin ? '' : url.origin) + path + query, sameOrigin, origin: url.origin };
|
|
75
|
+
}
|
|
76
|
+
/** Fingerprint used to de-dupe within a page AND to detect shared calls across pages (see sharedCalls.ts). */
|
|
77
|
+
function apiCallFingerprint(call) {
|
|
78
|
+
return `${call.method}||${call.urlPattern}||${call.sameOrigin}`;
|
|
79
|
+
}
|
|
80
|
+
/** Aggregate raw captured responses for one page into de-duplicated, normalized API calls. */
|
|
81
|
+
function aggregateApiCalls(responses, pageOrigin, safeParams = exports.DEFAULT_SAFE_QUERY_PARAMS) {
|
|
82
|
+
const byKey = new Map();
|
|
83
|
+
for (const r of responses) {
|
|
84
|
+
if (!API_RESOURCE_TYPES.has(r.resourceType))
|
|
85
|
+
continue;
|
|
86
|
+
const { urlPattern, sameOrigin, origin } = normalizeApiUrl(r.url, pageOrigin, safeParams);
|
|
87
|
+
const key = apiCallFingerprint({ method: r.method, urlPattern, sameOrigin });
|
|
88
|
+
const existing = byKey.get(key);
|
|
89
|
+
if (existing) {
|
|
90
|
+
existing.count++;
|
|
91
|
+
existing.status = r.status;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
byKey.set(key, { method: r.method, urlPattern, sameOrigin, origin, status: r.status, resourceType: r.resourceType, count: 1 });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return Array.from(byKey.values()).sort((a, b) => a.urlPattern.localeCompare(b.urlPattern) || a.method.localeCompare(b.method));
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../src/network.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { PageCapture } from './capture/page';
|
|
2
|
+
import { type ClassificationView } from './classificationView';
|
|
3
|
+
import type { SharedCallsFile } from './sharedCalls';
|
|
4
|
+
import type { SurfaceManifest } from './scan';
|
|
5
|
+
/**
|
|
6
|
+
* `_overview.md` โ the human-readable index of a scan.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors `@ia-qa/self-healing`'s `_overview.md`: the machine-readable half
|
|
9
|
+
* (surface.json + one JSON per page) is what a later stage consumes, and this
|
|
10
|
+
* is what a person opens to answer "what IS this app?" without reading N JSON
|
|
11
|
+
* files. Same file serves an LLM reading it as a RAG chunk โ one contract, not
|
|
12
|
+
* a simplified human view and a richer machine view.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately NOT a verdict document. F1 measures, it does not judge: there is
|
|
15
|
+
* no PASS/FAIL here, and a JUnit export would be a gate that can never fail.
|
|
16
|
+
* Every line traces to something observed in the DOM or on the network.
|
|
17
|
+
*/
|
|
18
|
+
export declare const OVERVIEW_FILENAME = "_overview.md";
|
|
19
|
+
export interface OverviewInput {
|
|
20
|
+
manifest: SurfaceManifest;
|
|
21
|
+
pages: PageCapture[];
|
|
22
|
+
shared: SharedCallsFile;
|
|
23
|
+
/** Pages still showing a login form โ the coverage caveat, see scan.ts. */
|
|
24
|
+
loginWall: string[];
|
|
25
|
+
/** Session file used for the scan, when there was one. */
|
|
26
|
+
session?: string;
|
|
27
|
+
/** The crawl harvested no internal links at all โ see `hrefBlind` in scan.ts. */
|
|
28
|
+
hrefBlind?: boolean;
|
|
29
|
+
/** Pages known but not visited because of the --max cap. Never silently dropped. */
|
|
30
|
+
droppedByCap?: number;
|
|
31
|
+
/** How many pages the sitemap declared, when one was read. */
|
|
32
|
+
sitemapDeclared?: number | null;
|
|
33
|
+
/**
|
|
34
|
+
* The optional BYOK layer's reading of the entry points, when one exists.
|
|
35
|
+
* Absent for every scan run without a key โ which is the normal case, and the
|
|
36
|
+
* report must be complete without it.
|
|
37
|
+
*/
|
|
38
|
+
classification?: ClassificationView;
|
|
39
|
+
}
|
|
40
|
+
export declare function renderOverview(input: OverviewInput): string;
|
package/dist/overview.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OVERVIEW_FILENAME = void 0;
|
|
4
|
+
exports.renderOverview = renderOverview;
|
|
5
|
+
const classificationView_1 = require("./classificationView");
|
|
6
|
+
/**
|
|
7
|
+
* `_overview.md` โ the human-readable index of a scan.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors `@ia-qa/self-healing`'s `_overview.md`: the machine-readable half
|
|
10
|
+
* (surface.json + one JSON per page) is what a later stage consumes, and this
|
|
11
|
+
* is what a person opens to answer "what IS this app?" without reading N JSON
|
|
12
|
+
* files. Same file serves an LLM reading it as a RAG chunk โ one contract, not
|
|
13
|
+
* a simplified human view and a richer machine view.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately NOT a verdict document. F1 measures, it does not judge: there is
|
|
16
|
+
* no PASS/FAIL here, and a JUnit export would be a gate that can never fail.
|
|
17
|
+
* Every line traces to something observed in the DOM or on the network.
|
|
18
|
+
*/
|
|
19
|
+
exports.OVERVIEW_FILENAME = '_overview.md';
|
|
20
|
+
function escapeCell(s) {
|
|
21
|
+
return (s || '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ').replace(/`/g, 'โ');
|
|
22
|
+
}
|
|
23
|
+
/** How a form states where it submits โ never synthesized. See extractPage.js. */
|
|
24
|
+
function submitTarget(form) {
|
|
25
|
+
if (form.action === null && form.method === null)
|
|
26
|
+
return 'not stated (JS-handled submit)';
|
|
27
|
+
const method = form.method ? form.method.toUpperCase() : 'GET (default)';
|
|
28
|
+
const action = form.action ? `\`${escapeCell(form.action)}\`` : 'the page itself';
|
|
29
|
+
return `${method} โ ${action}`;
|
|
30
|
+
}
|
|
31
|
+
function renderOverview(input) {
|
|
32
|
+
const { manifest, pages, shared, loginWall, session, hrefBlind, droppedByCap, sitemapDeclared, classification } = input;
|
|
33
|
+
const lines = [];
|
|
34
|
+
const formCount = pages.reduce((n, p) => n + p.forms.length, 0);
|
|
35
|
+
const pageCalls = pages.reduce((n, p) => n + p.apiCalls.length, 0);
|
|
36
|
+
const fields = pages.flatMap((p) => [...p.forms.flatMap((f) => f.fields), ...p.looseFields]);
|
|
37
|
+
const unstable = fields.filter((f) => !f.stableSelector);
|
|
38
|
+
lines.push('# App surface โ ' + manifest.baseUrl);
|
|
39
|
+
lines.push('');
|
|
40
|
+
lines.push('- **Base URL:** `' + manifest.baseUrl + '`');
|
|
41
|
+
lines.push('- **Pages captured:** ' + manifest.pagesCaptured + ' of ' + manifest.pagesFound + ' found');
|
|
42
|
+
lines.push('- **Forms:** ' + formCount + ' ยท **Fields:** ' + fields.length);
|
|
43
|
+
lines.push('- **API calls:** ' + pageCalls + ' page-specific' + (shared.calls.length > 0 ? ' + ' + shared.calls.length + ' shared' : ''));
|
|
44
|
+
lines.push('- **Distinct origins called:** ' + manifest.origins.length);
|
|
45
|
+
lines.push('- **Captured:** ' + manifest.capturedAt);
|
|
46
|
+
if (session)
|
|
47
|
+
lines.push('- **Session:** used (`' + escapeCell(session) + '`)');
|
|
48
|
+
lines.push('');
|
|
49
|
+
lines.push('> Generated by `@ia-qa/qa-discovery` โ **start here**. This is the index of the page');
|
|
50
|
+
lines.push('> captures in this folder. It is **reconnaissance, not a test plan and not a verdict**:');
|
|
51
|
+
lines.push('> every line below traces to something observed in the DOM or on the network. Rerun');
|
|
52
|
+
lines.push('> `ia-qa-discover scan` to regenerate it.');
|
|
53
|
+
lines.push('');
|
|
54
|
+
// A loaded page is ONE state of a page. Where most of the input surface sits
|
|
55
|
+
// behind a click, the field count above describes a fraction of the app while
|
|
56
|
+
// looking complete โ the same shape as every other false green here.
|
|
57
|
+
const openable = pages.reduce((n, p) => n + (p.exploration?.openable ?? 0), 0);
|
|
58
|
+
const explored = pages.some((p) => p.exploration?.explored);
|
|
59
|
+
const revealed = pages.reduce((n, p) => n + (p.exploration?.revealed ?? 0), 0);
|
|
60
|
+
const collided = pages.reduce((n, p) => n + (p.exploration?.collided ?? 0), 0);
|
|
61
|
+
const truncatedPages = pages.filter((p) => p.exploration?.truncated).length;
|
|
62
|
+
if (!explored && openable > 0) {
|
|
63
|
+
lines.push('## โ Coverage โ the loaded state only');
|
|
64
|
+
lines.push('');
|
|
65
|
+
lines.push('These pages carry **' + openable + ' control(s) that can reveal more** โ menus, tabs, accordions, a');
|
|
66
|
+
lines.push('button that opens a dialog. Only what the page shows on load was captured, so the ' + fields.length +
|
|
67
|
+
' field(s)');
|
|
68
|
+
lines.push('above are the input surface **at rest**, not the whole one. On one measured app a single page');
|
|
69
|
+
lines.push('had 64 such controls and 2 visible fields.');
|
|
70
|
+
lines.push('');
|
|
71
|
+
lines.push('Run `ia-qa-discover scan --deep` to open them and capture what they reveal.');
|
|
72
|
+
lines.push('');
|
|
73
|
+
}
|
|
74
|
+
else if (explored) {
|
|
75
|
+
lines.push('## Deep capture');
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push('`--deep` opened ' + openable + ' control(s) one level down and found **' + revealed +
|
|
78
|
+
' field(s) that are not visible at load**. Each carries the click that reveals it (the `via` column).');
|
|
79
|
+
if (collided > 0) {
|
|
80
|
+
lines.push('');
|
|
81
|
+
lines.push('โ ' + collided + ' revealed field(s) were **reached but not captured**: their selector already named a');
|
|
82
|
+
lines.push('different element in another state, and a selector that means two things is worse than a');
|
|
83
|
+
lines.push('missing one. That is coverage lost, and it is stated rather than hidden.');
|
|
84
|
+
}
|
|
85
|
+
if (truncatedPages > 0) {
|
|
86
|
+
lines.push('');
|
|
87
|
+
lines.push('โ ' + truncatedPages + ' page(s) hit the click budget, so their exploration is partial.');
|
|
88
|
+
}
|
|
89
|
+
lines.push('');
|
|
90
|
+
}
|
|
91
|
+
// A page known and deliberately not visited is not a page that does not
|
|
92
|
+
// exist. Stated before the tables, next to every other coverage caveat.
|
|
93
|
+
if (droppedByCap && droppedByCap > 0) {
|
|
94
|
+
lines.push('## โ Coverage โ the page cap was reached');
|
|
95
|
+
lines.push('');
|
|
96
|
+
lines.push('**' + droppedByCap + ' more page(s) were known and not visited**, because `--max` stopped the run at ' +
|
|
97
|
+
manifest.pagesCaptured + '.' + (sitemapDeclared ? ' The sitemap alone declared ' + sitemapDeclared + '.' : ''));
|
|
98
|
+
lines.push('The count above is a budget, not a census. Raise `--max` to cover more.');
|
|
99
|
+
lines.push('');
|
|
100
|
+
}
|
|
101
|
+
// Coverage honesty first, before any number that could be mistaken for the app.
|
|
102
|
+
if (hrefBlind) {
|
|
103
|
+
lines.push('## โ Coverage โ discovery could not see past the entry URL');
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push('**No internal links were found.** Discovery follows `<a href>`; an app that navigates');
|
|
106
|
+
lines.push("programmatically โ React Router's `useNavigate()`, a button with an onClick handler โ");
|
|
107
|
+
lines.push('exposes no href for a crawl to follow. So "' + manifest.pagesCaptured + ' of ' + manifest.pagesFound +
|
|
108
|
+
' found" above describes **one page**, not');
|
|
109
|
+
lines.push('the application: the denominator is what the crawl could reach, not what exists.');
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push('Declare the routes you know in `.ia-qa-discovery/config.json` โ `"pages"`, then scan again.');
|
|
112
|
+
lines.push('');
|
|
113
|
+
}
|
|
114
|
+
else if (loginWall.length > 1) {
|
|
115
|
+
lines.push('## โ Coverage โ you are probably looking at the login wall');
|
|
116
|
+
lines.push('');
|
|
117
|
+
lines.push(loginWall.length + ' of ' + manifest.pagesCaptured + ' captured pages still show a login form (' +
|
|
118
|
+
loginWall.map((p) => '`' + escapeCell(p) + '`').join(', ') + '). An app whose catch-all route');
|
|
119
|
+
lines.push('renders the login page answers HTTP 200 at every URL, so the page count above looks like');
|
|
120
|
+
lines.push('success while the same wall was captured several times. **A page count is not coverage.**');
|
|
121
|
+
lines.push('');
|
|
122
|
+
lines.push(session
|
|
123
|
+
? 'The saved session did not get past it โ it may have expired. Re-run `ia-qa-discover login`.'
|
|
124
|
+
: 'Run `ia-qa-discover login` to log in by hand once, then scan again.');
|
|
125
|
+
lines.push('');
|
|
126
|
+
}
|
|
127
|
+
else if (loginWall.length === 1 && !session) {
|
|
128
|
+
lines.push('## โ Coverage โ public surface only');
|
|
129
|
+
lines.push('');
|
|
130
|
+
lines.push('This app has a login (`' + escapeCell(loginWall[0]) + '`) and no session was used, so whatever is');
|
|
131
|
+
lines.push('behind it was never reached. Run `ia-qa-discover login`, then scan again.');
|
|
132
|
+
lines.push('');
|
|
133
|
+
}
|
|
134
|
+
if (pages.length === 0) {
|
|
135
|
+
lines.push('_No page was captured. Run `ia-qa-discover scan <url>`._');
|
|
136
|
+
lines.push('');
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
lines.push('## Pages (' + pages.length + ')');
|
|
140
|
+
lines.push('');
|
|
141
|
+
lines.push('| Page | URL | Title | Headings | Forms | Fields | API calls | Capture |');
|
|
142
|
+
lines.push('| --- | --- | --- | --- | --- | --- | --- | --- |');
|
|
143
|
+
for (const p of pages) {
|
|
144
|
+
const file = manifest.pages.find((m) => m.name === p.page)?.file ?? '';
|
|
145
|
+
const formed = p.forms.reduce((n, f) => n + f.fields.length, 0);
|
|
146
|
+
const fieldCell = formed + p.looseFields.length + (p.looseFields.length > 0 ? ` (${p.looseFields.length} loose)` : '');
|
|
147
|
+
let pathname = p.url;
|
|
148
|
+
try {
|
|
149
|
+
pathname = new URL(p.url).pathname;
|
|
150
|
+
}
|
|
151
|
+
catch { /* keep raw */ }
|
|
152
|
+
lines.push('| ' + escapeCell(p.page) +
|
|
153
|
+
' | `' + escapeCell(pathname) + '`' +
|
|
154
|
+
' | ' + escapeCell(p.meta.title || 'โ') +
|
|
155
|
+
' | ' + p.headings.length +
|
|
156
|
+
' | ' + p.forms.length +
|
|
157
|
+
' | ' + fieldCell +
|
|
158
|
+
' | ' + p.apiCalls.length +
|
|
159
|
+
' | [' + escapeCell(file) + '](' + file + ') |');
|
|
160
|
+
}
|
|
161
|
+
lines.push('');
|
|
162
|
+
// Titles are a page's identity in a tab, in history, and to a screen reader
|
|
163
|
+
// announcing a route change. One title across many pages is a real defect,
|
|
164
|
+
// and it is only visible when the pages are seen side by side.
|
|
165
|
+
const distinctTitles = new Set(pages.map((p) => p.meta.title).filter(Boolean));
|
|
166
|
+
if (pages.length > 2 && distinctTitles.size === 1) {
|
|
167
|
+
lines.push('> โ All ' + pages.length + ' pages share one `<title>` (`' + escapeCell([...distinctTitles][0]) +
|
|
168
|
+
'`). The tab, the browser history and a screen reader announcing a route change cannot tell them apart.');
|
|
169
|
+
lines.push('');
|
|
170
|
+
}
|
|
171
|
+
if (formCount > 0) {
|
|
172
|
+
lines.push('## Forms (' + formCount + ')');
|
|
173
|
+
lines.push('');
|
|
174
|
+
lines.push('The entry points where this app takes input โ where a test suite starts, and where');
|
|
175
|
+
lines.push('validation, auth and payment behaviour live.');
|
|
176
|
+
lines.push('');
|
|
177
|
+
for (const p of pages) {
|
|
178
|
+
for (const form of p.forms) {
|
|
179
|
+
const required = form.fields.filter((f) => f.required).length;
|
|
180
|
+
lines.push('### `' + escapeCell(p.page) + '` โ ' + form.fields.length + ' field' + (form.fields.length === 1 ? '' : 's') +
|
|
181
|
+
(required > 0 ? ', ' + required + ' required' : ''));
|
|
182
|
+
lines.push('');
|
|
183
|
+
lines.push('- **Selector:** `' + escapeCell(form.selector) + '`');
|
|
184
|
+
lines.push('- **Submits to:** ' + submitTarget(form));
|
|
185
|
+
lines.push('- **Submit control present:** ' + (form.hasSubmit ? 'yes' : 'no'));
|
|
186
|
+
lines.push('');
|
|
187
|
+
if (form.fields.length > 0) {
|
|
188
|
+
lines.push('| Field | Type | Required | Selector | Locatable |');
|
|
189
|
+
lines.push('| --- | --- | --- | --- | --- |');
|
|
190
|
+
for (const f of form.fields) {
|
|
191
|
+
const locatable = f.ambiguousSelector
|
|
192
|
+
? 'โ ambiguous'
|
|
193
|
+
: f.stableSelector
|
|
194
|
+
? 'โ
stable'
|
|
195
|
+
: 'โ positional';
|
|
196
|
+
lines.push('| ' + escapeCell(f.name || '_(unnamed)_') +
|
|
197
|
+
' | ' + escapeCell(f.type) +
|
|
198
|
+
' | ' + (f.required ? 'yes' : 'no') +
|
|
199
|
+
' | `' + escapeCell(f.selector) + '`' +
|
|
200
|
+
' | ' + locatable + ' |');
|
|
201
|
+
}
|
|
202
|
+
lines.push('');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Loose fields โ the input surface of an app that never wraps anything in a
|
|
208
|
+
// `<form>`, which is most React/Vue apps. Reporting only `<form>` descendants
|
|
209
|
+
// showed "0 fields" on an app with hundreds of inputs.
|
|
210
|
+
const looseTotal = pages.reduce((n, p) => n + p.looseFields.length, 0);
|
|
211
|
+
if (looseTotal > 0) {
|
|
212
|
+
lines.push('## Input fields outside a form (' + looseTotal + ')');
|
|
213
|
+
lines.push('');
|
|
214
|
+
lines.push('These are inputs this app takes **without** a `<form>` wrapper โ submission is handled in JS.');
|
|
215
|
+
lines.push('They are entry points all the same, and for most React/Vue apps they are the *only* ones.');
|
|
216
|
+
lines.push('');
|
|
217
|
+
for (const p of pages) {
|
|
218
|
+
if (p.looseFields.length === 0)
|
|
219
|
+
continue;
|
|
220
|
+
lines.push('### `' + escapeCell(p.page) + '` โ ' + p.looseFields.length + ' field' + (p.looseFields.length === 1 ? '' : 's'));
|
|
221
|
+
lines.push('');
|
|
222
|
+
lines.push('| Field | Type | Required | Where / how to reach | Selector | Locatable |');
|
|
223
|
+
lines.push('| --- | --- | --- | --- | --- | --- |');
|
|
224
|
+
for (const f of p.looseFields) {
|
|
225
|
+
const locatable = f.ambiguousSelector ? 'โ ambiguous' : f.stableSelector ? 'โ
stable' : 'โ positional';
|
|
226
|
+
// A revealed field is useless without the click that reveals it.
|
|
227
|
+
const where = f.via?.length
|
|
228
|
+
? '๐ click ' + f.via.map((v) => 'โ' + escapeCell(v) + 'โ').join(' โบ ')
|
|
229
|
+
: escapeCell(f.context || 'โ');
|
|
230
|
+
lines.push('| ' + escapeCell(f.name || '_(unnamed)_') +
|
|
231
|
+
' | ' + escapeCell(f.type) +
|
|
232
|
+
' | ' + (f.required ? 'yes' : 'no') +
|
|
233
|
+
' | ' + where +
|
|
234
|
+
' | `' + escapeCell(f.selector) + '`' +
|
|
235
|
+
' | ' + locatable + ' |');
|
|
236
|
+
}
|
|
237
|
+
lines.push('');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
lines.push('## API surface');
|
|
241
|
+
lines.push('');
|
|
242
|
+
if (manifest.origins.length === 0) {
|
|
243
|
+
lines.push('_No XHR/fetch traffic was observed. Either the app makes none, or the scan ran with `--no-network`._');
|
|
244
|
+
lines.push('');
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
lines.push('| Origin | Calls | Pages | Same-origin as the app |');
|
|
248
|
+
lines.push('| --- | --- | --- | --- |');
|
|
249
|
+
for (const o of manifest.origins) {
|
|
250
|
+
lines.push('| `' + escapeCell(o.origin) + '` | ' + o.calls + ' | ' + o.pages + ' | ' + (o.sameOrigin ? 'yes' : 'no') + ' |');
|
|
251
|
+
}
|
|
252
|
+
lines.push('');
|
|
253
|
+
lines.push("> `Same-origin: no` is not the same as third-party: an app whose API lives on its own");
|
|
254
|
+
lines.push('> separate host (an `api.*` subdomain, a platform backend) has every one of its OWN calls');
|
|
255
|
+
lines.push('> listed here as cross-origin, next to any analytics or ad host. Deciding which of these');
|
|
256
|
+
lines.push('> are first-party is a judgment this stage does not make โ nothing in the DOM states it.');
|
|
257
|
+
lines.push('');
|
|
258
|
+
}
|
|
259
|
+
if (shared.calls.length > 0) {
|
|
260
|
+
lines.push('### Shared calls (on โฅ ' + Math.max(2, Math.ceil(shared.threshold * shared.sourcePages)) + ' of ' + shared.sourcePages + ' pages)');
|
|
261
|
+
lines.push('');
|
|
262
|
+
lines.push('Cross-cutting traffic โ session refresh, telemetry, feature flags. Promoted out of the');
|
|
263
|
+
lines.push('per-page captures so one analytics beacon does not read as N page-specific calls.');
|
|
264
|
+
lines.push('');
|
|
265
|
+
lines.push('| Method | URL | Status | Pages |');
|
|
266
|
+
lines.push('| --- | --- | --- | --- |');
|
|
267
|
+
for (const c of shared.calls) {
|
|
268
|
+
lines.push('| ' + escapeCell(c.method) + ' | `' + escapeCell(c.urlPattern) + '` | ' + c.status + ' | ' + c.count + ' |');
|
|
269
|
+
}
|
|
270
|
+
lines.push('');
|
|
271
|
+
}
|
|
272
|
+
const perPageCalls = pages.filter((p) => p.apiCalls.length > 0);
|
|
273
|
+
if (perPageCalls.length > 0) {
|
|
274
|
+
lines.push('### Page-specific calls');
|
|
275
|
+
lines.push('');
|
|
276
|
+
lines.push('| Page | Method | URL | Status |');
|
|
277
|
+
lines.push('| --- | --- | --- | --- |');
|
|
278
|
+
for (const p of perPageCalls) {
|
|
279
|
+
for (const c of p.apiCalls) {
|
|
280
|
+
lines.push('| ' + escapeCell(p.page) + ' | ' + escapeCell(c.method) + ' | `' + escapeCell(c.urlPattern) + '` | ' + c.status + ' |');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
lines.push('');
|
|
284
|
+
}
|
|
285
|
+
// Everything above this line is a literal observation. Everything in this
|
|
286
|
+
// section is a model's reading of one, and it says so in its own heading โ
|
|
287
|
+
// the two must never share a table.
|
|
288
|
+
if (classification) {
|
|
289
|
+
const c = classification;
|
|
290
|
+
lines.push('## ๐ค What these entry points are FOR โ model-classified');
|
|
291
|
+
lines.push('');
|
|
292
|
+
lines.push('_' + (0, classificationView_1.classificationHeadline)(c) + '_ ยท `' + c.model + '` ยท `' + c.taxonomyVersion + '` ยท classified ' + c.classifiedAt);
|
|
293
|
+
lines.push('');
|
|
294
|
+
lines.push(classificationView_1.CLASSIFICATION_CAVEAT);
|
|
295
|
+
lines.push('');
|
|
296
|
+
if (c.staleCount > 0) {
|
|
297
|
+
lines.push('> โ **' + c.staleCount + ' page(s) below are stale.** ' + classificationView_1.STALE_CAVEAT);
|
|
298
|
+
lines.push('');
|
|
299
|
+
}
|
|
300
|
+
for (const p of c.pages) {
|
|
301
|
+
if (p.failed) {
|
|
302
|
+
// A failed call is a fact about the model, never a finding about the app.
|
|
303
|
+
lines.push('### `' + p.page + '` โ not classified');
|
|
304
|
+
lines.push('');
|
|
305
|
+
lines.push('The model call did not succeed: ' + p.failed + ' Nothing is wrong with this page.');
|
|
306
|
+
lines.push('');
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (p.kept.length === 0 && p.refused.length === 0)
|
|
310
|
+
continue;
|
|
311
|
+
lines.push('### `' + p.page + '`' + (p.stale ? ' โ stale' : ''));
|
|
312
|
+
lines.push('');
|
|
313
|
+
lines.push('| Entry point | Intent | Handles | Confidence | Why |');
|
|
314
|
+
lines.push('|---|---|---|---|---|');
|
|
315
|
+
for (const k of p.kept) {
|
|
316
|
+
lines.push('| `' + escapeCell(k.entryPoint.split('#')[1] ?? k.entryPoint) + '` | **' + escapeCell(k.intent) + '** | ' +
|
|
317
|
+
escapeCell(k.sensitivity) + ' | ' + k.confidence.toFixed(2) + ' | ' + escapeCell(k.why) + ' |');
|
|
318
|
+
}
|
|
319
|
+
// A refusal is an answer. Listed, not hidden, and never as a failure:
|
|
320
|
+
// "the evidence does not support a label" is the correct output when it is
|
|
321
|
+
// true, and a person is the only thing that can take it further.
|
|
322
|
+
for (const r of p.refused) {
|
|
323
|
+
lines.push('| `' + escapeCell(r.entryPoint.split('#')[1] ?? r.entryPoint) + '` | _declined_ | โ | ' +
|
|
324
|
+
r.confidence.toFixed(2) + ' | ' + escapeCell(r.why) + ' |');
|
|
325
|
+
}
|
|
326
|
+
lines.push('');
|
|
327
|
+
if (p.dropped > 0) {
|
|
328
|
+
lines.push('_' + p.dropped + ' classification(s) on this page were dropped: their evidence did not check out. See `classification.json`._');
|
|
329
|
+
lines.push('');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (c.securityTools.length > 0) {
|
|
333
|
+
// Deterministic: selected by the taxonomy from the intent, never by the
|
|
334
|
+
// model. This is the point of classifying at all โ a label that selects
|
|
335
|
+
// no work is a label nobody needed.
|
|
336
|
+
lines.push('**Security checks this selects** (on the ia-qa.com MCP server):');
|
|
337
|
+
lines.push('');
|
|
338
|
+
for (const t of c.securityTools)
|
|
339
|
+
lines.push('- `' + t.tool + '` โ for ' + t.pages.map((p) => '`' + p + '`').join(', '));
|
|
340
|
+
lines.push('');
|
|
341
|
+
}
|
|
342
|
+
// The honesty counter. A section that shows 12 classified pages and says
|
|
343
|
+
// nothing about the 30 it never looked at is the exact shape of confident
|
|
344
|
+
// number this package exists to refuse.
|
|
345
|
+
if (c.unclassified.length > 0) {
|
|
346
|
+
lines.push('โ **' + c.unclassified.length + ' page(s) with entry points carry no classification** โ ' +
|
|
347
|
+
'either they were not sent, or their call failed: ' + c.unclassified.map((p) => '`' + p + '`').join(', ') + '.');
|
|
348
|
+
lines.push('');
|
|
349
|
+
}
|
|
350
|
+
if (c.noEntryPoints > 0) {
|
|
351
|
+
lines.push('_' + c.noEntryPoints + ' captured page(s) take no input at all, so there was nothing on them to classify._');
|
|
352
|
+
lines.push('');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// A finding about the APP, derived deterministically from the capture โ not a
|
|
356
|
+
// caveat about this tool. A field with no durable identity cannot be located
|
|
357
|
+
// reliably by any suite, and that is worth fixing before one is written.
|
|
358
|
+
if (unstable.length > 0) {
|
|
359
|
+
lines.push('## Testability');
|
|
360
|
+
lines.push('');
|
|
361
|
+
lines.push('**' + unstable.length + ' of ' + fields.length + ' form field' + (fields.length === 1 ? '' : 's') +
|
|
362
|
+
' carry no durable identity** โ no `data-testid`, no `id`, no `name`. The');
|
|
363
|
+
lines.push('selectors above for those fields are positional: they resolve today and break the moment a');
|
|
364
|
+
lines.push('field is inserted above them. Adding a `data-testid` to each is the cheapest durable fix,');
|
|
365
|
+
lines.push('and it is worth doing *before* a test suite is written against them, not after.');
|
|
366
|
+
lines.push('');
|
|
367
|
+
}
|
|
368
|
+
return lines.join('\n');
|
|
369
|
+
}
|
|
370
|
+
//# sourceMappingURL=overview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overview.js","sourceRoot":"","sources":["../src/overview.ts"],"names":[],"mappings":"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"}
|