@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/README.md +19 -0
- package/dist/index.d.mts +9 -3
- package/dist/index.mjs +465 -42
- package/dist/{launch-yIGimB0F.mjs → launch-Dqnd-Lqe.mjs} +350 -42
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -217,9 +217,222 @@ function cmpVersion(versionA, versionB) {
|
|
|
217
217
|
}
|
|
218
218
|
return 0;
|
|
219
219
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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-
|
|
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.
|
|
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
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
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
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
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:
|
|
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:${
|
|
3375
|
+
timeout: Math.min(3e3, connectTimeout),
|
|
3376
|
+
wsEndpoint: `ws://127.0.0.1:${targetPort}`
|
|
2967
3377
|
});
|
|
2968
3378
|
try {
|
|
2969
|
-
|
|
2970
|
-
if (
|
|
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:${
|
|
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) {
|