@weapp-vite/miniprogram-automator 1.2.15 → 1.2.17

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
@@ -126,6 +126,21 @@ await page.waitFor(500)
126
126
 
127
127
  > 降级元素上的不支持操作会**立即抛出带替代建议的错误**,不会再像旧版那样挂起 30s 才超时。
128
128
 
129
+ ### 4.6 结构化错误日志
130
+
131
+ `enableLog(timeout, { structured: true })` 先通过 `App.CDPEnable` 注册事件通道,再启用 Runtime,保留 CDP 原始日志参数,并读取 Error 的非枚举 `name`、`message`、`stack` 数据属性,避免 SDK 将错误序列化为 `{}`。普通对象仍是原始 RemoteObject;不会执行 getter 或远程函数。默认不传选项时继续使用 SDK 日志格式。
132
+
133
+ ```ts
134
+ miniProgram.addListener('console', (entry) => {
135
+ console.log(entry)
136
+ })
137
+ await miniProgram.enableLog(3_000, { structured: true })
138
+ await miniProgram.reLaunch('/pages/index/index')
139
+ await miniProgram.flushConsole()
140
+ ```
141
+
142
+ 先用 `addListener` 被动注册,再显式等待 `enableLog`,可以记录初始化期间的日志。`flushConsole()` 等待调用时已收到的错误属性检查完成;每次查询最多等待 1 秒,失败会在原参数上保留 `inspectionError`。断开连接时尚未完成的日志也会携带该诊断发布。只有宿主明确返回 CDP 命令不支持时才回退 SDK 格式;其他初始化错误仍然抛出。重复调用 `enableLog` 会刷新 Runtime 订阅,并保留已选择的格式。
143
+
129
144
  ## 5. 主要导出
130
145
 
131
146
  | 导出 | 说明 |
package/dist/index.d.mts CHANGED
@@ -63,6 +63,28 @@ declare class Connection extends EventEmitter {
63
63
  static create(url: string, timeout?: number): Promise<Connection>;
64
64
  }
65
65
  //#endregion
66
+ //#region src/structuredConsole.d.ts
67
+ interface ConsoleLogOptions {
68
+ /** 使用 CDP 原始参数,并读取 Error 的非枚举数据属性;默认保留 SDK 日志格式。 */
69
+ structured?: boolean;
70
+ }
71
+ interface ConsoleRemoteObject {
72
+ type?: string;
73
+ subtype?: string;
74
+ className?: string;
75
+ objectId?: string;
76
+ description?: string;
77
+ value?: unknown;
78
+ /** 属性查询失败时保留原参数和诊断,不把不可检查的错误当作成功。 */
79
+ inspectionError?: string;
80
+ [key: string]: unknown;
81
+ }
82
+ interface StructuredConsoleEntry {
83
+ type: string;
84
+ args: ConsoleRemoteObject[];
85
+ [key: string]: unknown;
86
+ }
87
+ //#endregion
66
88
  //#region src/Native.d.ts
67
89
  /** ISwitchTabOptions 的类型定义。 */
