@ait-co/devtools 0.1.136 → 0.1.137
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/bundle-BKqyhEK9.d.ts.map +1 -1
- package/dist/{debug-server-DApo0o3t.js → debug-server-BQplCzf8.js} +21 -3
- package/dist/debug-server-BQplCzf8.js.map +1 -0
- package/dist/{debug-server-BZtH2Z5b.js → debug-server-CBK7dn8O.js} +19 -1
- package/dist/debug-server-CBK7dn8O.js.map +1 -0
- package/dist/mcp/cli.js +70 -19
- 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-C6TgrcyW.d.ts → pool-CmfvXnwh.d.ts} +2 -2
- package/dist/{pool-C6TgrcyW.d.ts.map → pool-CmfvXnwh.d.ts.map} +1 -1
- package/dist/{relay-factory-DnuTH4v-.js → relay-factory-DVzpBRxz.js} +3 -2
- package/dist/relay-factory-DVzpBRxz.js.map +1 -0
- package/dist/{relay-worker-UK0nBfDX.js → relay-worker-6fy1eaIo.js} +6 -3
- package/dist/{relay-worker-UK0nBfDX.js.map → relay-worker-6fy1eaIo.js.map} +1 -1
- package/dist/{relay-worker-Dw3aIzIK.d.ts → relay-worker-DJnZkXza.d.ts} +41 -7
- package/dist/relay-worker-DJnZkXza.d.ts.map +1 -0
- package/dist/test-runner/bin.js +108 -27
- package/dist/test-runner/bin.js.map +1 -1
- package/dist/test-runner/bridge-stub.d.ts +125 -0
- package/dist/test-runner/bridge-stub.d.ts.map +1 -0
- package/dist/test-runner/bridge-stub.js +92 -0
- package/dist/test-runner/bridge-stub.js.map +1 -0
- package/dist/test-runner/bundle.js +44 -14
- package/dist/test-runner/bundle.js.map +1 -1
- package/dist/test-runner/config.d.ts +11 -1
- package/dist/test-runner/config.d.ts.map +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/relay-factory.d.ts +10 -0
- package/dist/test-runner/relay-factory.d.ts.map +1 -1
- package/dist/test-runner/relay-factory.js +2 -1
- package/dist/test-runner/relay-factory.js.map +1 -1
- package/dist/test-runner/relay-worker.d.ts +1 -1
- package/dist/test-runner/relay-worker.js +1 -1
- package/dist/test-runner/report.d.ts +41 -15
- package/dist/test-runner/report.d.ts.map +1 -1
- package/dist/test-runner/report.js +33 -9
- package/dist/test-runner/report.js.map +1 -1
- package/package.json +1 -1
- package/dist/debug-server-BZtH2Z5b.js.map +0 -1
- package/dist/debug-server-DApo0o3t.js.map +0 -1
- package/dist/relay-factory-DnuTH4v-.js.map +0 -1
- package/dist/relay-worker-Dw3aIzIK.d.ts.map +0 -1
package/dist/test-runner/bin.js
CHANGED
|
@@ -210,6 +210,7 @@ function createRelayConnectionFactory(opts) {
|
|
|
210
210
|
}
|
|
211
211
|
await injectDebugIndicator(booted.connection);
|
|
212
212
|
if (opts.cell !== void 0) await injectGlobals(booted.connection, { __AIT_CELL__: opts.cell });
|
|
213
|
+
if (opts.stubBlocking === true) await injectGlobals(booted.connection, { __AIT_STUB_BLOCKING__: true });
|
|
213
214
|
return booted.connection;
|
|
214
215
|
},
|
|
215
216
|
onSessionPhase(next) {
|
|
@@ -906,6 +907,16 @@ const SDK_IMPORT_FILTER = new RegExp(`^${SDK_PACKAGE.replace(/[.*+?^${}()|[\]\\]
|
|
|
906
907
|
*
|
|
907
908
|
* If `window.__sdk` is absent (non-dog-food build), every access throws a
|
|
908
909
|
* descriptive error rather than returning `undefined` silently.
|
|
910
|
+
*
|
|
911
|
+
* Bridge-stub interception (devtools#740, DT-2): the virtual module runs the
|
|
912
|
+
* resolved `window.__sdk` object through `wrapSdkWithStub` before exporting
|
|
913
|
+
* it. `wrapSdkWithStub` is a pass-through (returns the same object,
|
|
914
|
+
* unwrapped) unless `isStubBlockingEnabled()` reads `true` off
|
|
915
|
+
* `globalThis.__AIT_STUB_BLOCKING__` — an opt-in flag injected the same way
|
|
916
|
+
* `__AIT_CELL__` is (`cell.ts#injectGlobals`, wired from the CLI's
|
|
917
|
+
* `--stub-blocking` flag). Default off => zero behavior change: every existing
|
|
918
|
+
* (non-stub) test run resolves the exact same `window.__sdk` reference it did
|
|
919
|
+
* before this plugin learned about stubbing.
|
|
909
920
|
*/
|
|
910
921
|
function sdkRedirectPlugin() {
|
|
911
922
|
return {
|
|
@@ -920,16 +931,19 @@ function sdkRedirectPlugin() {
|
|
|
920
931
|
namespace: "sdk-redirect"
|
|
921
932
|
}, () => ({
|
|
922
933
|
contents: `
|
|
923
|
-
|
|
934
|
+
import { wrapSdkWithStub, isStubBlockingEnabled } from ${JSON.stringify(getBridgeStubPath())};
|
|
935
|
+
var __rawSdk = (typeof window !== 'undefined' && window.__sdk)
|
|
924
936
|
? window.__sdk
|
|
925
937
|
: new Proxy({}, {
|
|
926
938
|
get: function(_t, p) {
|
|
927
939
|
throw new Error('window.__sdk is not installed — run in a dog-food build. Missing: ' + String(p));
|
|
928
940
|
}
|
|
929
941
|
});
|
|
942
|
+
var __proxy = wrapSdkWithStub(__rawSdk, isStubBlockingEnabled());
|
|
930
943
|
module.exports = __proxy;
|
|
931
944
|
`,
|
|
932
|
-
loader: "js"
|
|
945
|
+
loader: "js",
|
|
946
|
+
resolveDir: process.cwd()
|
|
933
947
|
}));
|
|
934
948
|
}
|
|
935
949
|
};
|
|
@@ -1063,8 +1077,9 @@ function userFactoryPlugin(absPath) {
|
|
|
1063
1077
|
};
|
|
1064
1078
|
}
|
|
1065
1079
|
/**
|
|
1066
|
-
* Returns the absolute filesystem path to
|
|
1067
|
-
* (
|
|
1080
|
+
* Returns the absolute filesystem path to a page-side leaf module shipped
|
|
1081
|
+
* alongside `bundle.ts` in dist (e.g. `runtime.js`, `bridge-stub.js`) — a
|
|
1082
|
+
* fully self-contained module with no further page-side deps of its own.
|
|
1068
1083
|
*
|
|
1069
1084
|
* Rolldown code-splitting duplicates this bundling logic into shared chunks
|
|
1070
1085
|
* emitted at ARBITRARY dist depths: the `devtools-test` CLI pulls it from
|
|
@@ -1074,26 +1089,30 @@ function userFactoryPlugin(absPath) {
|
|
|
1074
1089
|
* is therefore wrong from at least one chunk — the live #697 regression.
|
|
1075
1090
|
*
|
|
1076
1091
|
* This resolves WITHOUT assuming chunk depth: from `import.meta.url`'s dir it
|
|
1077
|
-
* probes the co-located
|
|
1092
|
+
* probes the co-located `<name>.js` and the nested `test-runner/<name>.js`,
|
|
1078
1093
|
* then ascends one directory at a time (bounded) repeating both probes. The
|
|
1079
|
-
* nested probe catches dist/test-runner
|
|
1094
|
+
* nested probe catches dist/test-runner/<name>.js from the dist/ root level no
|
|
1080
1095
|
* matter which depth the chunk was hoisted to (root, dist/mcp/, or a future
|
|
1081
|
-
* relocation). The build always emits dist/test-runner
|
|
1082
|
-
* `'test-runner
|
|
1096
|
+
* relocation). The build always emits dist/test-runner/<name>.js (tsdown entry
|
|
1097
|
+
* `'test-runner/<name>'`; guarded by scripts/check-test-runner-dist.sh for
|
|
1098
|
+
* `runtime`).
|
|
1083
1099
|
*
|
|
1084
1100
|
* An ABSOLUTE path is returned deliberately: esbuild loads it as a literal file
|
|
1085
1101
|
* read, bypassing Node module resolution entirely, so this works identically in
|
|
1086
1102
|
* the npx-daemon context (its own dist tree) and the consumer-CLI context
|
|
1087
1103
|
* (the mini-app's installed @ait-co/devtools dist) — neither needs the package
|
|
1088
1104
|
* to be node-resolvable from the caller.
|
|
1105
|
+
*
|
|
1106
|
+
* @param moduleName - The leaf module's basename without extension, e.g.
|
|
1107
|
+
* `'runtime'` or `'bridge-stub'`.
|
|
1089
1108
|
*/
|
|
1090
|
-
function
|
|
1109
|
+
function getPageSideModulePath(moduleName) {
|
|
1091
1110
|
const startDir = path$1.dirname(fileURLToPath(import.meta.url));
|
|
1092
1111
|
const RELATIVE_PROBES = [
|
|
1093
|
-
[
|
|
1094
|
-
["test-runner",
|
|
1095
|
-
[
|
|
1096
|
-
["test-runner",
|
|
1112
|
+
[`${moduleName}.js`],
|
|
1113
|
+
["test-runner", `${moduleName}.js`],
|
|
1114
|
+
[`${moduleName}.ts`],
|
|
1115
|
+
["test-runner", `${moduleName}.ts`]
|
|
1097
1116
|
];
|
|
1098
1117
|
let dir = startDir;
|
|
1099
1118
|
for (let i = 0; i < 12; i++) {
|
|
@@ -1108,7 +1127,19 @@ function getRuntimePath() {
|
|
|
1108
1127
|
if (parent === dir) break;
|
|
1109
1128
|
dir = parent;
|
|
1110
1129
|
}
|
|
1111
|
-
return path$1.join(startDir,
|
|
1130
|
+
return path$1.join(startDir, `${moduleName}.js`);
|
|
1131
|
+
}
|
|
1132
|
+
/** Absolute path to the test-runner page-side runtime (describe/it/test/expect). */
|
|
1133
|
+
function getRuntimePath() {
|
|
1134
|
+
return getPageSideModulePath("runtime");
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Absolute path to the bridge-stub interceptor (devtools#740, DT-2) —
|
|
1138
|
+
* `wrapSdkWithStub`/`isStubBlockingEnabled`, statically imported by the
|
|
1139
|
+
* `sdk-redirect` virtual module.
|
|
1140
|
+
*/
|
|
1141
|
+
function getBridgeStubPath() {
|
|
1142
|
+
return getPageSideModulePath("bridge-stub");
|
|
1112
1143
|
}
|
|
1113
1144
|
/**
|
|
1114
1145
|
* Bundles `absPath` into a single IIFE string suitable for `Runtime.evaluate`.
|
|
@@ -1382,6 +1413,7 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1382
1413
|
const manualFiles = opts?.manualFiles;
|
|
1383
1414
|
const manualTotal = manualFiles?.size ?? 0;
|
|
1384
1415
|
let manualIndex = 0;
|
|
1416
|
+
const stubBlockingFiles = opts?.stubBlockingFiles;
|
|
1385
1417
|
let preflightPermissions;
|
|
1386
1418
|
if (files.length > 0) preflightPermissions = await runPermissionPreflight(connection);
|
|
1387
1419
|
try {
|
|
@@ -1390,7 +1422,8 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1390
1422
|
await attemptReconnect("next-file");
|
|
1391
1423
|
pendingReconnectCheck = false;
|
|
1392
1424
|
}
|
|
1393
|
-
const
|
|
1425
|
+
const isStubbed = stubBlockingFiles?.has(file) === true;
|
|
1426
|
+
const isManual = !isStubbed && manualFiles?.has(file) === true;
|
|
1394
1427
|
if (isManual) {
|
|
1395
1428
|
manualIndex += 1;
|
|
1396
1429
|
opts?.onManualFile?.(file, manualIndex, manualTotal);
|
|
@@ -1457,7 +1490,8 @@ async function runTestFilesOverRelay(connection, files, opts) {
|
|
|
1457
1490
|
};
|
|
1458
1491
|
}
|
|
1459
1492
|
if ("error" in fileEntry.result && isRelayDisconnectMessage(fileEntry.result.error)) pendingReconnectCheck = true;
|
|
1460
|
-
if (
|
|
1493
|
+
if (isStubbed) fileEntry.mode = "stubbed";
|
|
1494
|
+
else if (isManual) fileEntry.mode = "manual";
|
|
1461
1495
|
fileResults.push(fileEntry);
|
|
1462
1496
|
}
|
|
1463
1497
|
} finally {
|
|
@@ -1573,7 +1607,7 @@ function serializeRelayReport(report, meta) {
|
|
|
1573
1607
|
...report.preflight ? { preflight: report.preflight } : {},
|
|
1574
1608
|
files: report.files.map((f) => {
|
|
1575
1609
|
const file = relativise(meta.projectRoot, f.file);
|
|
1576
|
-
const modeField = f.mode === "manual" ? { mode:
|
|
1610
|
+
const modeField = f.mode === "manual" || f.mode === "stubbed" ? { mode: f.mode } : {};
|
|
1577
1611
|
if ("error" in f.result) return {
|
|
1578
1612
|
file,
|
|
1579
1613
|
error: f.result.error,
|
|
@@ -1605,11 +1639,21 @@ function serializeRelayReport(report, meta) {
|
|
|
1605
1639
|
* `<dir>/<sdkLine>.<platform>.manual.json` artifact instead of the standard
|
|
1606
1640
|
* one — the standard `<sdkLine>.<platform>.json` filename is reserved for the
|
|
1607
1641
|
* regular (unattended) files only, so a manual run's presence never mutates
|
|
1608
|
-
* what the unattended-baseline filename means.
|
|
1609
|
-
*
|
|
1610
|
-
*
|
|
1611
|
-
*
|
|
1612
|
-
*
|
|
1642
|
+
* what the unattended-baseline filename means.
|
|
1643
|
+
*
|
|
1644
|
+
* Stub-blocking provenance (devtools#740, DT-2): `mode: 'stubbed'` files are
|
|
1645
|
+
* written to a THIRD, separate `<dir>/<sdkLine>.<platform>.stubbed.json`
|
|
1646
|
+
* artifact — never merged into the standard file (it is unattended, like the
|
|
1647
|
+
* standard file, so a naive filename split could conflate the two) and never
|
|
1648
|
+
* merged into `.manual.json` (it did NOT have a human present). Its body is
|
|
1649
|
+
* ALSO stamped `cell.bridgeStub: true` — the mandatory HYBRID-cell provenance
|
|
1650
|
+
* from the issue: a stubbed result must never be silently mixed into the
|
|
1651
|
+
* real-device baseline report.
|
|
1652
|
+
*
|
|
1653
|
+
* If ALL files in the run are regular, only the standard artifact is written
|
|
1654
|
+
* (today's behavior, unchanged). Any combination of the three modes present
|
|
1655
|
+
* in a single run writes ONLY the corresponding artifacts — each is additive,
|
|
1656
|
+
* never replacing another.
|
|
1613
1657
|
*
|
|
1614
1658
|
* SECRET-HANDLING: the written body contains no relay/secret fields (the schema
|
|
1615
1659
|
* has none). `dir`/`projectRoot` are local filesystem paths, never logged here.
|
|
@@ -1618,14 +1662,16 @@ function serializeRelayReport(report, meta) {
|
|
|
1618
1662
|
* @param dir - Output directory (created recursively if missing).
|
|
1619
1663
|
* @param meta - Cell axes + projectRoot.
|
|
1620
1664
|
* @returns The absolute path(s) written, in order: standard first (if any
|
|
1621
|
-
* regular files ran), then manual (if any manual files ran)
|
|
1622
|
-
* path is always returned when
|
|
1665
|
+
* regular files ran), then manual (if any manual files ran), then stubbed
|
|
1666
|
+
* (if any stubbed files ran). At least one path is always returned when
|
|
1667
|
+
* `report.files` is non-empty.
|
|
1623
1668
|
*/
|
|
1624
1669
|
async function writeReportArtifact(report, dir, meta) {
|
|
1625
1670
|
const serialised = serializeRelayReport(report, meta);
|
|
1626
1671
|
await mkdir(dir, { recursive: true });
|
|
1627
|
-
const regularFiles = serialised.files.filter((f) => f.mode
|
|
1672
|
+
const regularFiles = serialised.files.filter((f) => f.mode === void 0);
|
|
1628
1673
|
const manualFiles = serialised.files.filter((f) => f.mode === "manual");
|
|
1674
|
+
const stubbedFiles = serialised.files.filter((f) => f.mode === "stubbed");
|
|
1629
1675
|
const written = [];
|
|
1630
1676
|
if (regularFiles.length > 0 || serialised.files.length === 0) {
|
|
1631
1677
|
const outFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.json`);
|
|
@@ -1643,6 +1689,18 @@ async function writeReportArtifact(report, dir, meta) {
|
|
|
1643
1689
|
}, null, 2)}\n`, "utf8");
|
|
1644
1690
|
written.push(manualOutFile);
|
|
1645
1691
|
}
|
|
1692
|
+
if (stubbedFiles.length > 0) {
|
|
1693
|
+
const stubbedOutFile = path.join(dir, `${meta.sdkLine}.${meta.platform}.stubbed.json`);
|
|
1694
|
+
await writeFile(stubbedOutFile, `${JSON.stringify({
|
|
1695
|
+
...serialised,
|
|
1696
|
+
cell: {
|
|
1697
|
+
...serialised.cell,
|
|
1698
|
+
bridgeStub: true
|
|
1699
|
+
},
|
|
1700
|
+
files: stubbedFiles
|
|
1701
|
+
}, null, 2)}\n`, "utf8");
|
|
1702
|
+
written.push(stubbedOutFile);
|
|
1703
|
+
}
|
|
1646
1704
|
return written;
|
|
1647
1705
|
}
|
|
1648
1706
|
/**
|
|
@@ -1822,6 +1880,24 @@ OPTIONS
|
|
|
1822
1880
|
each manual file's report entry is stamped
|
|
1823
1881
|
mode: 'manual' — never diff a manual run against an
|
|
1824
1882
|
unattended baseline as if they were equivalent.
|
|
1883
|
+
--stub-blocking Run manual-tagged test files (*.manual.ait.test.ts)
|
|
1884
|
+
UNATTENDED (devtools#740, DT-2) by intercepting a
|
|
1885
|
+
fixed allowlist of blocking-UI SDK calls (ads
|
|
1886
|
+
show*, openPermissionDialog/requestPermission,
|
|
1887
|
+
saveBase64Data) in the page and answering them from
|
|
1888
|
+
fixtures captured by a real --manual-blocking run,
|
|
1889
|
+
instead of forwarding them to native UI. Implies
|
|
1890
|
+
--manual-blocking (manual files are included in the
|
|
1891
|
+
run); no human presence or QR-dashboard prompt is
|
|
1892
|
+
needed for them. HYBRID cell, not pure env3 — every
|
|
1893
|
+
other SDK call in the same run still hits the real
|
|
1894
|
+
native bridge. With --report-dir, files that ran
|
|
1895
|
+
under the stub are written to a SEPARATE
|
|
1896
|
+
<sdkLine>.<platform>.stubbed.json artifact (never
|
|
1897
|
+
merged into the standard or .manual.json report) and
|
|
1898
|
+
the report body is stamped cell.bridgeStub: true —
|
|
1899
|
+
never diff a stubbed run against a real device
|
|
1900
|
+
baseline (manual or unattended) as if equivalent.
|
|
1825
1901
|
--help, -h Show this help message
|
|
1826
1902
|
|
|
1827
1903
|
DESCRIPTION
|
|
@@ -2000,7 +2076,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2000
2076
|
"no-qr-stdout": { type: "boolean" },
|
|
2001
2077
|
headless: { type: "boolean" },
|
|
2002
2078
|
"project-root": { type: "string" },
|
|
2003
|
-
"manual-blocking": { type: "boolean" }
|
|
2079
|
+
"manual-blocking": { type: "boolean" },
|
|
2080
|
+
"stub-blocking": { type: "boolean" }
|
|
2004
2081
|
},
|
|
2005
2082
|
allowPositionals: true
|
|
2006
2083
|
});
|
|
@@ -2037,7 +2114,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2037
2114
|
return;
|
|
2038
2115
|
}
|
|
2039
2116
|
const suppressQr = shouldSuppressQr(vals["no-qr-stdout"] === true);
|
|
2040
|
-
const
|
|
2117
|
+
const stubBlocking = vals["stub-blocking"] === true;
|
|
2118
|
+
const manualBlocking = vals["manual-blocking"] === true || stubBlocking;
|
|
2041
2119
|
const cellSdkLine = typeof vals["cell-sdk-line"] === "string" ? vals["cell-sdk-line"] : void 0;
|
|
2042
2120
|
const cellPlatform = typeof vals["cell-platform"] === "string" ? vals["cell-platform"] : process.env.AIT_CELL_PLATFORM;
|
|
2043
2121
|
const hasCell = cellSdkLine !== void 0 || cellPlatform !== void 0;
|
|
@@ -2063,6 +2141,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2063
2141
|
const manualFileSet = new Set(manual);
|
|
2064
2142
|
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`);
|
|
2065
2143
|
if (hasCell) process.stderr.write(`devtools-test: injecting __AIT_CELL__ = ${JSON.stringify(cell)}\n`);
|
|
2144
|
+
if (stubBlocking) process.stderr.write("devtools-test: --stub-blocking enabled — blocking-UI calls in manual-tagged files will be answered from fixtures (devtools#740), not forwarded to native UI\n");
|
|
2066
2145
|
const factory = createRelayConnectionFactory({
|
|
2067
2146
|
schemeUrl,
|
|
2068
2147
|
projectRoot,
|
|
@@ -2070,6 +2149,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2070
2149
|
...dashboardPort !== void 0 ? { dashboardPort } : {},
|
|
2071
2150
|
headless,
|
|
2072
2151
|
cell: hasCell ? cell : void 0,
|
|
2152
|
+
stubBlocking,
|
|
2073
2153
|
onQrContent: (chunks) => {
|
|
2074
2154
|
if (suppressQr) {
|
|
2075
2155
|
process.stdout.write("QR suppressed (non-interactive)\n");
|
|
@@ -2094,6 +2174,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2094
2174
|
printSummary: true,
|
|
2095
2175
|
collectCaptures: reportDir !== void 0,
|
|
2096
2176
|
manualFiles: manualBlocking && manualFileSet.size > 0 ? manualFileSet : void 0,
|
|
2177
|
+
stubBlockingFiles: stubBlocking && manualFileSet.size > 0 ? manualFileSet : void 0,
|
|
2097
2178
|
onManualFile: (file, index, total) => {
|
|
2098
2179
|
const name = basename(file);
|
|
2099
2180
|
process.stdout.write(`수동 단계: ${name} — 폰에서 네이티브 시트가 뜨면 안내에 따라 조작하세요 (${index}/${total})\n`);
|