@qatonic_innovations/qaios 0.3.2 → 0.4.1

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.
@@ -0,0 +1,75 @@
1
+ -- 0003 — xray network capture (v0.4).
2
+ -- Source of truth: docs/internal/qaios-v0.4-xray-spec.md §7.
3
+ -- Forward-only; no down-migrations in v0.x.
4
+ --
5
+ -- These tables hold the per-test network capture that `qaios run --xray` /
6
+ -- `qaios explore` produce: one net_runs row per (test attempt, browser), one
7
+ -- net_requests row per captured request (attributed to a step by the
8
+ -- CausalityEngine and normalized into a stable identity key), content-addressed
9
+ -- bodies in net_bodies (gzip, deduped by hash), and learned per-field volatility
10
+ -- in net_volatility. WAL is already enabled (0001_init.sql) so the parallel
11
+ -- Playwright workers can each append safely.
12
+
13
+ -- ── net_runs (one per test attempt × browser) ─────────────────────────
14
+ CREATE TABLE net_runs (
15
+ run_id TEXT PRIMARY KEY, -- ULID
16
+ workflow_id TEXT NOT NULL,
17
+ test_id TEXT NOT NULL, -- stable per (file, title)
18
+ browser TEXT NOT NULL, -- chromium|firefox|webkit
19
+ tier TEXT NOT NULL CHECK (tier IN ('A','B')),
20
+ capture_level TEXT NOT NULL, -- headers|summary|full
21
+ started_at TEXT NOT NULL,
22
+ is_green INTEGER NOT NULL DEFAULT 0, -- 0/1 — test passed
23
+ is_baseline INTEGER NOT NULL DEFAULT 0, -- 0/1 — pinned baseline
24
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id)
25
+ );
26
+
27
+ CREATE INDEX idx_netruns_workflow ON net_runs(workflow_id);
28
+ CREATE INDEX idx_netruns_test ON net_runs(test_id, browser);
29
+
30
+ -- ── net_requests (one per captured request) ───────────────────────────
31
+ CREATE TABLE net_requests (
32
+ id TEXT PRIMARY KEY, -- ULID
33
+ run_id TEXT NOT NULL REFERENCES net_runs(run_id),
34
+ step_id TEXT, -- NULL => setup/background
35
+ attribution TEXT NOT NULL, -- initiator|temporal|temporal_ambiguous|background
36
+ target_type TEXT NOT NULL, -- page|iframe|service_worker
37
+ method TEXT NOT NULL,
38
+ url_raw TEXT NOT NULL,
39
+ url_template TEXT NOT NULL,
40
+ gql_operation TEXT,
41
+ identity_key TEXT NOT NULL, -- method|host|url_template|gql_operation
42
+ status INTEGER,
43
+ error_text TEXT, -- net::ERR_*, CORS, etc.
44
+ mime TEXT,
45
+ req_hash TEXT,
46
+ resp_hash TEXT,
47
+ shape_hash TEXT,
48
+ req_body_ref TEXT, -- net_bodies.hash, nullable
49
+ resp_body_ref TEXT, -- net_bodies.hash, nullable
50
+ body_status TEXT, -- stored|evicted|truncated|streaming|skipped
51
+ redirects_json TEXT,
52
+ timings_json TEXT, -- dns/connect/ttfb/total (ms)
53
+ initiator_json TEXT, -- trimmed stack (top 5 frames)
54
+ wall_start TEXT NOT NULL
55
+ );
56
+
57
+ CREATE INDEX idx_netreq_identity ON net_requests(identity_key, run_id);
58
+ CREATE INDEX idx_netreq_run_step ON net_requests(run_id, step_id);
59
+
60
+ -- ── net_bodies (content-addressed, deduped, gzip-compressed) ──────────
61
+ CREATE TABLE net_bodies (
62
+ hash TEXT PRIMARY KEY, -- sha256 of canonical form
63
+ compressed BLOB NOT NULL, -- node:zlib gzip
64
+ size_raw INTEGER NOT NULL
65
+ );
66
+
67
+ -- ── net_volatility (learned per-field volatility) ─────────────────────
68
+ CREATE TABLE net_volatility (
69
+ test_id TEXT NOT NULL,
70
+ identity_key TEXT NOT NULL,
71
+ json_path TEXT NOT NULL, -- '' => presence-level volatility
72
+ kind TEXT NOT NULL CHECK (kind IN ('value','presence')),
73
+ learned_at TEXT NOT NULL,
74
+ PRIMARY KEY (test_id, identity_key, json_path)
75
+ );
@@ -0,0 +1,62 @@
1
+ // QAIOS xray STEP reporter — runs inside the Playwright subprocess.
2
+ //
3
+ // Playwright's built-in `json` reporter does NOT serialize per-step timings, so
4
+ // QAIOS can't recover the `pw:api` step windows it needs to attribute network
5
+ // requests to the test action that caused them. This tiny reporter fills that
6
+ // gap: it records every step's (testId, title, category, start, end) wall-clock
7
+ // window to an NDJSON sidecar. It captures NO network — that's recordHar's job —
8
+ // so it's a pure, side-effect-free observer that coexists with `--reporter=json`.
9
+ //
10
+ // The sidecar path comes from env (QAIOS_XRAY_STEPS_PATH). One NDJSON line per
11
+ // step: {testId, title, category, startWall, endWall}. The runtime's
12
+ // step-windows parser reads this instead of the (step-less) JSON report.
13
+
14
+ import { appendFileSync, mkdirSync } from 'node:fs';
15
+ import path from 'node:path';
16
+
17
+ class XrayStepReporter {
18
+ constructor() {
19
+ this.out = process.env.QAIOS_XRAY_STEPS_PATH ?? '';
20
+ if (this.out) {
21
+ try {
22
+ mkdirSync(path.dirname(this.out), { recursive: true });
23
+ } catch {
24
+ /* best-effort */
25
+ }
26
+ }
27
+ }
28
+
29
+ // Stable test id matching the report parser's convention exactly:
30
+ // `<basename>::<title>`. The report parser uses path.basename for the file
31
+ // part, so we must too, or the sidecar-window merge won't match by testId.
32
+ _testId(test) {
33
+ const file = test.location?.file ?? '';
34
+ const base = file ? path.basename(file) : '';
35
+ const title = test.titlePath ? test.titlePath().slice(3).join(' › ') : test.title;
36
+ return `${base}::${title}`;
37
+ }
38
+
39
+ onStepEnd(test, _result, step) {
40
+ if (!this.out) return;
41
+ // Only `pw:api` steps (click/fill/goto/…) define a network-causing window.
42
+ if (step.category !== 'pw:api') return;
43
+ const start =
44
+ step.startTime instanceof Date ? step.startTime.getTime() : Date.parse(step.startTime);
45
+ if (!Number.isFinite(start)) return;
46
+ const dur = typeof step.duration === 'number' && step.duration >= 0 ? step.duration : 0;
47
+ const line = {
48
+ testId: this._testId(test),
49
+ title: step.title ?? '(step)',
50
+ category: step.category,
51
+ startWall: start,
52
+ endWall: start + dur,
53
+ };
54
+ try {
55
+ appendFileSync(this.out, JSON.stringify(line) + '\n', 'utf-8');
56
+ } catch {
57
+ /* never let a reporting write failure affect the run */
58
+ }
59
+ }
60
+ }
61
+
62
+ export default XrayStepReporter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qatonic_innovations/qaios",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "AI QA engineer in your terminal — designs, writes, runs, heals, and explores tests for web UI and APIs with audit-grade traceability.",
6
6
  "license": "MIT",
@@ -49,6 +49,7 @@
49
49
  "commander": "^12.1.0",
50
50
  "openai": "^4.77.0",
51
51
  "ink": "^5.2.1",
52
+ "es-toolkit": "^1.47.1",
52
53
  "pino": "^9.5.0",
53
54
  "pino-pretty": "^11.3.0",
54
55
  "pixelmatch": "^7.2.0",