68
90
  interface ISwitchTabOptions {
@@ -169,45 +191,45 @@ declare class Element {
169
191
  static create(connection: Connection, options: IElementOptions, elementMap: ElementMap): Element;
170
192
  }
171
193
  /** CustomElement 的实现。 */
172
- declare class CustomElement extends Element {
194
+ export declare class CustomElement extends Element {
173
195
  setData(data: any): Promise<void>;
174
196
  data(path?: string): Promise<any>;
175
197
  callMethod(method: string, ...args: any[]): Promise<any>;
176
198
  }
177
199
  /** InputElement 的实现。 */
178
- declare class InputElement extends Element {
200
+ export declare class InputElement extends Element {
179
201
  input(value: string): Promise<void>;
180
202
  }
181
203
  /** TextareaElement 的实现。 */
182
- declare class TextareaElement extends Element {
204
+ export declare class TextareaElement extends Element {
183
205
  input(value: string): Promise<void>;
184
206
  }
185
207
  /** ScrollViewElement 的实现。 */
186
- declare class ScrollViewElement extends Element {
208
+ export declare class ScrollViewElement extends Element {
187
209
  scrollTo(x: number, y: number): Promise<void>;
188
210
  property(name: string): Promise<any>;
189
211
  scrollWidth(): Promise<any>;
190
212
  scrollHeight(): Promise<any>;
191
213
  }
192
214
  /** SwiperElement 的实现。 */
193
- declare class SwiperElement extends Element {
215
+ export declare class SwiperElement extends Element {
194
216
  swipeTo(index: number): Promise<void>;
195
217
  }
196
218
  /** MovableViewElement 的实现。 */
197
- declare class MovableViewElement extends Element {
219
+ export declare class MovableViewElement extends Element {
198
220
  moveTo(x: number, y: number): Promise<void>;
199
221
  property(name: string): Promise<any>;
200
222
  }
201
223
  /** SwitchElement 的实现。 */
202
- declare class SwitchElement extends Element {
224
+ export declare class SwitchElement extends Element {
203
225
  tap(): Promise<void>;
204
226
  }
205
227
  /** SliderElement 的实现。 */
206
- declare class SliderElement extends Element {
228
+ export declare class SliderElement extends Element {
207
229
  slideTo(value: number): Promise<void>;
208
230
  }
209
231
  /** ContextElement 的实现。 */
210
- declare class ContextElement extends Element {
232
+ export declare class ContextElement extends Element {
211
233
  callContextMethod(method: string, ...args: any[]): Promise<any>;
212
234
  }
213
235
  //#endregion
@@ -215,6 +237,7 @@ declare class ContextElement extends Element {
215
237
  /** IPageOptions 的类型定义。 */
216
238
  interface IPageOptions {
217
239
  id: number;
240
+ hasStableIdentity?: boolean;
218
241
  path: string;
219
242
  query: any;
220
243
  }
@@ -261,15 +284,18 @@ declare class Page {
261
284
  path: string;
262
285
  query: any;
263
286
  private id;
287
+ private hasStableIdentity;
264
288
  private elementMap;
265
289
  private preferAppServicePageProtocol;
266
290
  constructor(connection: Connection, options: IPageOptions);
291
+ /** 当前页面的协议帧身份,同路由新页面具有独立身份。 */
292
+ get pageId(): number;
267
293
  updateFromOptions(options: IPageOptions): void;
268
294
  waitFor(condition: WaitCondition): Promise<void>;
269
295
  $(selector: string, options?: PageQueryOptions): Promise<Element | null>;
270
296
  $$(selector: string, options?: PageQueryOptions): Promise<any>;
271
297
  getElementByXpath(selector: string, options?: PageQueryOptions): Promise<Element | null>;
272
- getElementsByXpath(selector: string, options?: PageQueryOptions): Promise<any>;
298
+ getElementsByXpath(selector: string, options?: PageQueryOptions): Promise<Element[]>;
273
299
  xpath(selector: string): Promise<Element | null>;
274
300
  wxml(): Promise<any>;
275
301
  renderedNodes(selector: string, options?: PageQueryOptions): Promise<RenderedNodeSnapshot[]>;
@@ -313,6 +339,7 @@ interface IToolClearCacheOptions {
313
339
  type AutomatorCallable = (...args: any[]) => any;
314
340
  interface CurrentPageOptions {
315
341
  appFunctionFallback?: boolean;
342
+ pageStackFallback?: boolean;
316
343
  retries?: number;
317
344
  timeout?: number;
318
345
  }
@@ -324,6 +351,9 @@ declare class MiniProgram extends EventEmitter {
324
351
  private connection;
325
352
  private appBindings;
326
353
  private logEnabled;
354
+ private logEnabling?;
355
+ private structuredLogs;
356
+ private structuredConsole;
327
357
  private pageMap;
328
358
  private nativeIns?;
329
359
  constructor(connection: Connection);
@@ -351,7 +381,9 @@ declare class MiniProgram extends EventEmitter {
351
381
  close(): Promise<void>;
352
382
  remote(auto?: boolean): Promise<void>;
353
383
  disconnect(): void;
354
- enableLog(timeout?: number): Promise<void>;
384
+ /** 等待已接收的结构化日志完成 Error 属性检查并发布。 */
385
+ flushConsole(): Promise<void>;
386
+ enableLog(timeout?: number, options?: ConsoleLogOptions): Promise<void>;
355
387
  on(event: string | symbol, listener: (...args: any[]) => void): this;
356
388
  exposeFunction(name: string, bindingFunction: AutomatorCallable): Promise<void>;
357
389
  checkVersion(timeout?: number): Promise<void>;
@@ -553,8 +585,8 @@ declare function devices(deviceType: string, _connectType?: string): Promise<Sma
553
585
  /**
554
586
  * @file 小程序自动化平台适配定义。
555
587
  */
556
- type MiniprogramAutomatorPlatform = 'wechat' | 'swan' | 'baidu';
557
- interface IExternalAutomatorModule {
588
+ export type MiniprogramAutomatorPlatform = 'wechat' | 'swan' | 'baidu';
589
+ export interface IExternalAutomatorModule {
558
590
  launch: (options: Record<string, unknown>) => Promise<unknown>;
559
591
  connect?: (options: Record<string, unknown>) => Promise<unknown>;
560
592
  devices?: (deviceType: string, connectType?: string) => Promise<unknown>;
@@ -563,17 +595,17 @@ interface IExternalAutomatorModule {
563
595
  log?: unknown;
564
596
  time?: unknown;
565
597
  }
566
- type NormalizedMiniprogramAutomatorPlatform = Exclude<MiniprogramAutomatorPlatform, 'baidu'>;
567
- declare function normalizePlatform(platform?: MiniprogramAutomatorPlatform): NormalizedMiniprogramAutomatorPlatform;
598
+ export type NormalizedMiniprogramAutomatorPlatform = Exclude<MiniprogramAutomatorPlatform, 'baidu'>;
599
+ export declare function normalizePlatform(platform?: MiniprogramAutomatorPlatform): NormalizedMiniprogramAutomatorPlatform;
568
600
  //#endregion
569
601
  //#region src/Launcher.d.ts
570
- interface IConnectOptions {
602
+ export interface IConnectOptions {
571
603
  wsEndpoint: string;
572
604
  timeout?: number;
573
605
  platform?: MiniprogramAutomatorPlatform;
574
606
  }
575
607
  /** ILaunchOptions 的类型定义。 */
576
- interface ILaunchOptions {
608
+ export interface ILaunchOptions {
577
609
  platform?: MiniprogramAutomatorPlatform;
578
610
  cliPath?: string;
579
611
  connectType?: string;
@@ -601,7 +633,7 @@ interface ILaunchOptions {
601
633
  wdaProjPath?: string;
602
634
  webModel?: string;
603
635
  }
604
- interface ILauncherSessionMetadata {
636
+ export interface ILauncherSessionMetadata {
605
637
  port: number;
606
638
  projectPath: string;
607
639
  wsEndpoint: string;
@@ -635,16 +667,16 @@ declare class Automator {
635
667
  //#endregion
636
668
  //#region src/util.d.ts
637
669
  /** decodeQrCode 的方法封装。 */
638
- declare function decodeQrCode(qrCode: string): Promise<string>;
670
+ export declare function decodeQrCode(qrCode: string): Promise<string>;
639
671
  /** printQrCode 的方法封装。 */
640
- declare function printQrCode(content: string): Promise<void>;
672
+ export declare function printQrCode(content: string): Promise<void>;
641
673
  /** isPluginPath 的方法封装。 */
642
- declare function isPluginPath(p: unknown): boolean;
674
+ export declare function isPluginPath(p: unknown): boolean;
643
675
  /** extractPluginId 的方法封装。 */
644
- declare function extractPluginId(p: unknown): string;
676
+ export declare function extractPluginId(p: unknown): string;
645
677
  //#endregion
646
678
  //#region src/index.d.ts
647
- declare const SmartappAutomator: {
679
+ export declare const SmartappAutomator: {
648
680
  connect: typeof connect;
649
681
  devices: typeof devices;
650
682
  file: {
@@ -669,4 +701,4 @@ declare const SmartappAutomator: {
669
701
  };
670
702
  };
671
703
  //#endregion
672
- export { Automator, Connection, ContextElement, CustomElement, Element, IConnectOptions, IExternalAutomatorModule, ILaunchOptions, ILauncherSessionMetadata, InputElement, Launcher, MiniProgram, MiniprogramAutomatorPlatform, MovableViewElement, Native, NormalizedMiniprogramAutomatorPlatform, Page, ScrollViewElement, SliderElement, SmartappAutomator, SwiperElement, SwitchElement, TextareaElement, Transport, decodeQrCode, extractPluginId, isPluginPath, normalizePlatform, printQrCode };
704
+ export { Automator, Connection, type ConsoleLogOptions, type ConsoleRemoteObject, Element, Launcher, MiniProgram, Native, Page, type StructuredConsoleEntry, Transport };
package/dist/index.mjs CHANGED
@@ -364,13 +364,14 @@ const ROUTE_ELEMENT_SNAPSHOT_FUNCTION_DECLARATION = `function (route, query, sel
364
364
  }
365
365
  selectorQuery
366
366
  .selectAll(selector)
367
- .fields({
367
+ // 宿主 JSON.parse 创建可跨 DevTools evaluate 上下文传输的查询选项。
368
+ .fields(JSON.parse(JSON.stringify({
368
369
  id: true,
369
370
  dataset: true,
370
371
  rect: true,
371
372
  size: true,
372
373
  computedStyle: Array.isArray(styleNames) ? styleNames : []
373
- }, function (nodes) {
374
+ })), function (nodes) {
374
375
  finish(nodes);
375
376
  })
376
377
  .exec();
@@ -942,7 +943,7 @@ var Connection = class Connection extends EventEmitter {
942
943
  //#endregion
943
944
  //#region src/headless.ts
944
945
  async function launchHeadlessAutomator(options) {
945
- return await (await import("./launch-Dqnd-Lqe.mjs")).launch({ projectPath: options.projectPath });
946
+ return await (await import("./launch-BM8VnZ6V.mjs")).launch({ projectPath: options.projectPath });
946
947
  }
947
948
  //#endregion
948
949
  //#region src/launcher/portLease.ts
@@ -1056,20 +1057,20 @@ async function enableAutomatorViaHttp(options) {
1056
1057
  if (options.trustProject) endpoint.searchParams.set("trustProject", "true");
1057
1058
  const response = await fetch(endpoint, { redirect: "follow" });
1058
1059
  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
+ if (!response.ok) throw new Error(`WeChat DevTools HTTP automator fallback failed with status ${response.status}`);
1060
1061
  let result;
1061
1062
  try {
1062
1063
  result = JSON.parse(body);
1063
- } catch (error) {
1064
- throw new Error("WeChat DevTools HTTP automator fallback returned invalid JSON", { cause: error });
1064
+ } catch {
1065
+ throw new Error("WeChat DevTools HTTP automator fallback returned invalid JSON");
1065
1066
  }
1066
1067
  if (result && typeof result === "object" && "autoPort" in result) {
1067
1068
  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
+ if (!Number.isInteger(autoPort) || autoPort <= 0 || autoPort > 65535) throw new Error("WeChat DevTools HTTP automator fallback returned invalid autoPort");
1069
1070
  return autoPort;
1070
1071
  }
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)}`);
1072
+ if (typeof result === "string" && (/^s\d+$/.test(result) || /^[0-9a-f]{32}$/i.test(result))) return options.autoPort;
1073
+ throw new Error("WeChat DevTools HTTP automator fallback returned unsupported response");
1073
1074
  }
1074
1075
  const AUTOMATOR_VALUE_OPTIONS = /* @__PURE__ */ new Set([
1075
1076
  "--auto-account",
@@ -1097,7 +1098,7 @@ function resolveWechatDevtoolsBootstrapArgs(args) {
1097
1098
  }
1098
1099
  //#endregion
1099
1100
  //#region package.json
1100
- var version = "1.2.15";
1101
+ var version = "1.2.17";
1101
1102
  //#endregion
1102
1103
  //#region src/Native.ts
1103
1104
  /** Native 的实现。 */
@@ -1384,16 +1385,24 @@ var Page = class Page {
1384
1385
  path = "";
1385
1386
  query = {};
1386
1387
  id;
1388
+ hasStableIdentity;
1387
1389
  elementMap = /* @__PURE__ */ new Map();
1388
1390
  preferAppServicePageProtocol = false;
1389
1391
  constructor(connection, options) {
1390
1392
  this.connection = connection;
1391
1393
  this.id = options.id;
1394
+ this.hasStableIdentity = options.hasStableIdentity !== false;
1392
1395
  this.path = options.path;
1393
1396
  this.query = options.query;
1394
1397
  this.preferAppServicePageProtocol = connection.prefersAppServicePageProtocol;
1395
1398
  }
1399
+ /** 当前页面的协议帧身份,同路由新页面具有独立身份。 */
1400
+ get pageId() {
1401
+ if (!this.hasStableIdentity) throw new Error("DevTools did not provide a stable page frame identity");
1402
+ return this.id;
1403
+ }
1396
1404
  updateFromOptions(options) {
1405
+ this.hasStableIdentity = options.hasStableIdentity !== false;
1397
1406
  this.path = options.path;
1398
1407
  this.query = options.query;
1399
1408
  }
@@ -1443,6 +1452,7 @@ var Page = class Page {
1443
1452
  async getElementByXpath(selector, options = {}) {
1444
1453
  try {
1445
1454
  const element = await this.send("Page.getElementByXpath", { selector }, { timeout: options.timeout ?? PAGE_QUERY_TIMEOUT });
1455
+ if (element === null) return null;
1446
1456
  return Element.create(this.connection, {
1447
1457
  ...element,
1448
1458
  pageId: this.id
@@ -1452,7 +1462,9 @@ var Page = class Page {
1452
1462
  }
1453
1463
  }
1454
1464
  async getElementsByXpath(selector, options = {}) {
1455
- const { elements } = await this.send("Page.getElementsByXpath", { selector }, { timeout: options.timeout ?? PAGE_QUERY_TIMEOUT });
1465
+ const response = await this.send("Page.getElementsByXpath", { selector }, { timeout: options.timeout ?? PAGE_QUERY_TIMEOUT });
1466
+ const elements = Array.isArray(response) ? response : response?.elements;
1467
+ if (!Array.isArray(elements)) throw new TypeError("DevTools Page.getElementsByXpath 返回了无效响应:缺少 elements 数组。");
1456
1468
  return elements.map((element) => {
1457
1469
  return Element.create(this.connection, {
1458
1470
  ...element,
@@ -1613,12 +1625,13 @@ var Page = class Page {
1613
1625
  }
1614
1626
  query
1615
1627
  .selectAll(selector)
1616
- .fields({
1628
+ // 宿主 JSON.parse 创建可跨 DevTools evaluate 上下文传输的查询选项。
1629
+ .fields(JSON.parse(JSON.stringify({
1617
1630
  dataset: true,
1618
1631
  id: true,
1619
1632
  rect: true,
1620
1633
  size: true
1621
- }, function (nodes) {
1634
+ })), function (nodes) {
1622
1635
  finish(nodes);
1623
1636
  })
1624
1637
  .exec();
@@ -1797,12 +1810,13 @@ var Page = class Page {
1797
1810
  }
1798
1811
  query
1799
1812
  .selectAll(selector)
1800
- .fields({
1813
+ // 宿主 JSON.parse 创建可跨 DevTools evaluate 上下文传输的查询选项。
1814
+ .fields(JSON.parse(JSON.stringify({
1801
1815
  dataset: true,
1802
1816
  id: true,
1803
1817
  rect: true,
1804
1818
  size: true
1805
- }, function (nodes) {
1819
+ })), function (nodes) {
1806
1820
  finish(nodes);
1807
1821
  })
1808
1822
  .exec();
@@ -2181,6 +2195,100 @@ var Page = class Page {
2181
2195
  }
2182
2196
  };
2183
2197
  //#endregion
2198
+ //#region src/structuredConsole.ts
2199
+ function isRecord(value) {
2200
+ return value !== null && typeof value === "object";
2201
+ }
2202
+ function isRemoteError(arg) {
2203
+ return isRecord(arg) && arg.type === "object" && typeof arg.objectId === "string" && (arg.subtype === "error" || /^(?:Error|EvalError|RangeError|ReferenceError|SyntaxError|TypeError|URIError|AggregateError)$/.test(arg.className ?? ""));
2204
+ }
2205
+ /** 按原始事件顺序发布日志;查询仅读取 Error 数据描述符,不执行 getter 或远程函数。 */
2206
+ var StructuredConsole = class {
2207
+ send;
2208
+ emit;
2209
+ enabled = false;
2210
+ pending = [];
2211
+ constructor(send, emit) {
2212
+ this.send = send;
2213
+ this.emit = emit;
2214
+ }
2215
+ receive = (event) => {
2216
+ if (!this.enabled || !isRecord(event) || event.domain !== "Runtime" || event.event !== "consoleAPICalled") return;
2217
+ const params = event.params;
2218
+ if (!isRecord(params) || typeof params.type !== "string" || !Array.isArray(params.args)) return;
2219
+ const original = params;
2220
+ const entry = {
2221
+ original,
2222
+ task: Promise.resolve()
2223
+ };
2224
+ this.pending.push(entry);
2225
+ entry.task = Promise.all(original.args.map((arg) => this.inspect(arg))).then((args) => {
2226
+ if (!entry.result) {
2227
+ entry.result = {
2228
+ ...original,
2229
+ args
2230
+ };
2231
+ this.drain();
2232
+ }
2233
+ });
2234
+ };
2235
+ async flush() {
2236
+ await Promise.all(this.pending.map((entry) => entry.task));
2237
+ }
2238
+ stop() {
2239
+ this.enabled = false;
2240
+ for (const entry of this.pending) entry.result ??= {
2241
+ ...entry.original,
2242
+ args: entry.original.args.map((arg) => isRemoteError(arg) ? {
2243
+ ...arg,
2244
+ inspectionError: "Console connection closed before Error inspection completed"
2245
+ } : arg)
2246
+ };
2247
+ this.drain();
2248
+ }
2249
+ drain() {
2250
+ while (this.pending[0]?.result) {
2251
+ const entry = this.pending.shift();
2252
+ this.emit(entry.result);
2253
+ }
2254
+ }
2255
+ async inspect(arg) {
2256
+ if (!isRemoteError(arg)) return arg;
2257
+ try {
2258
+ const response = await this.send("App.CDPCommand", {
2259
+ domain: "Runtime",
2260
+ method: "getProperties",
2261
+ params: {
2262
+ objectId: arg.objectId,
2263
+ ownProperties: true,
2264
+ accessorPropertiesOnly: false,
2265
+ generatePreview: false
2266
+ }
2267
+ }, { timeout: 1e3 });
2268
+ if (!isRecord(response) || !Array.isArray(response.result) || response.exceptionDetails) throw new Error("Runtime.getProperties did not return Error data descriptors");
2269
+ const value = { name: arg.className ?? "Error" };
2270
+ for (const descriptor of response.result) {
2271
+ if (!isRecord(descriptor) || ![
2272
+ "name",
2273
+ "message",
2274
+ "stack"
2275
+ ].includes(String(descriptor.name))) continue;
2276
+ if (!("get" in descriptor) && !("set" in descriptor) && isRecord(descriptor.value) && typeof descriptor.value.value === "string") value[String(descriptor.name)] = descriptor.value.value;
2277
+ }
2278
+ return {
2279
+ ...arg,
2280
+ value,
2281
+ ...!("message" in value) && !("stack" in value) ? { inspectionError: "Error has no readable message or stack data descriptors" } : {}
2282
+ };
2283
+ } catch (error) {
2284
+ return {
2285
+ ...arg,
2286
+ inspectionError: error instanceof Error ? error.message : String(error)
2287
+ };
2288
+ }
2289
+ }
2290
+ };
2291
+ //#endregion
2184
2292
  //#region src/util.ts
2185
2293
  /**
2186
2294
  * @file 二维码与插件路径工具。
@@ -2327,11 +2435,16 @@ var MiniProgram = class extends EventEmitter {
2327
2435
  connection;
2328
2436
  appBindings = /* @__PURE__ */ new Map();
2329
2437
  logEnabled = false;
2438
+ logEnabling;
2439
+ structuredLogs = false;
2440
+ structuredConsole;
2330
2441
  pageMap = /* @__PURE__ */ new Map();
2331
2442
  nativeIns;
2332
2443
  constructor(connection) {
2333
2444
  super();
2334
2445
  this.connection = connection;
2446
+ this.structuredConsole = new StructuredConsole((method, params, options) => this.send(method, params, options), (entry) => this.emit("console", entry));
2447
+ connection.on("App.CDPEvent", this.structuredConsole.receive);
2335
2448
  connection.on("App.logAdded", this.onLogAdded);
2336
2449
  connection.on("App.bindingCalled", this.onBindingCalled);
2337
2450
  connection.on("App.exceptionThrown", this.onExceptionThrown);
@@ -2376,7 +2489,7 @@ var MiniProgram = class extends EventEmitter {
2376
2489
  continue;
2377
2490
  }
2378
2491
  }
2379
- if (isCurrentPageProtocolTimeout(lastError) || isPageMetaMissingError(lastError) || isCurrentFrameTimedOutError(lastError)) try {
2492
+ if ((options.pageStackFallback ?? true) && (isCurrentPageProtocolTimeout(lastError) || isPageMetaMissingError(lastError) || isCurrentFrameTimedOutError(lastError))) try {
2380
2493
  const { pageStack } = await this.send("App.getPageStack", {}, sendOptions);
2381
2494
  const page = pageStack[pageStack.length - 1];
2382
2495
  if (page) return this.createPageFromPayload(page, pageStack.length);
@@ -2497,12 +2610,54 @@ var MiniProgram = class extends EventEmitter {
2497
2610
  });
2498
2611
  }
2499
2612
  disconnect() {
2613
+ this.structuredConsole.stop();
2500
2614
  this.connection.dispose();
2501
2615
  }
2502
- async enableLog(timeout) {
2503
- if (timeout === void 0) await this.send("App.enableLog");
2504
- else await this.send("App.enableLog", {}, { timeout });
2505
- this.logEnabled = true;
2616
+ /** 等待已接收的结构化日志完成 Error 属性检查并发布。 */
2617
+ async flushConsole() {
2618
+ await this.structuredConsole.flush();
2619
+ }
2620
+ enableLog(timeout, options = {}) {
2621
+ if (this.logEnabling) {
2622
+ if (options.structured !== void 0 && options.structured !== this.structuredLogs) return Promise.reject(/* @__PURE__ */ new Error("Cannot change console format while logging is being enabled"));
2623
+ return this.logEnabling;
2624
+ }
2625
+ this.structuredLogs = options.structured ?? this.structuredLogs;
2626
+ this.structuredConsole.enabled = this.structuredLogs;
2627
+ this.logEnabled = false;
2628
+ const startedAt = Date.now();
2629
+ const request = async (method, params = {}) => {
2630
+ if (timeout === void 0) return await this.send(method, params);
2631
+ const remaining = timeout - (Date.now() - startedAt);
2632
+ if (remaining <= 0) throw new Error(`Timed out enabling console logging within ${timeout}ms`);
2633
+ return await this.send(method, params, { timeout: remaining });
2634
+ };
2635
+ this.logEnabling = (async () => {
2636
+ if (!this.structuredLogs) await request("App.enableLog");
2637
+ let cdpMethod = this.structuredLogs ? "App.CDPEnable" : "App.CDPCommand";
2638
+ try {
2639
+ if (this.structuredLogs) {
2640
+ await request(cdpMethod);
2641
+ cdpMethod = "App.CDPCommand";
2642
+ }
2643
+ await request("App.CDPCommand", {
2644
+ domain: "Runtime",
2645
+ method: "enable",
2646
+ params: {}
2647
+ });
2648
+ } catch (error) {
2649
+ if (!(error instanceof Error) || error.message !== `appservice ${cdpMethod} unimplemented`) throw error;
2650
+ if (this.structuredLogs) {
2651
+ this.structuredConsole.enabled = false;
2652
+ await request("App.enableLog");
2653
+ }
2654
+ }
2655
+ this.logEnabled = true;
2656
+ })().finally(async () => {
2657
+ await this.flushConsole();
2658
+ this.logEnabling = void 0;
2659
+ });
2660
+ return this.logEnabling;
2506
2661
  }
2507
2662
  on(event, listener) {
2508
2663
  if (event === "console" && !this.logEnabled) this.enableLog().catch(() => {});
@@ -2709,8 +2864,10 @@ var MiniProgram = class extends EventEmitter {
2709
2864
  }
2710
2865
  }
2711
2866
  createPageFromPayload(page, fallbackId = 1) {
2867
+ const protocolId = page.pageId ?? page.__wxWebviewId__ ?? page.__webviewId__;
2712
2868
  return Page.create(this.connection, {
2713
2869
  id: resolvePagePayloadId(page, fallbackId),
2870
+ hasStableIdentity: typeof protocolId === "number" && Number.isSafeInteger(protocolId) && protocolId >= 0,
2714
2871
  path: resolvePagePayloadPath(page),
2715
2872
  query: page.query ?? page.options ?? {}
2716
2873
  }, this.pageMap);
@@ -2724,9 +2881,9 @@ var MiniProgram = class extends EventEmitter {
2724
2881
  const { result } = await this.send("App.callFunction", {
2725
2882
  functionDeclaration: `function () {
2726
2883
  var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
2727
- return pages.map(function (page, index) {
2884
+ return pages.map(function (page) {
2728
2885
  return {
2729
- pageId: page.pageId || page.__wxWebviewId__ || page.__webviewId__ || index + 1,
2886
+ pageId: page.pageId || page.__wxWebviewId__ || page.__webviewId__,
2730
2887
  path: page.path || page.route || page.__route__ || '',
2731
2888
  query: page.query || page.options || {}
2732
2889
  };
@@ -2746,7 +2903,7 @@ var MiniProgram = class extends EventEmitter {
2746
2903
  return options ? await this.connection.send(method, params, options) : await this.connection.send(method, params);
2747
2904
  }
2748
2905
  onLogAdded = (payload) => {
2749
- this.emit("console", payload);
2906
+ if (!this.structuredConsole.enabled) this.emit("console", payload);
2750
2907
  };
2751
2908
  onBindingCalled = (payload) => {
2752
2909
  const binding = this.appBindings.get(payload.name);
@@ -3425,9 +3582,25 @@ var Launcher = class {
3425
3582
  }
3426
3583
  async connect(options) {
3427
3584
  if (normalizePlatform(options.platform) === "swan") return await new SwanLauncher().connect(options);
3428
- const miniProgram = await this.connectTool(options);
3429
- await miniProgram.checkVersion(options.timeout);
3430
- return miniProgram;
3585
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
3586
+ const deadlineAt = performance.now() + timeout;
3587
+ const miniProgram = await this.connectTool({
3588
+ ...options,
3589
+ timeout
3590
+ });
3591
+ try {
3592
+ const remaining = Math.ceil(deadlineAt - performance.now());
3593
+ if (remaining <= 0) throw new Error(`Timed out connecting to automator after ${timeout}ms`);
3594
+ await miniProgram.checkVersion(remaining);
3595
+ return miniProgram;
3596
+ } catch (error) {
3597
+ try {
3598
+ miniProgram.disconnect();
3599
+ } catch (cleanupError) {
3600
+ throw new AggregateError([error, cleanupError], "Automator connection and cleanup both failed", { cause: error });
3601
+ }
3602
+ throw error;
3603
+ }
3431
3604
  }
3432
3605
  async extendProjectConfig(projectConfig, projectPath) {
3433
3606
  const projectConfigPath = path.resolve(projectPath, "project.config.json");