@weapp-vite/miniprogram-automator 1.0.1 → 1.0.2

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
@@ -1,7 +1,98 @@
1
1
  # @weapp-vite/miniprogram-automator
2
2
 
3
- 面向 `weapp-vite` 生态的现代化 `miniprogram-automator` 兼容实现。
3
+ ## 1. 简介
4
4
 
5
- - 提供纯根入口 named exports
6
- - 对齐官方 `MiniProgram / Page / Element / Native` 能力模型
7
- - 支持 DevTools 启动、WebSocket 协议连接与 headless 适配
5
+ `@weapp-vite/miniprogram-automator` 是面向 `weapp-vite` 生态维护的 `miniprogram-automator` 兼容实现,目标是在保留官方心智模型的前提下,提供更现代的 ESM 导出、类型支持与 headless 调试能力。
6
+
7
+ 它同时服务于:
8
+
9
+ - `weapp-ide-cli` 的自动化能力
10
+ - 仓库内 WeChat DevTools 相关 e2e 测试
11
+ - 需要直接连接微信开发者工具 WebSocket 协议的 Node 工具
12
+
13
+ ## 2. 特性
14
+
15
+ - 对齐官方常见对象模型:`MiniProgram`、`Page`、`Element`、`Native`
16
+ - 提供 `Launcher` 用于启动、连接和复用 DevTools 会话
17
+ - 支持截图、输入、滚动、点击、页面跳转等自动化操作
18
+ - 默认输出现代 ESM 与完整类型声明
19
+ - 内置二维码打印与解码辅助能力,便于配合登录、连接流程调试
20
+ - 支持 headless 自动化启动入口
21
+
22
+ ## 3. 安装
23
+
24
+ ```bash
25
+ pnpm add -D @weapp-vite/miniprogram-automator
26
+ ```
27
+
28
+ > **注意**:运行前仍需要本机安装微信开发者工具,并开启服务端口。
29
+
30
+ ## 4. 快速开始
31
+
32
+ ### 4.1 连接现有会话
33
+
34
+ ```ts
35
+ import { Launcher } from '@weapp-vite/miniprogram-automator'
36
+
37
+ const launcher = new Launcher()
38
+ const miniProgram = await launcher.connect({
39
+ wsEndpoint: 'ws://127.0.0.1:9420',
40
+ })
41
+
42
+ const page = await miniProgram.currentPage()
43
+ console.log(await page.data())
44
+ ```
45
+
46
+ ### 4.2 启动并打开项目
47
+
48
+ ```ts
49
+ import { Launcher } from '@weapp-vite/miniprogram-automator'
50
+
51
+ const launcher = new Launcher()
52
+ const miniProgram = await launcher.launch({
53
+ projectPath: './dist/build/mp-weixin',
54
+ })
55
+
56
+ await miniProgram.reLaunch('/pages/index/index')
57
+ ```
58
+
59
+ ### 4.3 使用页面与元素对象
60
+
61
+ ```ts
62
+ import { Launcher } from '@weapp-vite/miniprogram-automator'
63
+
64
+ const launcher = new Launcher()
65
+ const miniProgram = await launcher.launch({
66
+ projectPath: './dist/build/mp-weixin',
67
+ })
68
+
69
+ const page = await miniProgram.currentPage()
70
+ const button = await page.$('.submit')
71
+
72
+ await button?.tap()
73
+ await page.waitFor(500)
74
+ ```
75
+
76
+ ## 5. 主要导出
77
+
78
+ | 导出 | 说明 |
79
+ | ------------------------- | ---------------------------------------- |
80
+ | `Launcher` | 启动 DevTools、连接 WebSocket、创建会话 |
81
+ | `MiniProgram` | 小程序级操作入口,如页面跳转、获取当前页 |
82
+ | `Page` | 页面级查询与操作入口 |
83
+ | `Element` | 通用节点对象,支持点击、输入、事件触发 |
84
+ | `Native` | 原生能力桥接 |
85
+ | `launchHeadlessAutomator` | headless 启动辅助函数 |
86
+
87
+ ## 6. 本地开发
88
+
89
+ ```bash
90
+ pnpm --filter @weapp-vite/miniprogram-automator build
91
+ pnpm --filter @weapp-vite/miniprogram-automator test
92
+ pnpm --filter @weapp-vite/miniprogram-automator typecheck
93
+ ```
94
+
95
+ ## 7. 相关链接
96
+
97
+ - 仓库:https://github.com/weapp-vite/weapp-vite
98
+ - `weapp-ide-cli`:[../weapp-ide-cli/README.md](../weapp-ide-cli/README.md)
package/dist/index.d.mts CHANGED
@@ -265,6 +265,11 @@ interface ILaunchOptions {
265
265
  cwd?: string;
266
266
  runtimeProvider?: 'devtools' | 'headless';
267
267
  }
