@weapp-vite/miniprogram-automator 1.2.13 → 1.2.15

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/index.mjs CHANGED
@@ -217,9 +217,222 @@ function cmpVersion(versionA, versionB) {
217
217
  }
218
218
  return 0;
219
219
  }
220
- /** toStr 的方法封装。 */
221
- function toStr(value) {
222
- return value == null ? "" : String(value);
220
+ //#endregion
221
+ //#region src/pageRouteSnapshot.ts
222
+ const ROUTE_ELEMENT_SNAPSHOT_TIMEOUT = 5e3;
223
+ const ROUTE_ELEMENT_SNAPSHOT_EMPTY_RETRIES = 2;
224
+ const ROUTE_ELEMENT_SNAPSHOT_RETRY_DELAY = 220;
225
+ /**
226
+ * 页面定位 + 组件作用域遍历逻辑与 Page.renderedNodes 保持一致:
227
+ * 先按 route/query 找到目标页面,再逐层进入自定义组件作用域查询,
228
+ * 返回首个命中作用域里 selectAll(selector)[index] 的快照;未命中返回 null。
229
+ */
230
+ const ROUTE_ELEMENT_SNAPSHOT_FUNCTION_DECLARATION = `function (route, query, selector, index, scopeSelectors, styleNames) {
231
+ function normalizeRoute(value) {
232
+ return String(value || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
233
+ }
234
+ function matchesQuery(page, expectedQuery) {
235
+ if (!expectedQuery || !Object.keys(expectedQuery).length) {
236
+ return true;
237
+ }
238
+ var actualQuery = page && (page.options || page.query || {});
239
+ return Object.keys(expectedQuery).every(function (key) {
240
+ var actualValue = String(actualQuery[key] == null ? '' : actualQuery[key]);
241
+ var expectedValue = String(expectedQuery[key]);
242
+ var decodedActualValue = actualValue;
243
+ try {
244
+ decodedActualValue = decodeURIComponent(actualValue);
245
+ }
246
+ catch (_) {
247
+ }
248
+ return actualValue === expectedValue
249
+ || decodedActualValue === expectedValue
250
+ || actualValue === encodeURIComponent(expectedValue);
251
+ });
252
+ }
253
+ var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
254
+ var expectedRoute = normalizeRoute(route);
255
+ var page = null;
256
+ for (var pageIndex = pages.length - 1; pageIndex >= 0; pageIndex -= 1) {
257
+ var candidate = pages[pageIndex];
258
+ var candidateRoute = normalizeRoute(candidate && (candidate.path || candidate.route || candidate.__route__));
259
+ if ((!expectedRoute || candidateRoute === expectedRoute) && matchesQuery(candidate, query)) {
260
+ page = candidate;
261
+ break;
262
+ }
263
+ }
264
+ if (!page) {
265
+ return null;
266
+ }
267
+ function createSelectorQuery(scope) {
268
+ var selectorQuery = null;
269
+ var createdFromScope = false;
270
+ if (scope && typeof scope.createSelectorQuery === 'function') {
271
+ selectorQuery = scope.createSelectorQuery();
272
+ createdFromScope = true;
273
+ }
274
+ else if (typeof wx !== 'undefined' && wx && typeof wx.createSelectorQuery === 'function') {
275
+ selectorQuery = wx.createSelectorQuery();
276
+ }
277
+ else if (typeof page.createSelectorQuery === 'function') {
278
+ selectorQuery = page.createSelectorQuery();
279
+ }
280
+ if (!selectorQuery) {
281
+ return null;
282
+ }
283
+ if (!createdFromScope && typeof selectorQuery.in === 'function') {
284
+ try {
285
+ selectorQuery = selectorQuery.in(scope || page);
286
+ }
287
+ catch (_) {
288
+ }
289
+ }
290
+ return selectorQuery;
291
+ }
292
+ function pushUnique(list, seen, item) {
293
+ if (!item) {
294
+ return;
295
+ }
296
+ var id = item.is || item.id || item.__wxWebviewId__ || item.__wxExparserNodeId__ || String(list.length);
297
+ if (seen[id]) {
298
+ return;
299
+ }
300
+ seen[id] = true;
301
+ list.push(item);
302
+ }
303
+ function collectScopes(root, scopeSelectors) {
304
+ var scopes = [];
305
+ var seen = {};
306
+ var queue = [];
307
+ pushUnique(scopes, seen, root);
308
+ queue.push(root);
309
+ for (var queueIndex = 0; queueIndex < queue.length && queueIndex < 60; queueIndex += 1) {
310
+ var scope = queue[queueIndex];
311
+ if (!scope || typeof scope.selectAllComponents !== 'function') {
312
+ continue;
313
+ }
314
+ var children = [];
315
+ var componentSelectors = Array.isArray(scopeSelectors) ? scopeSelectors.slice() : [];
316
+ componentSelectors.push(selector, '*', 'weapp-app-shell', 'weapp-layout-default');
317
+ for (var selectorIndex = 0; selectorIndex < componentSelectors.length; selectorIndex += 1) {
318
+ if (!componentSelectors[selectorIndex]) {
319
+ continue;
320
+ }
321
+ try {
322
+ var selected = scope.selectAllComponents(componentSelectors[selectorIndex]);
323
+ if (Array.isArray(selected)) {
324
+ children = children.concat(selected);
325
+ }
326
+ }
327
+ catch (_) {
328
+ }
329
+ }
330
+ for (var childIndex = 0; childIndex < children.length; childIndex += 1) {
331
+ var child = children[childIndex];
332
+ var previousLength = scopes.length;
333
+ pushUnique(scopes, seen, child);
334
+ if (scopes.length > previousLength) {
335
+ queue.push(child);
336
+ }
337
+ }
338
+ }
339
+ return scopes;
340
+ }
341
+ function queryScope(scope) {
342
+ return new Promise(function (resolve) {
343
+ var settled = false;
344
+ var timer = setTimeout(function () {
345
+ if (settled) {
346
+ return;
347
+ }
348
+ settled = true;
349
+ resolve([]);
350
+ }, 800);
351
+ function finish(nodes) {
352
+ if (settled) {
353
+ return;
354
+ }
355
+ settled = true;
356
+ clearTimeout(timer);
357
+ resolve(Array.isArray(nodes) ? nodes : nodes ? [nodes] : []);
358
+ }
359
+ try {
360
+ var selectorQuery = createSelectorQuery(scope);
361
+ if (!selectorQuery) {
362
+ finish([]);
363
+ return;
364
+ }
365
+ selectorQuery
366
+ .selectAll(selector)
367
+ .fields({
368
+ id: true,
369
+ dataset: true,
370
+ rect: true,
371
+ size: true,
372
+ computedStyle: Array.isArray(styleNames) ? styleNames : []
373
+ }, function (nodes) {
374
+ finish(nodes);
375
+ })
376
+ .exec();
377
+ }
378
+ catch (_) {
379
+ finish([]);
380
+ }
381
+ });
382
+ }
383
+ return new Promise(function (resolve) {
384
+ try {
385
+ var scopes = collectScopes(page, scopeSelectors);
386
+ var scopeIndex = 0;
387
+ function next() {
388
+ if (scopeIndex >= scopes.length) {
389
+ resolve(null);
390
+ return;
391
+ }
392
+ var scope = scopes[scopeIndex];
393
+ scopeIndex += 1;
394
+ queryScope(scope).then(function (nodes) {
395
+ if (nodes.length > 0) {
396
+ resolve(nodes[index] || null);
397
+ return;
398
+ }
399
+ next();
400
+ }, function () {
401
+ next();
402
+ });
403
+ }
404
+ next();
405
+ }
406
+ catch (_) {
407
+ resolve(null);
408
+ }
409
+ });
410
+ }`;
411
+ /**
412
+ * 读取降级元素快照。未找到(页面不在/选择器无命中/索引越界)返回 null,
413
+ * 由调用方决定抛错文案;永不因为协议失效而长时间挂起。
414
+ */
415
+ async function readRouteElementSnapshot(connection, route, query, selector, index, scopeSelectors = [], styleNames = [], timeout = ROUTE_ELEMENT_SNAPSHOT_TIMEOUT) {
416
+ const startedAt = Date.now();
417
+ for (let attempt = 0; attempt <= ROUTE_ELEMENT_SNAPSHOT_EMPTY_RETRIES; attempt += 1) {
418
+ const remaining = attempt === 0 ? timeout : Math.max(1, timeout - (Date.now() - startedAt));
419
+ const { result } = await connection.send("App.callFunction", {
420
+ functionDeclaration: ROUTE_ELEMENT_SNAPSHOT_FUNCTION_DECLARATION,
421
+ args: [
422
+ route,
423
+ query,
424
+ selector,
425
+ index,
426
+ scopeSelectors,
427
+ styleNames
428
+ ]
429
+ }, { timeout: remaining });
430
+ if (result != null || attempt === ROUTE_ELEMENT_SNAPSHOT_EMPTY_RETRIES) return result ?? null;
431
+ const retryRemaining = timeout - (Date.now() - startedAt);
432
+ if (retryRemaining <= 0) break;
433
+ await sleep$1(Math.min(ROUTE_ELEMENT_SNAPSHOT_RETRY_DELAY, retryRemaining));
434
+ }
435
+ return null;
223
436
  }
224
437
  //#endregion
225
438
  //#region src/Element.ts
@@ -272,7 +485,17 @@ var Element = class Element {
272
485
  };
273
486
  }
