@ait-co/devtools 0.1.131 → 0.1.132
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/{debug-server-CD4zYBq4.js → debug-server-BBUzxZSf.js} +2 -2
- package/dist/{debug-server-CD4zYBq4.js.map → debug-server-BBUzxZSf.js.map} +1 -1
- package/dist/{debug-server-qRSRG7YZ.js → debug-server-Ch5p0ryv.js} +20 -2
- package/dist/debug-server-Ch5p0ryv.js.map +1 -0
- package/dist/{debug-server-BSbfnFR5.js → debug-server-DoWAtTNj.js} +22 -4
- package/dist/debug-server-DoWAtTNj.js.map +1 -0
- package/dist/mcp/cli.js +72 -7
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/panel/index.js +1 -1
- package/dist/{pool-BaSCZALv.d.ts → pool-BtLh7U1y.d.ts} +16 -2
- package/dist/{pool-BaSCZALv.d.ts.map → pool-BtLh7U1y.d.ts.map} +1 -1
- package/dist/{qr-http-server-BxoRp2w7.cjs → qr-http-server-DX_9UA-p.cjs} +3 -2
- package/dist/qr-http-server-DX_9UA-p.cjs.map +1 -0
- package/dist/{qr-http-server-BdNGPRoL.js → qr-http-server-DeRrKp_R.js} +3 -2
- package/dist/{qr-http-server-cKbciY5k.js.map → qr-http-server-DeRrKp_R.js.map} +1 -1
- package/dist/{qr-http-server-DCbjORFa.js → qr-http-server-FVn61xAq.js} +3 -3
- package/dist/{qr-http-server-BdNGPRoL.js.map → qr-http-server-FVn61xAq.js.map} +1 -1
- package/dist/{qr-http-server-cKbciY5k.js → qr-http-server-KB147M-S.js} +4 -2
- package/dist/{qr-http-server-DCbjORFa.js.map → qr-http-server-KB147M-S.js.map} +1 -1
- package/dist/{qr-http-server-Bq9zR7-z.js → qr-http-server-V0xuU5TJ.js} +3 -2
- package/dist/{qr-http-server-Bq9zR7-z.js.map → qr-http-server-V0xuU5TJ.js.map} +1 -1
- package/dist/{qr-http-server-CV6TA9ZN.cjs → qr-http-server-iH_Hh_si.cjs} +3 -2
- package/dist/qr-http-server-iH_Hh_si.cjs.map +1 -0
- package/dist/{relay-factory-Cloog1jb.js → relay-factory-cNgPfrWg.js} +10 -4
- package/dist/relay-factory-cNgPfrWg.js.map +1 -0
- package/dist/{relay-worker-o0M2ndyJ.d.ts → relay-worker-BMSnlyls.d.ts} +39 -2
- package/dist/relay-worker-BMSnlyls.d.ts.map +1 -0
- package/dist/{relay-worker-CCAHLC4a.js → relay-worker-l34xNuci.js} +20 -3
- package/dist/{relay-worker-CCAHLC4a.js.map → relay-worker-l34xNuci.js.map} +1 -1
- package/dist/test-runner/bin.js +160 -20
- package/dist/test-runner/bin.js.map +1 -1
- package/dist/test-runner/config.d.ts +1 -1
- package/dist/test-runner/config.js +1 -1
- package/dist/test-runner/pool.d.ts +1 -1
- package/dist/test-runner/pool.js +1 -1
- package/dist/test-runner/pool.js.map +1 -1
- package/dist/test-runner/relay-factory.d.ts +14 -0
- package/dist/test-runner/relay-factory.d.ts.map +1 -1
- package/dist/test-runner/relay-factory.js +9 -3
- package/dist/test-runner/relay-factory.js.map +1 -1
- package/dist/test-runner/relay-worker.d.ts +2 -2
- package/dist/test-runner/relay-worker.js +2 -2
- package/dist/test-runner/report.d.ts +26 -4
- package/dist/test-runner/report.d.ts.map +1 -1
- package/dist/test-runner/report.js +41 -7
- package/dist/test-runner/report.js.map +1 -1
- package/dist/{tunnel-B2UCKAc7.cjs → tunnel-DP453L4e.cjs} +2 -2
- package/dist/{tunnel-B2UCKAc7.cjs.map → tunnel-DP453L4e.cjs.map} +1 -1
- package/dist/{tunnel-C-rQ34dM.js → tunnel-Djf-zmjJ.js} +2 -2
- package/dist/{tunnel-C-rQ34dM.js.map → tunnel-Djf-zmjJ.js.map} +1 -1
- package/dist/unplugin/index.cjs +1 -1
- package/dist/unplugin/index.js +1 -1
- package/dist/unplugin/tunnel.cjs +1 -1
- package/dist/unplugin/tunnel.js +1 -1
- package/package.json +1 -1
- package/dist/debug-server-BSbfnFR5.js.map +0 -1
- package/dist/debug-server-qRSRG7YZ.js.map +0 -1
- package/dist/qr-http-server-BxoRp2w7.cjs.map +0 -1
- package/dist/qr-http-server-CV6TA9ZN.cjs.map +0 -1
- package/dist/relay-factory-Cloog1jb.js.map +0 -1
- package/dist/relay-worker-o0M2ndyJ.d.ts.map +0 -1
package/dist/test-runner/bin.js
CHANGED
|
@@ -22,6 +22,23 @@ import { fileURLToPath } from "node:url";
|
|
|
22
22
|
* safe to import from the MCP daemon graph.
|
|
23
23
|
*/
|
|
24
24
|
/**
|
|
25
|
+
* Filename suffix that opts a test file into manual-variant scheduling
|
|
26
|
+
* (devtools#741). A file named `<name>.manual.ait.test.ts` is EXCLUDED from
|
|
27
|
+
* `discoverTestFiles`'s default output (so existing unattended runs are
|
|
28
|
+
* unaffected — the zero-diff-when-off constraint) and is only surfaced when
|
|
29
|
+
* the caller explicitly asks for manual files via {@link partitionManualTests}
|
|
30
|
+
* (wired from the CLI's `--manual-blocking` flag).
|
|
31
|
+
*
|
|
32
|
+
* This is the entire tagging contract for v1 — no separate manifest/config,
|
|
33
|
+
* just a filename convention. Documented here + in the CLI `--help` text
|
|
34
|
+
* (cli.ts USAGE) and the test-runner README.
|
|
35
|
+
*/
|
|
36
|
+
const MANUAL_TEST_SUFFIX = ".manual.ait.test.ts";
|
|
37
|
+
/** True when `file`'s basename ends with {@link MANUAL_TEST_SUFFIX}. */
|
|
38
|
+
function isManualTestFile(file) {
|
|
39
|
+
return basename(file).endsWith(MANUAL_TEST_SUFFIX);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
25
42
|
* Expands `patterns` (globs or plain paths) into a sorted, de-duplicated list of
|
|
26
43
|
* ABSOLUTE test file paths, resolved relative to `cwd`.
|
|
27
44
|
*
|
|
@@ -30,14 +47,46 @@ import { fileURLToPath } from "node:url";
|
|
|
30
47
|
* resolved against `cwd`. `bundleTestFile` requires an absolute path, so the
|
|
31
48
|
* absolute output feeds it directly.
|
|
32
49
|
*
|
|
50
|
+
* By default, files matching {@link MANUAL_TEST_SUFFIX} are EXCLUDED from the
|
|
51
|
+
* result (devtools#741) — blocking-UI tests opt in via that filename
|
|
52
|
+
* convention and must never appear in an unattended run unless the caller
|
|
53
|
+
* explicitly asks for them via `includeManual: true`. This keeps the
|
|
54
|
+
* default (flag-off) discovery path byte-for-byte identical to before this
|
|
55
|
+
* option existed.
|
|
56
|
+
*
|
|
33
57
|
* @param patterns Glob patterns or file paths (e.g. `['src/**\/*.ait.test.ts']`).
|
|
34
58
|
* @param cwd Base directory for relative patterns/results.
|
|
59
|
+
* @param opts `{ includeManual }` — when true, manual-tagged files are kept
|
|
60
|
+
* in the (still-sorted) output instead of being filtered out.
|
|
61
|
+
* Use {@link partitionManualTests} to separate + reorder them.
|
|
35
62
|
* @returns Sorted, de-duplicated absolute file paths. Empty when nothing matches.
|
|
36
63
|
*/
|
|
37
|
-
async function discoverTestFiles(patterns, cwd) {
|
|
64
|
+
async function discoverTestFiles(patterns, cwd, opts) {
|
|
38
65
|
const out = /* @__PURE__ */ new Set();
|
|
39
66
|
for await (const match of glob(patterns, { cwd })) out.add(isAbsolute(match) ? match : resolve(cwd, match));
|
|
40
|
-
|
|
67
|
+
const sorted = [...out].sort();
|
|
68
|
+
if (opts?.includeManual) return sorted;
|
|
69
|
+
return sorted.filter((f) => !isManualTestFile(f));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Splits an already-discovered file list into `{ regular, manual }`, each
|
|
73
|
+
* still sorted. Used by the CLI's `--manual-blocking` path to schedule manual
|
|
74
|
+
* files strictly AFTER every regular file (devtools#741) — regular files run
|
|
75
|
+
* first (and produce the unattended-shaped part of the report) and manual
|
|
76
|
+
* files run last, each preceded by a dashboard prompt.
|
|
77
|
+
*
|
|
78
|
+
* Pure partition — does not itself decide inclusion; call
|
|
79
|
+
* `discoverTestFiles(patterns, cwd, { includeManual: true })` first so manual
|
|
80
|
+
* files are present in `files` to partition.
|
|
81
|
+
*/
|
|
82
|
+
function partitionManualTests(files) {
|
|
83
|
+
const regular = [];
|
|
84
|
+
const manual = [];
|
|
85
|
+
for (const f of files) (isManualTestFile(f) ? manual : regular).push(f);
|
|
86
|
+
return {
|
|
87
|
+
regular,
|
|
88
|
+
manual
|
|
89
|
+
};
|
|
41
90
|
}
|
|
42
91
|
//#endregion
|
|
43
92
|
//#region src/test-runner/relay-factory.ts
|
|
@@ -65,12 +114,13 @@ function createRelayConnectionFactory(opts) {
|
|
|
65
114
|
let family;
|
|
66
115
|
let qrServer;
|
|
67
116
|
let phase = "active";
|
|
117
|
+
let manualPrompt = null;
|
|
68
118
|
return {
|
|
69
119
|
async open() {
|
|
70
120
|
const { prepareAttach, renderAndMaybeWait, mintAttachUrl } = await import("../attach-orchestrator-ca34r46y.js");
|
|
71
121
|
const { injectDebugIndicator, injectGlobals } = await import("../cell-rr6gL9AT.js");
|
|
72
122
|
const { loadRelaySecretReadOnly } = await import("../relay-secret-store-CFc9n0OA.js");
|
|
73
|
-
const { bootRelayFamily, buildRelayVerifyAuth } = await import("../debug-server-
|
|
123
|
+
const { bootRelayFamily, buildRelayVerifyAuth } = await import("../debug-server-BBUzxZSf.js");
|
|
74
124
|
await loadRelaySecretReadOnly({ projectRoot });
|
|
75
125
|
let resolveTunnelUp;
|
|
76
126
|
const tunnelReady = new Promise((resolve) => {
|
|
@@ -100,13 +150,14 @@ function createRelayConnectionFactory(opts) {
|
|
|
100
150
|
canOpenBrowser: () => !headless
|
|
101
151
|
};
|
|
102
152
|
try {
|
|
103
|
-
const { startQrHttpServer } = await import("../qr-http-server-
|
|
153
|
+
const { startQrHttpServer } = await import("../qr-http-server-KB147M-S.js");
|
|
104
154
|
const getDashboardState = () => ({
|
|
105
155
|
tunnel: attachDeps.getTunnelStatus(),
|
|
106
156
|
pages: null,
|
|
107
157
|
attachUrl: lastAttachParts ? mintAttachUrl(attachDeps, lastAttachParts) : null,
|
|
108
158
|
mode: "relay-dev",
|
|
109
|
-
phase
|
|
159
|
+
phase,
|
|
160
|
+
manualPrompt
|
|
110
161
|
});
|
|
111
162
|
qrServer = await startQrHttpServer(getDashboardState);
|
|
112
163
|
attachDeps.qrHttpServer = qrServer;
|
|
@@ -163,6 +214,10 @@ function createRelayConnectionFactory(opts) {
|
|
|
163
214
|
phase = next;
|
|
164
215
|
qrServer?.notifyStateChange();
|
|
165
216
|
},
|
|
217
|
+
onManualPrompt(next) {
|
|
218
|
+
manualPrompt = next;
|
|
219
|
+
qrServer?.notifyStateChange();
|
|
220
|
+
},
|
|
166
221
|
async close(connection) {
|
|
167
222
|
if (phase !== "complete") {
|
|
168
223
|
phase = "complete";
|
|
@@ -1316,6 +1371,13 @@ async function injectAndRunBundle(connection, bundleCode, timeoutMs = DEFAULT_TI
|
|
|
1316
1371
|
//#endregion
|
|
1317
1372
|
//#region src/test-runner/relay-worker.ts
|
|
1318
1373
|
/**
|
|
1374
|
+
* Per-file evaluate timeout (ms) for manual-variant files (devtools#741) — a
|
|
1375
|
+
* human is expected to be tapping through a native sheet, so the timeout must
|
|
1376
|
+
* be far longer than the unattended default (60s, rpc.ts DEFAULT_TIMEOUT_MS).
|
|
1377
|
+
* Fixed constant (not configurable in v1) — documented here + CLI `--help`.
|
|
1378
|
+
*/
|
|
1379
|
+
const MANUAL_FILE_TIMEOUT_MS = 5 * 6e4;
|
|
1380
|
+
/**
|
|
1319
1381
|
* Sentinel string embedded in the error message by `injectAndRunBundle` when
|
|
1320
1382
|
* the per-file evaluate race hits the timeout. Used by the retry guard so only
|
|
1321
1383
|
* genuine timeouts get a second attempt — not bundle errors or parse failures.
|
|
@@ -1386,12 +1448,21 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1386
1448
|
process.stderr.write(`relay-worker: reconnect failed (${e instanceof Error ? e.message : String(e)}) — continuing, remaining files may fail\n`);
|
|
1387
1449
|
}
|
|
1388
1450
|
};
|
|
1451
|
+
const manualFiles = opts?.manualFiles;
|
|
1452
|
+
const manualTotal = manualFiles?.size ?? 0;
|
|
1453
|
+
let manualIndex = 0;
|
|
1389
1454
|
try {
|
|
1390
1455
|
for (const file of files) {
|
|
1391
1456
|
if (pendingReconnectCheck) {
|
|
1392
1457
|
await attemptReconnect("next-file");
|
|
1393
1458
|
pendingReconnectCheck = false;
|
|
1394
1459
|
}
|
|
1460
|
+
const isManual = manualFiles?.has(file) === true;
|
|
1461
|
+
if (isManual) {
|
|
1462
|
+
manualIndex += 1;
|
|
1463
|
+
opts?.onManualFile?.(file, manualIndex, manualTotal);
|
|
1464
|
+
}
|
|
1465
|
+
const fileTimeoutMs = isManual ? MANUAL_FILE_TIMEOUT_MS : opts?.timeoutMs;
|
|
1395
1466
|
let fileEntry;
|
|
1396
1467
|
try {
|
|
1397
1468
|
const { code } = await bundleTestFile(file, opts?.bundleOptions);
|
|
@@ -1417,7 +1488,7 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1417
1488
|
const attempt = async () => {
|
|
1418
1489
|
let rpcResult;
|
|
1419
1490
|
try {
|
|
1420
|
-
rpcResult = await injectAndRunBundle(connection, code,
|
|
1491
|
+
rpcResult = await injectAndRunBundle(connection, code, fileTimeoutMs);
|
|
1421
1492
|
} catch (e) {
|
|
1422
1493
|
return {
|
|
1423
1494
|
file,
|
|
@@ -1453,6 +1524,7 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1453
1524
|
};
|
|
1454
1525
|
}
|
|
1455
1526
|
if ("error" in fileEntry.result && isRelayDisconnectMessage(fileEntry.result.error)) pendingReconnectCheck = true;
|
|
1527
|
+
if (isManual) fileEntry.mode = "manual";
|
|
1456
1528
|
fileResults.push(fileEntry);
|
|
1457
1529
|
}
|
|
1458
1530
|
} finally {
|
|
@@ -1566,9 +1638,11 @@ function serializeRelayReport(report, meta) {
|
|
|
1566
1638
|
totals: report.totals,
|
|
1567
1639
|
files: report.files.map((f) => {
|
|
1568
1640
|
const file = relativise(meta.projectRoot, f.file);
|
|
1641
|
+
const modeField = f.mode === "manual" ? { mode: "manual" } : {};
|
|
1569
1642
|
if ("error" in f.result) return {
|
|
1570
1643
|
file,
|
|
1571
|
-
error: f.result.error
|
|
1644
|
+
error: f.result.error,
|
|
1645
|
+
...modeField
|
|
1572
1646
|
};
|
|
1573
1647
|
return {
|
|
1574
1648
|
file,
|
|
@@ -1576,33 +1650,65 @@ function serializeRelayReport(report, meta) {
|
|
|
1576
1650
|
passed: f.result.passed,
|
|
1577
1651
|
failed: f.result.failed,
|
|
1578
1652
|
skipped: f.result.skipped,
|
|
1579
|
-
tests: f.result.tests
|
|
1653
|
+
tests: f.result.tests,
|
|
1654
|
+
...modeField
|
|
1580
1655
|
};
|
|
1581
1656
|
})
|
|
1582
1657
|
};
|
|
1583
1658
|
}
|
|
1584
1659
|
/**
|
|
1585
1660
|
* Writes the serialised report to `<dir>/<sdkLine>.<platform>.json`, creating
|
|
1586
|
-
* `dir` if needed. Returns the absolute path written.
|
|
1661
|
+
* `dir` if needed. Returns the absolute path(s) written.
|
|
1587
1662
|
*
|
|
1588
1663
|
* The cell-suffixed filename keeps 2.x and 3.0 (and per-platform) runs as
|
|
1589
1664
|
* distinct artifacts in the same directory; the same cell metadata is also baked
|
|
1590
1665
|
* into the body so a renamed/moved file still carries its provenance.
|
|
1591
1666
|
*
|
|
1667
|
+
* Manual-run provenance (devtools#741): when `report.files` contains ANY
|
|
1668
|
+
* `mode: 'manual'` entry (i.e. this run included `--manual-blocking` files),
|
|
1669
|
+
* the manual-tagged files are written to a SEPARATE
|
|
1670
|
+
* `<dir>/<sdkLine>.<platform>.manual.json` artifact instead of the standard
|
|
1671
|
+
* one — the standard `<sdkLine>.<platform>.json` filename is reserved for the
|
|
1672
|
+
* regular (unattended) files only, so a manual run's presence never mutates
|
|
1673
|
+
* what the unattended-baseline filename means. If ALL files in the run are
|
|
1674
|
+
* regular, only the standard artifact is written (today's behavior,
|
|
1675
|
+
* unchanged). If a run mixes both (regular files + `--manual-blocking`
|
|
1676
|
+
* scheduled last), BOTH artifacts are written — the manual one ALONGSIDE, not
|
|
1677
|
+
* replacing, the standard one.
|
|
1678
|
+
*
|
|
1592
1679
|
* SECRET-HANDLING: the written body contains no relay/secret fields (the schema
|
|
1593
1680
|
* has none). `dir`/`projectRoot` are local filesystem paths, never logged here.
|
|
1594
1681
|
*
|
|
1595
1682
|
* @param report - The core relay run report.
|
|
1596
1683
|
* @param dir - Output directory (created recursively if missing).
|
|
1597
1684
|
* @param meta - Cell axes + projectRoot.
|
|
1598
|
-
* @returns The absolute path
|
|
1685
|
+
* @returns The absolute path(s) written, in order: standard first (if any
|
|
1686
|
+
* regular files ran), then manual (if any manual files ran). At least one
|
|
1687
|
+
* path is always returned when `report.files` is non-empty.
|
|
1599
1688
|
*/
|
|
1600
1689
|
async function writeReportArtifact(report, dir, meta) {
|
|
1601
1690
|
const serialised = serializeRelayReport(report, meta);
|
|
1602
1691
|
await mkdir(dir, { recursive: true });
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
|
|
1692
|
+
const regularFiles = serialised.files.filter((f) => f.mode !== "manual");
|
|
1693
|
+
const manualFiles = serialised.files.filter((f) => f.mode === "manual");
|
|
1694
|
+
const written = [];
|
|
1695
|
+
if (regularFiles.length > 0 || serialised.files.length === 0) {
|
|
1696
|
+
const outFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.json`);
|
|
1697
|
+
await writeFile(outFile, `${JSON.stringify({
|
|
1698
|
+
...serialised,
|
|
1699
|
+
files: regularFiles
|
|
1700
|
+
}, null, 2)}\n`, "utf8");
|
|
1701
|
+
written.push(outFile);
|
|
1702
|
+
}
|
|
1703
|
+
if (manualFiles.length > 0) {
|
|
1704
|
+
const manualOutFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.manual.json`);
|
|
1705
|
+
await writeFile(manualOutFile, `${JSON.stringify({
|
|
1706
|
+
...serialised,
|
|
1707
|
+
files: manualFiles
|
|
1708
|
+
}, null, 2)}\n`, "utf8");
|
|
1709
|
+
written.push(manualOutFile);
|
|
1710
|
+
}
|
|
1711
|
+
return written;
|
|
1606
1712
|
}
|
|
1607
1713
|
/**
|
|
1608
1714
|
* Writes harvested `__AIT_CAPTURE__` lines to per-category files under `dir`,
|
|
@@ -1688,6 +1794,24 @@ OPTIONS
|
|
|
1688
1794
|
--headless Disable browser auto-open (text QR only)
|
|
1689
1795
|
--project-root <dir> Project root for .ait_relay secret lookup
|
|
1690
1796
|
(default: current working directory)
|
|
1797
|
+
--manual-blocking Run manual-tagged test files (*.manual.ait.test.ts)
|
|
1798
|
+
LAST, after all regular files, with a human present.
|
|
1799
|
+
Before each manual file, the QR dashboard is pushed
|
|
1800
|
+
a step-by-step Korean prompt naming the file + its
|
|
1801
|
+
progress (k/n), and the same line is printed to
|
|
1802
|
+
stdout. Manual files get a 5-minute per-file evaluate
|
|
1803
|
+
timeout (vs. --timeout for everything else) since a
|
|
1804
|
+
human is expected to tap through a native sheet
|
|
1805
|
+
(photo picker, permission dialog, fullscreen ad).
|
|
1806
|
+
Without this flag (default off), *.manual.ait.test.ts
|
|
1807
|
+
files are EXCLUDED from the glob expansion entirely —
|
|
1808
|
+
existing unattended runs are byte-for-byte unaffected.
|
|
1809
|
+
With --report-dir, a run that included manual files
|
|
1810
|
+
ALSO writes <sdkLine>.<platform>.manual.json
|
|
1811
|
+
alongside (never replacing) the standard report, and
|
|
1812
|
+
each manual file's report entry is stamped
|
|
1813
|
+
mode: 'manual' — never diff a manual run against an
|
|
1814
|
+
unattended baseline as if they were equivalent.
|
|
1691
1815
|
--help, -h Show this help message
|
|
1692
1816
|
|
|
1693
1817
|
DESCRIPTION
|
|
@@ -1836,7 +1960,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1836
1960
|
"report-dir": { type: "string" },
|
|
1837
1961
|
"no-qr-stdout": { type: "boolean" },
|
|
1838
1962
|
headless: { type: "boolean" },
|
|
1839
|
-
"project-root": { type: "string" }
|
|
1963
|
+
"project-root": { type: "string" },
|
|
1964
|
+
"manual-blocking": { type: "boolean" }
|
|
1840
1965
|
},
|
|
1841
1966
|
allowPositionals: true
|
|
1842
1967
|
});
|
|
@@ -1867,6 +1992,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1867
1992
|
const projectRoot = typeof vals["project-root"] === "string" ? vals["project-root"] : process.cwd();
|
|
1868
1993
|
const reportDir = typeof vals["report-dir"] === "string" ? vals["report-dir"] : void 0;
|
|
1869
1994
|
const suppressQr = shouldSuppressQr(vals["no-qr-stdout"] === true);
|
|
1995
|
+
const manualBlocking = vals["manual-blocking"] === true;
|
|
1870
1996
|
const cellSdkLine = typeof vals["cell-sdk-line"] === "string" ? vals["cell-sdk-line"] : void 0;
|
|
1871
1997
|
const cellPlatform = typeof vals["cell-platform"] === "string" ? vals["cell-platform"] : process.env.AIT_CELL_PLATFORM;
|
|
1872
1998
|
const hasCell = cellSdkLine !== void 0 || cellPlatform !== void 0;
|
|
@@ -1881,13 +2007,16 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1881
2007
|
process.exitCode = 1;
|
|
1882
2008
|
return;
|
|
1883
2009
|
}
|
|
1884
|
-
const
|
|
1885
|
-
if (
|
|
2010
|
+
const discovered = await discoverTestFiles(globs, process.cwd(), { includeManual: manualBlocking });
|
|
2011
|
+
if (discovered.length === 0) {
|
|
1886
2012
|
process.stderr.write(`devtools-test: no test files matched ${globs.join(", ")}\n`);
|
|
1887
2013
|
process.exitCode = 1;
|
|
1888
2014
|
return;
|
|
1889
2015
|
}
|
|
1890
|
-
|
|
2016
|
+
const { regular, manual } = partitionManualTests(discovered);
|
|
2017
|
+
const files = manualBlocking ? [...regular, ...manual] : regular;
|
|
2018
|
+
const manualFileSet = new Set(manual);
|
|
2019
|
+
process.stderr.write(manualBlocking && manual.length > 0 ? `devtools-test: found ${regular.length} regular + ${manual.length} manual (${MANUAL_TEST_SUFFIX}) test file(s)\n` : `devtools-test: found ${files.length} test file(s)\n`);
|
|
1891
2020
|
if (hasCell) process.stderr.write(`devtools-test: injecting __AIT_CELL__ = ${JSON.stringify(cell)}\n`);
|
|
1892
2021
|
const factory = createRelayConnectionFactory({
|
|
1893
2022
|
schemeUrl,
|
|
@@ -1917,15 +2046,26 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1917
2046
|
const report = await runWithConnection(connection, files, {
|
|
1918
2047
|
timeoutMs: evaluateTimeoutMs,
|
|
1919
2048
|
printSummary: true,
|
|
1920
|
-
collectCaptures: reportDir !== void 0
|
|
2049
|
+
collectCaptures: reportDir !== void 0,
|
|
2050
|
+
manualFiles: manualBlocking && manualFileSet.size > 0 ? manualFileSet : void 0,
|
|
2051
|
+
onManualFile: (file, index, total) => {
|
|
2052
|
+
const name = basename(file);
|
|
2053
|
+
process.stdout.write(`수동 단계: ${name} — 폰에서 네이티브 시트가 뜨면 안내에 따라 조작하세요 (${index}/${total})\n`);
|
|
2054
|
+
factory.onManualPrompt?.({
|
|
2055
|
+
file: name,
|
|
2056
|
+
index,
|
|
2057
|
+
total
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
1921
2060
|
});
|
|
2061
|
+
if (manualBlocking && manualFileSet.size > 0) factory.onManualPrompt?.(null);
|
|
1922
2062
|
if (reportDir !== void 0) try {
|
|
1923
|
-
const
|
|
2063
|
+
const reportPaths = await writeReportArtifact(report, reportDir, {
|
|
1924
2064
|
sdkLine: cell.sdkLine,
|
|
1925
2065
|
platform: cell.platform,
|
|
1926
2066
|
projectRoot
|
|
1927
2067
|
});
|
|
1928
|
-
process.stderr.write(`devtools-test: wrote report ${reportPath}\n`);
|
|
2068
|
+
for (const reportPath of reportPaths) process.stderr.write(`devtools-test: wrote report ${reportPath}\n`);
|
|
1929
2069
|
const capturePaths = await writeCaptureArtifacts(report.captures, `${reportDir}/.ait-capture`, cell);
|
|
1930
2070
|
if (capturePaths.length > 0) process.stderr.write(`devtools-test: wrote ${capturePaths.length} capture file(s)\n`);
|
|
1931
2071
|
} catch (e) {
|