@weapp-vite/miniprogram-automator 1.2.12 → 1.2.14

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 CHANGED
@@ -107,6 +107,25 @@ await button?.tap()
107
107
  await page.waitFor(500)
108
108
  ```
109
109
 
110
+ ### 4.5 App-Service 降级(route fallback)能力边界
111
+
112
+ 部分 DevTools 版本(如 `2.01.2510290`)的 page-frame 定向协议失效,表现为 `Page.*` RPC 全部超时。此时本库自动切换到 App-Service 降级通道(`App.callFunction` + `wx.createSelectorQuery`),无需任何配置。
113
+
114
+ 降级模式下各能力可用性:
115
+
116
+ | 能力 | 状态 | 说明 |
117
+ | --------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------- |
118
+ | `page.$` / `page.$$` | ✅ | 经 route 查询返回降级元素 |
119
+ | `page.data()` / `page.setData()` / `page.callMethod()` | ✅ | 经 App-Service 读写页面数据、调用页面方法 |
120
+ | `element.offset()` / `element.size()` / `element.style()` | ✅ | 实时经 `createSelectorQuery` 读取 rect 与 computedStyle(每次读取都重新查询,不缓存) |
121
+ | `element.attribute('id')` / `element.attribute('data-*')` | ✅ | 经快照读取 id 与 dataset |
122
+ | `element.$()` / `element.$$()` | ❌ 明确报错 | 元素级查询依赖 page-frame 协议;请改用 `page.$()` / `page.$$()` |
123
+ | `element.text()` | ❌ 明确报错 | `SelectorQuery` 无法读取 `innerText`;请改用 `page.data()` 或 `page.callMethod()` 断言文案来源 |
124
+ | `element.tap()` / `trigger()` 等真实交互 | ❌ 明确报错 | AppService 无法合成真实触摸事件;请改用 `page.callMethod()` 或 `evaluate` 间接触发页面逻辑 |
125
+ | `scroll-view` 容器滚动 | ❌ | 需 page-frame 协议支持,降级模式不可用 |
126
+
127
+ > 降级元素上的不支持操作会**立即抛出带替代建议的错误**,不会再像旧版那样挂起 30s 才超时。
128
+
110
129
  ## 5. 主要导出
111
130
 
112
131
  | 导出 | 说明 |
package/dist/index.d.mts CHANGED
@@ -94,6 +94,12 @@ interface IElementOptions {
94
94
  videoId?: string;
95
95
  pageId: number;
96
96
  routeFallback?: boolean;
97
+ /** route 降级元素的定位信息:用于只读方法经 App-service 实时重查快照。 */
98
+ routeFallbackSelector?: string;
99
+ routeFallbackIndex?: number;
100
+ routeFallbackRoute?: string;
101
+ routeFallbackQuery?: Record<string, any>;
102
+ routeFallbackScopeSelectors?: string[];
97
103
  tagName: string;
98
104
  }
99
105
  /** ITouch 的类型定义。 */
@@ -123,7 +129,7 @@ interface ElementQueryOptions {
123
129
  }
124
130
  /** Element 的实现。 */
125
131
  declare class Element {
126
- private connection;
132
+ protected readonly connection: Connection;
127
133
  private elementMap;
128
134
  tagName: string;
129
135
  protected nodeId: string | null;
package/dist/index.mjs CHANGED
@@ -222,6 +222,223 @@ function toStr(value) {
222
222
  return value == null ? "" : String(value);
223
223
  }
224
224
  //#endregion
225
+ //#region src/pageRouteSnapshot.ts
226
+ const ROUTE_ELEMENT_SNAPSHOT_TIMEOUT = 5e3;
227
+ const ROUTE_ELEMENT_SNAPSHOT_EMPTY_RETRIES = 2;
228
+ const ROUTE_ELEMENT_SNAPSHOT_RETRY_DELAY = 220;
229
+ /**
230
+ * 页面定位 + 组件作用域遍历逻辑与 Page.renderedNodes 保持一致:
231
+ * 先按 route/query 找到目标页面,再逐层进入自定义组件作用域查询,
232
+ * 返回首个命中作用域里 selectAll(selector)[index] 的快照;未命中返回 null。
233
+ */
234
+ const ROUTE_ELEMENT_SNAPSHOT_FUNCTION_DECLARATION = `function (route, query, selector, index, scopeSelectors, styleNames) {
235
+ function normalizeRoute(value) {
236
+ return String(value || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
237
+ }
238
+ function matchesQuery(page, expectedQuery) {
239
+ if (!expectedQuery || !Object.keys(expectedQuery).length) {
240
+ return true;
241
+ }
242
+ var actualQuery = page && (page.options || page.query || {});
243
+ return Object.keys(expectedQuery).every(function (key) {
244
+ var actualValue = String(actualQuery[key] == null ? '' : actualQuery[key]);
245
+ var expectedValue = String(expectedQuery[key]);
246
+ var decodedActualValue = actualValue;
247
+ try {
248
+ decodedActualValue = decodeURIComponent(actualValue);
249
+ }
250
+ catch (_) {
251
+ }
252
+ return actualValue === expectedValue
253
+ || decodedActualValue === expectedValue
254
+ || actualValue === encodeURIComponent(expectedValue);
255
+ });
256
+ }
257
+ var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
258
+ var expectedRoute = normalizeRoute(route);
259
+ var page = null;
260
+ for (var pageIndex = pages.length - 1; pageIndex >= 0; pageIndex -= 1) {
261
+ var candidate = pages[pageIndex];
262
+ var candidateRoute = normalizeRoute(candidate && (candidate.path || candidate.route || candidate.__route__));
263
+ if ((!expectedRoute || candidateRoute === expectedRoute) && matchesQuery(candidate, query)) {
264
+ page = candidate;
265
+ break;
266
+ }
267
+ }
268
+ if (!page) {
269
+ return null;
270
+ }
271
+ function createSelectorQuery(scope) {
272
+ var selectorQuery = null;
273
+ var createdFromScope = false;
274
+ if (scope && typeof scope.createSelectorQuery === 'function') {
275
+ selectorQuery = scope.createSelectorQuery();
276
+ createdFromScope = true;
277
+ }
278
+ else if (typeof wx !== 'undefined' && wx && typeof wx.createSelectorQuery === 'function') {
279
+ selectorQuery = wx.createSelectorQuery();
280
+ }
281
+ else if (typeof page.createSelectorQuery === 'function') {
282
+ selectorQuery = page.createSelectorQuery();
283
+ }
284
+ if (!selectorQuery) {
285
+ return null;
286
+ }
287
+ if (!createdFromScope && typeof selectorQuery.in === 'function') {
288
+ try {
289
+ selectorQuery = selectorQuery.in(scope || page);
290
+ }
291
+ catch (_) {
292
+ }
293
+ }
294
+ return selectorQuery;
295
+ }
296
+ function pushUnique(list, seen, item) {
297
+ if (!item) {
298
+ return;
299
+ }
300
+ var id = item.is || item.id || item.__wxWebviewId__ || item.__wxExparserNodeId__ || String(list.length);
301
+ if (seen[id]) {
302
+ return;
303
+ }
304
+ seen[id] = true;
305
+ list.push(item);
306
+ }
307
+ function collectScopes(root, scopeSelectors) {
308
+ var scopes = [];
309
+ var seen = {};
310
+ var queue = [];
311
+ pushUnique(scopes, seen, root);
312
+ queue.push(root);
313
+ for (var queueIndex = 0; queueIndex < queue.length && queueIndex < 60; queueIndex += 1) {
314
+ var scope = queue[queueIndex];
315
+ if (!scope || typeof scope.selectAllComponents !== 'function') {
316
+ continue;
317
+ }
318
+ var children = [];
319
+ var componentSelectors = Array.isArray(scopeSelectors) ? scopeSelectors.slice() : [];
320
+ componentSelectors.push(selector, '*', 'weapp-app-shell', 'weapp-layout-default');
321
+ for (var selectorIndex = 0; selectorIndex < componentSelectors.length; selectorIndex += 1) {
322
+ if (!componentSelectors[selectorIndex]) {
323
+ continue;
324
+ }
325
+ try {
326
+ var selected = scope.selectAllComponents(componentSelectors[selectorIndex]);
327
+ if (Array.isArray(selected)) {
328
+ children = children.concat(selected);
329
+ }
330
+ }
331
+ catch (_) {
332
+ }
333
+ }
334
+ for (var childIndex = 0; childIndex < children.length; childIndex += 1) {
335
+ var child = children[childIndex];
336
+ var previousLength = scopes.length;
337
+ pushUnique(scopes, seen, child);
338
+ if (scopes.length > previousLength) {
339
+ queue.push(child);
340
+ }
341
+ }
342
+ }
343
+ return scopes;
344
+ }
345
+ function queryScope(scope) {
346
+ return new Promise(function (resolve) {
347
+ var settled = false;
348
+ var timer = setTimeout(function () {
349
+ if (settled) {
350
+ return;
351
+ }
352
+ settled = true;
353
+ resolve([]);
354
+ }, 800);
355
+ function finish(nodes) {
356
+ if (settled) {
357
+ return;
358
+ }
359
+ settled = true;
360
+ clearTimeout(timer);
361
+ resolve(Array.isArray(nodes) ? nodes : nodes ? [nodes] : []);
362
+ }
363
+ try {
364
+ var selectorQuery = createSelectorQuery(scope);
365
+ if (!selectorQuery) {
366
+ finish([]);
367
+ return;
368
+ }
369
+ selectorQuery
370
+ .selectAll(selector)
371
+ .fields({
372
+ id: true,
373
+ dataset: true,
374
+ rect: true,
375
+ size: true,
376
+ computedStyle: Array.isArray(styleNames) ? styleNames : []
377
+ }, function (nodes) {
378
+ finish(nodes);
379
+ })
380
+ .exec();
381
+ }
382
+ catch (_) {
383
+ finish([]);
384
+ }
385
+ });
386
+ }
387
+ return new Promise(function (resolve) {
388
+ try {
389
+ var scopes = collectScopes(page, scopeSelectors);
390
+ var scopeIndex = 0;
391
+ function next() {
392
+ if (scopeIndex >= scopes.length) {
393
+ resolve(null);
394
+ return;
395
+ }
396
+ var scope = scopes[scopeIndex];
397
+ scopeIndex += 1;
398
+ queryScope(scope).then(function (nodes) {
399
+ if (nodes.length > 0) {
400
+ resolve(nodes[index] || null);
401
+ return;
402
+ }
403
+ next();
404
+ }, function () {
405
+ next();
406
+ });
407
+ }
408
+ next();
409
+ }
410
+ catch (_) {
411
+ resolve(null);
412
+ }
413
+ });
414
+ }`;
415
+ /**
416
+ * 读取降级元素快照。未找到(页面不在/选择器无命中/索引越界)返回 null,
417
+ * 由调用方决定抛错文案;永不因为协议失效而长时间挂起。
418
+ */
419
+ async function readRouteElementSnapshot(connection, route, query, selector, index, scopeSelectors = [], styleNames = [], timeout = ROUTE_ELEMENT_SNAPSHOT_TIMEOUT) {
420
+ const startedAt = Date.now();
421
+ for (let attempt = 0; attempt <= ROUTE_ELEMENT_SNAPSHOT_EMPTY_RETRIES; attempt += 1) {
422
+ const remaining = attempt === 0 ? timeout : Math.max(1, timeout - (Date.now() - startedAt));
423
+ const { result } = await connection.send("App.callFunction", {
424
+ functionDeclaration: ROUTE_ELEMENT_SNAPSHOT_FUNCTION_DECLARATION,
425
+ args: [
426
+ route,
427
+ query,
428
+ selector,
429
+ index,
430
+ scopeSelectors,
431
+ styleNames
432
+ ]
433
+ }, { timeout: remaining });
434
+ if (result != null || attempt === ROUTE_ELEMENT_SNAPSHOT_EMPTY_RETRIES) return result ?? null;
435
+ const retryRemaining = timeout - (Date.now() - startedAt);
436
+ if (retryRemaining <= 0) break;
437
+ await sleep$1(Math.min(ROUTE_ELEMENT_SNAPSHOT_RETRY_DELAY, retryRemaining));
438
+ }
439
+ return null;
440
+ }
441
+ //#endregion
225
442
  //#region src/Element.ts
226
443
  const ELEMENT_QUERY_TIMEOUT = 2500;
227
444
  const ELEMENT_WXML_TIMEOUT = 8e3;
@@ -272,7 +489,17 @@ var Element = class Element {
272
489
  };
273
490
  }
274
491
  async offset() {
275
- return await this.send("Element.getOffset");
492
+ const offset = await this.send("Element.getOffset");
493
+ if (offset?.width !== void 0 && offset.height !== void 0) return offset;
494
+ try {
495
+ const dimensions = await this.size();
496
+ return {
497
+ ...offset,
498
+ ...dimensions
499
+ };
500
+ } catch {
501
+ return offset;
502
+ }
276
503
  }
277
504
  async text() {
278
505
  return await this.domProperty("innerText");
@@ -394,8 +621,87 @@ var Element = class Element {
394
621
  };
395
622
  /** App-Service 路由降级元素仅用于只读查询,不能伪装成交互协议元素。 */
396
623
  var RouteFallbackElement = class extends Element {
624
+ routeSelector;
625
+ routeIndex;
626
+ routeRoute;
627
+ routeQuery;
628
+ routeScopeSelectors;
629
+ constructor(connection, options, elementMap) {
630
+ super(connection, options, elementMap);
631
+ this.routeSelector = options.routeFallbackSelector;
632
+ this.routeIndex = options.routeFallbackIndex;
633
+ this.routeRoute = options.routeFallbackRoute;
634
+ this.routeQuery = options.routeFallbackQuery;
635
+ this.routeScopeSelectors = options.routeFallbackScopeSelectors ?? [];
636
+ }
637
+ /**
638
+ * 经 App-service SelectorQuery 实时重查元素快照(rect/size/dataset/computedStyle)。
639
+ * 每次读取都重新查询,保证滚动/重渲染后拿到的是当前值;未命中返回 null。
640
+ */
641
+ async readSnapshot(styleNames = []) {
642
+ if (!this.routeSelector || this.routeIndex === void 0 || !this.routeRoute) return null;
643
+ return await readRouteElementSnapshot(this.connection, this.routeRoute, this.routeQuery ?? {}, this.routeSelector, this.routeIndex, this.routeScopeSelectors, styleNames);
644
+ }
645
+ missingSnapshot(what) {
646
+ return /* @__PURE__ */ new Error(`route fallback 元素未取到${what}: ${this.routeSelector ?? "(未知选择器)"}(页面已切换或元素已卸载)`);
647
+ }
648
+ async $(selector, _options = {}) {
649
+ return this.unsupported(`Element.$(${selector})`);
650
+ }
651
+ async $$(selector, _options = {}) {
652
+ return this.unsupported(`Element.$$(${selector})`);
653
+ }
654
+ async offset() {
655
+ const snapshot = await this.readSnapshot();
656
+ if (snapshot?.left === void 0 || snapshot.top === void 0) throw this.missingSnapshot("坐标");
657
+ return {
658
+ left: snapshot.left,
659
+ top: snapshot.top,
660
+ width: snapshot.width,
661
+ height: snapshot.height
662
+ };
663
+ }
664
+ async size() {
665
+ const snapshot = await this.readSnapshot();
666
+ if (snapshot?.width === void 0 || snapshot.height === void 0) throw this.missingSnapshot("尺寸");
667
+ return {
668
+ width: snapshot.width,
669
+ height: snapshot.height
670
+ };
671
+ }
672
+ async style(name) {
673
+ if (!isStr(name)) throw new Error("name must be a string");
674
+ const value = (await this.readSnapshot([name]))?.[name];
675
+ if (value === void 0) throw this.missingSnapshot(`样式 ${name}`);
676
+ return value;
677
+ }
678
+ async attribute(name) {
679
+ if (!isStr(name)) throw new Error("name must be a string");
680
+ const snapshot = await this.readSnapshot();
681
+ if (name === "id") return snapshot?.id;
682
+ if (name.startsWith("data-")) {
683
+ const key = name.slice(5).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
684
+ return snapshot?.dataset?.[key];
685
+ }
686
+ throw new Error(`route fallback 元素仅支持读取 id 与 data-* 属性,不支持 attribute(${name})`);
687
+ }
397
688
  unsupported(method) {
398
- throw new Error(`App-Service route fallback 元素不支持 ${method}`);
689
+ throw new Error(`App-Service route fallback 元素不支持 ${method}(真实交互依赖已失效的 page-frame 协议);可改用 page.callMethod()/evaluate 间接触发页面逻辑`);
690
+ }
691
+ async text() {
692
+ throw new Error("App-Service route fallback 元素不支持 text(SelectorQuery 无法读取 innerText);请改用 page.data() 断言数据源,或 page.callMethod()/evaluate 读取");
693
+ }
694
+ async value() {
695
+ this.unsupported("Element.value");
696
+ }
697
+ async property(name) {
698
+ this.unsupported(`Element.property(${name})`);
699
+ }
700
+ async wxml() {
701
+ this.unsupported("Element.wxml");
702
+ }
703
+ async outerWxml() {
704
+ this.unsupported("Element.outerWxml");
399
705
  }
400
706
  async tap() {
401
707
  this.unsupported("Element.tap");
@@ -640,7 +946,7 @@ var Connection = class Connection extends EventEmitter {
640
946
  //#endregion
641
947
  //#region src/headless.ts
642
948
  async function launchHeadlessAutomator(options) {
643
- return await (await import("./launch-BFWwqoyn.mjs")).launch({ projectPath: options.projectPath });
949
+ return await (await import("./launch-DKcPk1N2.mjs")).launch({ projectPath: options.projectPath });
644
950
  }
645
951
  //#endregion
646
952
  //#region src/launcher/portLease.ts
@@ -739,7 +1045,7 @@ async function acquireAutomatorPortLease(preferredPort) {
739
1045
  }
740
1046
  //#endregion
741
1047
  //#region package.json
742
- var version = "1.2.12";
1048
+ var version = "1.2.14";
743
1049
  //#endregion
744
1050
  //#region src/Native.ts
745
1051
  /** Native 的实现。 */
@@ -800,14 +1106,19 @@ function resolveFallbackTagName(selector) {
800
1106
  return "view";
801
1107
  }
802
1108
  /** 将 App-Service 查询到的渲染节点包装为 Element 兼容对象。 */
803
- function createRouteFallbackElement(connection, elementMap, pageId, selector, node, index) {
1109
+ function createRouteFallbackElement(connection, elementMap, pageId, selector, node, index, route, query, scopeSelectors = []) {
804
1110
  const nodeId = typeof node.id === "string" && node.id ? normalizeFallbackIdPart(node.id) : String(index);
805
1111
  const elementId = `__weapp_vite_route_fallback_${pageId}_${normalizeFallbackIdPart(selector)}_${nodeId}_${index}`;
806
1112
  return Element.create(connection, {
807
1113
  elementId,
808
1114
  pageId,
809
1115
  routeFallback: true,
810
- tagName: resolveFallbackTagName(selector)
1116
+ tagName: resolveFallbackTagName(selector),
1117
+ routeFallbackSelector: selector,
1118
+ routeFallbackIndex: index,
1119
+ routeFallbackRoute: route,
1120
+ routeFallbackQuery: query,
1121
+ routeFallbackScopeSelectors: [...scopeSelectors]
811
1122
  }, elementMap);
812
1123
  }
813
1124
  //#endregion
@@ -976,6 +1287,7 @@ const PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY = 300;
976
1287
  const PAGE_RENDERED_TIMEOUT = 15e3;
977
1288
  const PAGE_RENDERED_POLL_DELAY = 220;
978
1289
  const PAGE_RENDERED_QUERY_TIMEOUT = 5e3;
1290
+ const PAGE_RENDERED_EMPTY_RETRIES = 2;
979
1291
  const PAGE_ROOT_SELECTORS = [
980
1292
  "page",
981
1293
  "body",
@@ -1723,10 +2035,21 @@ var Page = class Page {
1723
2035
  return (await this.queryRouteElements(selector, options))[0] ?? null;
1724
2036
  }
1725
2037
  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));
2038
+ const timeout = options.timeout ?? PAGE_QUERY_TIMEOUT;
2039
+ const startedAt = Date.now();
2040
+ let nodes = [];
2041
+ for (let attempt = 0; attempt <= PAGE_RENDERED_EMPTY_RETRIES; attempt += 1) {
2042
+ const requestTimeout = attempt === 0 ? timeout : Math.max(1, timeout - (Date.now() - startedAt));
2043
+ nodes = await this.renderedNodes(selector, {
2044
+ componentSelectors: options.componentSelectors,
2045
+ timeout: requestTimeout
2046
+ });
2047
+ if (nodes.length > 0 || attempt === PAGE_RENDERED_EMPTY_RETRIES) break;
2048
+ const remaining = timeout - (Date.now() - startedAt);
2049
+ if (remaining <= 0) break;
2050
+ await sleep$1(Math.min(PAGE_RENDERED_POLL_DELAY, remaining));
2051
+ }
2052
+ return nodes.map((node, index) => createRouteFallbackElement(this.connection, this.elementMap, this.id, selector, node, index, this.path, this.query, options.componentSelectors));
1730
2053
  }
1731
2054
  async callRouteMethod(method, args, timeout = PAGE_CALL_METHOD_TIMEOUT) {
1732
2055
  return await callRouteMethodViaAppService(this.connection, this.path, this.query, method, args, timeout);
@@ -2142,6 +2465,7 @@ var MiniProgram = class extends EventEmitter {
2142
2465
  const toolInfo = await this.send("Tool.getInfo");
2143
2466
  this.connection.configureToolInfo(toolInfo);
2144
2467
  const sdkVersion = toolInfo.SDKVersion;
2468
+ if (typeof sdkVersion !== "string" || !sdkVersion.trim()) return;
2145
2469
  if (sdkVersion !== "dev" && cmpVersion(sdkVersion, "2.7.3") < 0) throw new Error(`SDKVersion is currently ${sdkVersion}, while automator(${version}) requires at least version 2.7.3`);
2146
2470
  }
2147
2471
  /**