274
487
  async offset() {
275
- return await this.send("Element.getOffset");
488
+ const offset = await this.send("Element.getOffset");
489
+ if (offset?.width !== void 0 && offset.height !== void 0) return offset;
490
+ try {
491
+ const dimensions = await this.size();
492
+ return {
493
+ ...offset,
494
+ ...dimensions
495
+ };
496
+ } catch {
497
+ return offset;
498
+ }
276
499
  }
277
500
  async text() {
278
501
  return await this.domProperty("innerText");
@@ -394,8 +617,87 @@ var Element = class Element {
394
617
  };
395
618
  /** App-Service 路由降级元素仅用于只读查询,不能伪装成交互协议元素。 */
396
619
  var RouteFallbackElement = class extends Element {
620
+ routeSelector;
621
+ routeIndex;
622
+ routeRoute;
623
+ routeQuery;
624
+ routeScopeSelectors;
625
+ constructor(connection, options, elementMap) {
626
+ super(connection, options, elementMap);
627
+ this.routeSelector = options.routeFallbackSelector;
628
+ this.routeIndex = options.routeFallbackIndex;
629
+ this.routeRoute = options.routeFallbackRoute;
630
+ this.routeQuery = options.routeFallbackQuery;
631
+ this.routeScopeSelectors = options.routeFallbackScopeSelectors ?? [];
632
+ }
633
+ /**
634
+ * 经 App-service SelectorQuery 实时重查元素快照(rect/size/dataset/computedStyle)。
635
+ * 每次读取都重新查询,保证滚动/重渲染后拿到的是当前值;未命中返回 null。
636
+ */
637
+ async readSnapshot(styleNames = []) {
638
+ if (!this.routeSelector || this.routeIndex === void 0 || !this.routeRoute) return null;
639
+ return await readRouteElementSnapshot(this.connection, this.routeRoute, this.routeQuery ?? {}, this.routeSelector, this.routeIndex, this.routeScopeSelectors, styleNames);
640
+ }
641
+ missingSnapshot(what) {
642
+ return /* @__PURE__ */ new Error(`route fallback 元素未取到${what}: ${this.routeSelector ?? "(未知选择器)"}(页面已切换或元素已卸载)`);
643
+ }
644
+ async $(selector, _options = {}) {
645
+ return this.unsupported(`Element.$(${selector})`);
646
+ }
647
+ async $$(selector, _options = {}) {
648
+ return this.unsupported(`Element.$$(${selector})`);
649
+ }
650
+ async offset() {
651
+ const snapshot = await this.readSnapshot();
652
+ if (snapshot?.left === void 0 || snapshot.top === void 0) throw this.missingSnapshot("坐标");
653
+ return {
654
+ left: snapshot.left,
655
+ top: snapshot.top,
656
+ width: snapshot.width,
657
+ height: snapshot.height
658
+ };
659
+ }
660
+ async size() {
661
+ const snapshot = await this.readSnapshot();
662
+ if (snapshot?.width === void 0 || snapshot.height === void 0) throw this.missingSnapshot("尺寸");
663
+ return {
664
+ width: snapshot.width,
665
+ height: snapshot.height
666
+ };
667
+ }
668
+ async style(name) {
669
+ if (!isStr(name)) throw new Error("name must be a string");
670
+ const value = (await this.readSnapshot([name]))?.[name];
671
+ if (value === void 0) throw this.missingSnapshot(`样式 ${name}`);
672
+ return value;
673
+ }
674
+ async attribute(name) {
675
+ if (!isStr(name)) throw new Error("name must be a string");
676
+ const snapshot = await this.readSnapshot();
677
+ if (name === "id") return snapshot?.id;
678
+ if (name.startsWith("data-")) {
679
+ const key = name.slice(5).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
680
+ return snapshot?.dataset?.[key];
681
+ }
682
+ throw new Error(`route fallback 元素仅支持读取 id 与 data-* 属性,不支持 attribute(${name})`);
683
+ }
397
684
  unsupported(method) {
398
- throw new Error(`App-Service route fallback 元素不支持 ${method}`);
685
+ throw new Error(`App-Service route fallback 元素不支持 ${method}(真实交互依赖已失效的 page-frame 协议);可改用 page.callMethod()/evaluate 间接触发页面逻辑`);
686
+ }
687
+ async text() {
688
+ throw new Error("App-Service route fallback 元素不支持 text(SelectorQuery 无法读取 innerText);请改用 page.data() 断言数据源,或 page.callMethod()/evaluate 读取");
689
+ }
690
+ async value() {
691
+ this.unsupported("Element.value");
692
+ }
693
+ async property(name) {
694
+ this.unsupported(`Element.property(${name})`);
695
+ }
696
+ async wxml() {
697
+ this.unsupported("Element.wxml");
698
+ }
699
+ async outerWxml() {
700
+ this.unsupported("Element.outerWxml");
399
701
  }
400
702
  async tap() {
401
703
  this.unsupported("Element.tap");
@@ -640,7 +942,7 @@ var Connection = class Connection extends EventEmitter {
640
942
  //#endregion
641
943
  //#region src/headless.ts
642
944
  async function launchHeadlessAutomator(options) {
643
- return await (await import("./launch-yIGimB0F.mjs")).launch({ projectPath: options.projectPath });
945
+ return await (await import("./launch-Dqnd-Lqe.mjs")).launch({ projectPath: options.projectPath });
644
946
  }
645
947
  //#endregion
646
948
  //#region src/launcher/portLease.ts
@@ -738,8 +1040,64 @@ async function acquireAutomatorPortLease(preferredPort) {
738
1040
  return createLease(await getPort(0, "127.0.0.1"));
739
1041
  }
740
1042
  //#endregion
1043
+ //#region src/launcher/wechatCliFallback.ts
1044
+ const WECHAT_DEVTOOLS_SERVICE_PORT_RE = /listening\s+on\s+http:\/\/127\.0\.0\.1:(\d{2,5})/i;
1045
+ function extractWechatDevtoolsServicePort(output) {
1046
+ const port = Number(output.match(WECHAT_DEVTOOLS_SERVICE_PORT_RE)?.[1]);
1047
+ return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
1048
+ }
1049
+ async function enableAutomatorViaHttp(options) {
1050
+ const endpoint = new URL("/auto", `http://127.0.0.1:${options.servicePort}`);
1051
+ endpoint.searchParams.set("project", options.projectPath);
1052
+ endpoint.searchParams.set("port", String(options.autoPort));
1053
+ endpoint.searchParams.set("autoPort", String(options.autoPort));
1054
+ if (options.account) endpoint.searchParams.set("account", options.account);
1055
+ if (options.ticket) endpoint.searchParams.set("ticket", options.ticket);
1056
+ if (options.trustProject) endpoint.searchParams.set("trustProject", "true");
1057
+ const response = await fetch(endpoint, { redirect: "follow" });
1058
+ const body = await response.text();
1059
+ if (!response.ok) throw new Error(`WeChat DevTools HTTP automator fallback failed with status ${response.status}${body.trim() ? `: ${body.trim().slice(0, 400)}` : ""}`);
1060
+ let result;
1061
+ try {
1062
+ result = JSON.parse(body);
1063
+ } catch (error) {
1064
+ throw new Error("WeChat DevTools HTTP automator fallback returned invalid JSON", { cause: error });
1065
+ }
1066
+ if (result && typeof result === "object" && "autoPort" in result) {
1067
+ const autoPort = Number(result.autoPort);
1068
+ if (!Number.isInteger(autoPort) || autoPort <= 0 || autoPort > 65535) throw new Error(`WeChat DevTools HTTP automator fallback returned invalid autoPort: ${String(result.autoPort)}`);
1069
+ return autoPort;
1070
+ }
1071
+ if (typeof result === "string" && /^s\d+$/.test(result)) return options.autoPort;
1072
+ throw new Error(`WeChat DevTools HTTP automator fallback returned unsupported response: ${body.trim().slice(0, 400)}`);
1073
+ }
1074
+ const AUTOMATOR_VALUE_OPTIONS = /* @__PURE__ */ new Set([
1075
+ "--auto-account",
1076
+ "--auto-port",
1077
+ "--autoAccount",
1078
+ "--autoPort",
1079
+ "--project",
1080
+ "--test-ticket",
1081
+ "--ticket"
1082
+ ]);
1083
+ const AUTOMATOR_FLAG_OPTIONS = /* @__PURE__ */ new Set(["--trust-project"]);
1084
+ function resolveWechatDevtoolsBootstrapArgs(args) {
1085
+ const bootstrapArgs = [];
1086
+ for (let index = 0; index < args.length; index += 1) {
1087
+ const arg = args[index];
1088
+ if (AUTOMATOR_VALUE_OPTIONS.has(arg)) {
1089
+ index += 1;
1090
+ continue;
1091
+ }
1092
+ if (AUTOMATOR_FLAG_OPTIONS.has(arg) || arg === "auto") continue;
1093
+ bootstrapArgs.push(arg);
1094
+ }
1095
+ bootstrapArgs.push("islogin");
1096
+ return bootstrapArgs;
1097
+ }
1098
+ //#endregion
741
1099
  //#region package.json
742
- var version = "1.2.13";
1100
+ var version = "1.2.15";
743
1101
  //#endregion
744
1102
  //#region src/Native.ts
745
1103
  /** Native 的实现。 */
@@ -800,14 +1158,19 @@ function resolveFallbackTagName(selector) {
800
1158
  return "view";
801
1159
  }
802
1160
  /** 将 App-Service 查询到的渲染节点包装为 Element 兼容对象。 */
803
- function createRouteFallbackElement(connection, elementMap, pageId, selector, node, index) {
1161
+ function createRouteFallbackElement(connection, elementMap, pageId, selector, node, index, route, query, scopeSelectors = []) {
804
1162
  const nodeId = typeof node.id === "string" && node.id ? normalizeFallbackIdPart(node.id) : String(index);
805
1163
  const elementId = `__weapp_vite_route_fallback_${pageId}_${normalizeFallbackIdPart(selector)}_${nodeId}_${index}`;
806
1164
  return Element.create(connection, {
807
1165
  elementId,
808
1166
  pageId,
809
1167
  routeFallback: true,
810
- tagName: resolveFallbackTagName(selector)
1168
+ tagName: resolveFallbackTagName(selector),
1169
+ routeFallbackSelector: selector,
1170
+ routeFallbackIndex: index,
1171
+ routeFallbackRoute: route,
1172
+ routeFallbackQuery: query,
1173
+ routeFallbackScopeSelectors: [...scopeSelectors]
811
1174
  }, elementMap);
812
1175
  }
813
1176
  //#endregion
@@ -976,6 +1339,7 @@ const PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY = 300;
976
1339
  const PAGE_RENDERED_TIMEOUT = 15e3;
977
1340
  const PAGE_RENDERED_POLL_DELAY = 220;
978
1341
  const PAGE_RENDERED_QUERY_TIMEOUT = 5e3;
1342
+ const PAGE_RENDERED_EMPTY_RETRIES = 2;
979
1343
  const PAGE_ROOT_SELECTORS = [
980
1344
  "page",
981
1345
  "body",
@@ -1723,10 +2087,21 @@ var Page = class Page {
1723
2087
  return (await this.queryRouteElements(selector, options))[0] ?? null;
1724
2088
  }
1725
2089
  async queryRouteElements(selector, options) {
1726
- return (await this.renderedNodes(selector, {
1727
- componentSelectors: options.componentSelectors,
1728
- timeout: options.timeout ?? PAGE_QUERY_TIMEOUT
1729
- })).map((node, index) => createRouteFallbackElement(this.connection, this.elementMap, this.id, selector, node, index));
2090
+ const timeout = options.timeout ?? PAGE_QUERY_TIMEOUT;
2091
+ const startedAt = Date.now();
2092
+ let nodes = [];
2093
+ for (let attempt = 0; attempt <= PAGE_RENDERED_EMPTY_RETRIES; attempt += 1) {
2094
+ const requestTimeout = attempt === 0 ? timeout : Math.max(1, timeout - (Date.now() - startedAt));
2095
+ nodes = await this.renderedNodes(selector, {
2096
+ componentSelectors: options.componentSelectors,
2097
+ timeout: requestTimeout
2098
+ });
2099
+ if (nodes.length > 0 || attempt === PAGE_RENDERED_EMPTY_RETRIES) break;
2100
+ const remaining = timeout - (Date.now() - startedAt);
2101
+ if (remaining <= 0) break;
2102
+ await sleep$1(Math.min(PAGE_RENDERED_POLL_DELAY, remaining));
2103
+ }
2104
+ return nodes.map((node, index) => createRouteFallbackElement(this.connection, this.elementMap, this.id, selector, node, index, this.path, this.query, options.componentSelectors));
1730
2105
  }
1731
2106
  async callRouteMethod(method, args, timeout = PAGE_CALL_METHOD_TIMEOUT) {
1732
2107
  return await callRouteMethodViaAppService(this.connection, this.path, this.query, method, args, timeout);
@@ -2124,8 +2499,9 @@ var MiniProgram = class extends EventEmitter {
2124
2499
  disconnect() {
2125
2500
  this.connection.dispose();
2126
2501
  }
2127
- async enableLog() {
2128
- await this.send("App.enableLog");
2502
+ async enableLog(timeout) {
2503
+ if (timeout === void 0) await this.send("App.enableLog");
2504
+ else await this.send("App.enableLog", {}, { timeout });
2129
2505
  this.logEnabled = true;
2130
2506
  }
2131
2507
  on(event, listener) {
@@ -2138,8 +2514,8 @@ var MiniProgram = class extends EventEmitter {
2138
2514
  this.appBindings.set(name, bindingFunction);
2139
2515
  await this.send("App.addBinding", { name });
2140
2516
  }
2141
- async checkVersion() {
2142
- const toolInfo = await this.send("Tool.getInfo");
2517
+ async checkVersion(timeout) {
2518
+ const toolInfo = timeout === void 0 ? await this.send("Tool.getInfo") : await this.send("Tool.getInfo", {}, { timeout });
2143
2519
  this.connection.configureToolInfo(toolInfo);
2144
2520
  const sdkVersion = toolInfo.SDKVersion;
2145
2521
  if (typeof sdkVersion !== "string" || !sdkVersion.trim()) return;
@@ -2151,12 +2527,16 @@ var MiniProgram = class extends EventEmitter {
2151
2527
  async waitForAppReady(timeout = APP_READY_TIMEOUT) {
2152
2528
  const startedAt = Date.now();
2153
2529
  let lastError;
2154
- while (Date.now() - startedAt <= timeout) try {
2155
- await this.send("App.captureScreenshot", {}, { timeout: APP_READY_PROBE_TIMEOUT });
2156
- return;
2157
- } catch (error) {
2158
- lastError = error;
2159
- await sleep(APP_READY_POLL_DELAY);
2530
+ while (Date.now() - startedAt < timeout) {
2531
+ const remainingTimeout = timeout - (Date.now() - startedAt);
2532
+ try {
2533
+ await this.send("App.captureScreenshot", {}, { timeout: Math.min(APP_READY_PROBE_TIMEOUT, remainingTimeout) });
2534
+ return;
2535
+ } catch (error) {
2536
+ lastError = error;
2537
+ const remainingAfterProbe = timeout - (Date.now() - startedAt);
2538
+ if (remainingAfterProbe > 0) await sleep(Math.min(APP_READY_POLL_DELAY, remainingAfterProbe));
2539
+ }
2160
2540
  }
2161
2541
  throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`Timed out waiting App domain readiness after ${timeout}ms`);
2162
2542
  }
@@ -2805,6 +3185,7 @@ var SwanLauncher = class {
2805
3185
  * @file 开发者工具启动与连接流程。
2806
3186
  */
2807
3187
  const DEFAULT_TIMEOUT = 3e4;
3188
+ const VERSION_CHECK_TIMEOUT = 3e4;
2808
3189
  const AUTOMATOR_LAUNCH_RETRIES = 3;
2809
3190
  const DEFAULT_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_AUTOMATOR_RUNTIME_PROVIDER";
2810
3191
  const LEGACY_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_E2E_RUNTIME_PROVIDER";
@@ -2920,24 +3301,23 @@ var Launcher = class {
2920
3301
  let processError = null;
2921
3302
  let processExitCode = null;
2922
3303
  let processSignal = null;
2923
- args = [
2924
- ...args,
2925
- "auto",
2926
- "--project",
2927
- resolvedProjectPath,
2928
- "--auto-port",
2929
- toStr(port)
2930
- ];
2931
- if (account) args.push("--auto-account", account);
2932
- else if (ticket) args.push("--ticket", ticket);
2933
- if (trustProject) args.push("--trust-project");
3304
+ let successfulCliExitSettled = false;
3305
+ let httpFallbackAttempted = false;
3306
+ let httpFallbackError = null;
3307
+ let targetPort = port;
3308
+ const cliOutput = [];
3309
+ args = resolveWechatDevtoolsBootstrapArgs(args);
2934
3310
  try {
2935
3311
  const spawnTarget = shouldUseWindowsCommandShell(cliPath) ? resolveWindowsBatchSpawn(cliPath, args) : {
2936
3312
  file: cliPath,
2937
3313
  args
2938
3314
  };
2939
3315
  const child = spawn(spawnTarget.file, spawnTarget.args, {
2940
- stdio: "ignore",
3316
+ stdio: [
3317
+ "ignore",
3318
+ "pipe",
3319
+ "pipe"
3320
+ ],
2941
3321
  cwd: cwd || void 0,
2942
3322
  ...shouldUseWindowsCommandShell(cliPath) ? {
2943
3323
  windowsHide: true,
@@ -2947,10 +3327,17 @@ var Launcher = class {
2947
3327
  child.on("error", (error) => {
2948
3328
  processError = error;
2949
3329
  });
3330
+ child.stdout?.on("data", (chunk) => {
3331
+ cliOutput.push(String(chunk));
3332
+ });
3333
+ child.stderr?.on("data", (chunk) => {
3334
+ cliOutput.push(String(chunk));
3335
+ });
2950
3336
  child.on("exit", (code, signal) => {
2951
3337
  processExitCode = code;
2952
3338
  processSignal = signal;
2953
3339
  if (code !== 0 || signal) processError = /* @__PURE__ */ new Error(`DevTools cli exited unexpectedly with code ${code ?? "null"}${signal ? ` and signal ${signal}` : ""}`);
3340
+ else successfulCliExitSettled = true;
2954
3341
  });
2955
3342
  child.unref();
2956
3343
  } catch (error) {
@@ -2958,16 +3345,51 @@ var Launcher = class {
2958
3345
  }
2959
3346
  let miniProgram = null;
2960
3347
  let lastConnectError = null;
3348
+ const readinessStartedAt = Date.now();
3349
+ const resolveRemainingTimeout = () => Math.max(0, timeout - (Date.now() - readinessStartedAt));
2961
3350
  await waitUntil(async () => {
2962
3351
  try {
2963
3352
  if (processError) return true;
3353
+ if (successfulCliExitSettled && !httpFallbackAttempted) {
3354
+ const servicePort = extractWechatDevtoolsServicePort(cliOutput.join("\n"));
3355
+ if (servicePort) {
3356
+ httpFallbackAttempted = true;
3357
+ try {
3358
+ targetPort = await enableAutomatorViaHttp({
3359
+ account,
3360
+ autoPort: port,
3361
+ projectPath: resolvedProjectPath,
3362
+ servicePort,
3363
+ ticket,
3364
+ trustProject
3365
+ });
3366
+ } catch (error) {
3367
+ httpFallbackError = error;
3368
+ return true;
3369
+ }
3370
+ }
3371
+ }
3372
+ const connectTimeout = resolveRemainingTimeout();
3373
+ if (connectTimeout <= 0) return false;
2964
3374
  const candidate = await this.connectTool({
2965
- timeout: 3e3,
2966
- wsEndpoint: `ws://127.0.0.1:${port}`
3375
+ timeout: Math.min(3e3, connectTimeout),
3376
+ wsEndpoint: `ws://127.0.0.1:${targetPort}`
2967
3377
  });
2968
3378
  try {
2969
- await candidate.checkVersion();
2970
- if (typeof candidate.waitForAppReady === "function") await candidate.waitForAppReady(timeout);
3379
+ const checkVersionTimeout = resolveRemainingTimeout();
3380
+ if (checkVersionTimeout <= 0) {
3381
+ candidate.disconnect();
3382
+ return false;
3383
+ }
3384
+ await candidate.checkVersion(Math.min(VERSION_CHECK_TIMEOUT, checkVersionTimeout));
3385
+ if (typeof candidate.waitForAppReady === "function") {
3386
+ const appReadyTimeout = resolveRemainingTimeout();
3387
+ if (appReadyTimeout <= 0) {
3388
+ candidate.disconnect();
3389
+ return false;
3390
+ }
3391
+ await candidate.waitForAppReady(appReadyTimeout);
3392
+ }
2971
3393
  } catch (error) {
2972
3394
  candidate.disconnect();
2973
3395
  lastConnectError = error;
@@ -2982,6 +3404,7 @@ var Launcher = class {
2982
3404
  }
2983
3405
  }, timeout, 1e3);
2984
3406
  if (!miniProgram) {
3407
+ if (httpFallbackError) throw httpFallbackError;
2985
3408
  if (processError) throw new Error("Failed to launch wechat web devTools, please make sure cliPath is correctly specified");
2986
3409
  if (lastConnectError) throw lastConnectError;
2987
3410
  if (processExitCode !== null || processSignal) throw new Error("Failed to launch wechat web devTools, please make sure http port is open");
@@ -2989,9 +3412,9 @@ var Launcher = class {
2989
3412
  }
2990
3413
  const resolvedMiniProgram = miniProgram;
2991
3414
  Reflect.set(resolvedMiniProgram, "__WEAPP_VITE_SESSION_METADATA", {
2992
- port,
3415
+ port: targetPort,
2993
3416
  projectPath: resolvedProjectPath,
2994
- wsEndpoint: `ws://127.0.0.1:${port}`
3417
+ wsEndpoint: `ws://127.0.0.1:${targetPort}`
2995
3418
  });
2996
3419
  releasePortLeaseOnExit = !retainPortLeaseUntilSessionClose(resolvedMiniProgram, portLease);
2997
3420
  await sleep$1(5e3);
@@ -3003,7 +3426,7 @@ var Launcher = class {
3003
3426
  async connect(options) {
3004
3427
  if (normalizePlatform(options.platform) === "swan") return await new SwanLauncher().connect(options);
3005
3428
  const miniProgram = await this.connectTool(options);
3006
- await miniProgram.checkVersion();
3429
+ await miniProgram.checkVersion(options.timeout);
3007
3430
  return miniProgram;
3008
3431
  }
3009
3432
  async extendProjectConfig(projectConfig, projectPath) {