268
+ interface ILauncherSessionMetadata {
269
+ port: number;
270
+ projectPath: string;
271
+ wsEndpoint: string;
272
+ }
268
273
  /** Launcher 的实现。 */
269
274
  declare class Launcher {
270
275
  launch(options: ILaunchOptions): Promise<any>;
@@ -301,4 +306,4 @@ declare function isPluginPath(p: string): boolean;
301
306
  /** extractPluginId 的方法封装。 */
302
307
  declare function extractPluginId(p: string): string;
303
308
  //#endregion
304
- export { Automator, Connection, ContextElement, CustomElement, Element, IConnectOptions, ILaunchOptions, InputElement, Launcher, MiniProgram, MovableViewElement, Native, Page, ScrollViewElement, SliderElement, SwiperElement, SwitchElement, TextareaElement, Transport, decodeQrCode, extractPluginId, isPluginPath, printQrCode };
309
+ export { Automator, Connection, ContextElement, CustomElement, Element, IConnectOptions, ILaunchOptions, ILauncherSessionMetadata, InputElement, Launcher, MiniProgram, MovableViewElement, Native, Page, ScrollViewElement, SliderElement, SwiperElement, SwitchElement, TextareaElement, Transport, decodeQrCode, extractPluginId, isPluginPath, printQrCode };
package/dist/index.mjs CHANGED
@@ -497,6 +497,7 @@ var Transport = class extends EventEmitter {
497
497
  */
498
498
  const debugProtocol = debug("automator:protocol");
499
499
  const closeErrTip = "Connection closed, check if wechat web devTools is still running";
500
+ const REQUEST_TIMEOUT = 3e4;
500
501
  /** Connection 的实现。 */
501
502
  var Connection = class Connection extends EventEmitter {
502
503
  callbacks = /* @__PURE__ */ new Map();
@@ -515,13 +516,22 @@ var Connection = class Connection extends EventEmitter {
515
516
  });
516
517
  debugProtocol(`${dateFormat("yyyy-mm-dd HH:MM:ss:l")} SEND ► ${payload}`);
517
518
  return new Promise((resolve, reject) => {
519
+ const timeout = setTimeout(() => {
520
+ this.callbacks.delete(id);
521
+ const error = /* @__PURE__ */ new Error(`DevTools did not respond to protocol method ${method} within ${REQUEST_TIMEOUT}ms`);
522
+ error.code = "DEVTOOLS_PROTOCOL_TIMEOUT";
523
+ error.method = method;
524
+ reject(error);
525
+ }, REQUEST_TIMEOUT);
518
526
  this.callbacks.set(id, {
519
527
  resolve,
520
- reject
528
+ reject,
529
+ timeout
521
530
  });
522
531
  try {
523
532
  this.transport.send(payload);
524
533
  } catch {
534
+ clearTimeout(timeout);
525
535
  this.callbacks.delete(id);
526
536
  reject(new Error(closeErrTip));
527
537
  }
@@ -540,6 +550,7 @@ var Connection = class Connection extends EventEmitter {
540
550
  const callback = this.callbacks.get(id);
541
551
  if (!callback) return;
542
552
  this.callbacks.delete(id);
553
+ clearTimeout(callback.timeout);
543
554
  if (error) {
544
555
  callback.reject(new Error(error.message || closeErrTip));
545
556
  return;
@@ -547,7 +558,10 @@ var Connection = class Connection extends EventEmitter {
547
558
  callback.resolve(result);
548
559
  };
549
560
  onClose = () => {
550
- for (const callback of this.callbacks.values()) callback.reject(new Error(closeErrTip));
561
+ for (const callback of this.callbacks.values()) {
562
+ clearTimeout(callback.timeout);
563
+ callback.reject(new Error(closeErrTip));
564
+ }
551
565
  this.callbacks.clear();
552
566
  };
553
567
  static create(url) {
@@ -563,11 +577,11 @@ var Connection = class Connection extends EventEmitter {
563
577
  //#endregion
564
578
  //#region src/headless.ts
565
579
  async function launchHeadlessAutomator(options) {
566
- return await (await import("./launch-IFPMxQYb.mjs")).launch({ projectPath: options.projectPath });
580
+ return await (await import("./launch-Bd3TZy1I.mjs")).launch({ projectPath: options.projectPath });
567
581
  }
568
582
  //#endregion
569
583
  //#region package.json
570
- var version = "1.0.1";
584
+ var version = "1.0.2";
571
585
  //#endregion
572
586
  //#region src/Native.ts
573
587
  /** Native 的实现。 */
@@ -755,9 +769,24 @@ function extractPluginId(p) {
755
769
  }
756
770
  //#endregion
757
771
  //#region src/MiniProgram.ts
772
+ const CLOSE_STEP_TIMEOUT = 2e3;
758
773
  function sleep(ms) {
759
774
  return new Promise((resolve) => setTimeout(resolve, ms));
760
775
  }
776
+ function withTimeout(task, timeoutMs) {
777
+ return new Promise((resolve, reject) => {
778
+ const timeout = setTimeout(() => {
779
+ reject(/* @__PURE__ */ new Error(`Operation timed out after ${timeoutMs}ms`));
780
+ }, timeoutMs);
781
+ task.then((value) => {
782
+ clearTimeout(timeout);
783
+ resolve(value);
784
+ }).catch((error) => {
785
+ clearTimeout(timeout);
786
+ reject(error);
787
+ });
788
+ });
789
+ }
761
790
  function isFnStr(value) {
762
791
  if (!isStr(value)) return false;
763
792
  const trimmed = trim(value);
@@ -878,11 +907,14 @@ var MiniProgram = class extends EventEmitter {
878
907
  }
879
908
  async close() {
880
909
  try {
881
- await this.send("App.exit");
910
+ await withTimeout(this.send("App.exit"), CLOSE_STEP_TIMEOUT);
882
911
  } catch {}
883
912
  await sleep(1e3);
884
- await this.send("Tool.close");
885
- this.disconnect();
913
+ try {
914
+ await withTimeout(this.send("Tool.close"), CLOSE_STEP_TIMEOUT);
915
+ } catch {} finally {
916
+ this.disconnect();
917
+ }
886
918
  }
887
919
  async remote(auto = false) {
888
920
  const { qrCode } = await this.send("Tool.enableRemoteDebug", { auto });
@@ -972,7 +1004,11 @@ const DEFAULT_PORT = 9420;
972
1004
  const DEFAULT_TIMEOUT = 3e4;
973
1005
  const DEFAULT_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_AUTOMATOR_RUNTIME_PROVIDER";
974
1006
  const LEGACY_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_E2E_RUNTIME_PROVIDER";
1007
+ const EXTENSION_CONTEXT_INVALIDATED_RE = /Extension context invalidated/i;
975
1008
  let localhostListenPatched = false;
1009
+ function isExtensionContextInvalidatedError(error) {
1010
+ return error instanceof Error && EXTENSION_CONTEXT_INVALIDATED_RE.test(error.message);
1011
+ }
976
1012
  function patchNetListenToLoopback() {
977
1013
  if (localhostListenPatched) return;
978
1014
  localhostListenPatched = true;
@@ -1039,22 +1075,38 @@ var Launcher = class {
1039
1075
  processError = error;
1040
1076
  }
1041
1077
  let miniProgram = null;
1078
+ let lastConnectError = null;
1042
1079
  await waitUntil(async () => {
1043
1080
  try {
1044
1081
  if (processError || exited) return true;
1045
- miniProgram = await this.connectTool({ wsEndpoint: `ws://127.0.0.1:${port}` });
1082
+ const candidate = await this.connectTool({ wsEndpoint: `ws://127.0.0.1:${port}` });
1083
+ try {
1084
+ await candidate.checkVersion();
1085
+ } catch (error) {
1086
+ candidate.disconnect();
1087
+ lastConnectError = error;
1088
+ if (isExtensionContextInvalidatedError(error)) return false;
1089
+ throw error;
1090
+ }
1091
+ miniProgram = candidate;
1046
1092
  return true;
1047
- } catch {
1093
+ } catch (error) {
1094
+ lastConnectError = error;
1048
1095
  return false;
1049
1096
  }
1050
1097
  }, timeout, 1e3);
1051
1098
  if (!miniProgram) {
1052
1099
  if (processError) throw new Error("Failed to launch wechat web devTools, please make sure cliPath is correctly specified");
1100
+ if (lastConnectError) throw lastConnectError;
1053
1101
  if (exited) throw new Error("Failed to launch wechat web devTools, please make sure http port is open");
1054
1102
  throw new Error("Failed connecting to devtools websocket endpoint");
1055
1103
  }
1056
1104
  const resolvedMiniProgram = miniProgram;
1057
- await resolvedMiniProgram.checkVersion();
1105
+ Reflect.set(resolvedMiniProgram, "__WEAPP_VITE_SESSION_METADATA", {
1106
+ port,
1107
+ projectPath,
1108
+ wsEndpoint: `ws://127.0.0.1:${port}`
1109
+ });
1058
1110
  await sleep$1(5e3);
1059
1111
  return resolvedMiniProgram;
1060
1112
  }