@weapp-vite/miniprogram-automator 1.0.2 → 1.0.3

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.d.mts CHANGED
@@ -247,7 +247,6 @@ declare class MiniProgram extends EventEmitter {
247
247
  }
248
248
  //#endregion
249
249
  //#region src/Launcher.d.ts
250
- /** IConnectOptions 的类型定义。 */
251
250
  interface IConnectOptions {
252
251
  wsEndpoint: string;
253
252
  }
package/dist/index.mjs CHANGED
@@ -577,11 +577,11 @@ var Connection = class Connection extends EventEmitter {
577
577
  //#endregion
578
578
  //#region src/headless.ts
579
579
  async function launchHeadlessAutomator(options) {
580
- return await (await import("./launch-Bd3TZy1I.mjs")).launch({ projectPath: options.projectPath });
580
+ return await (await import("./launch-Didv0lMX.mjs")).launch({ projectPath: options.projectPath });
581
581
  }
582
582
  //#endregion
583
583
  //#region package.json
584
- var version = "1.0.2";
584
+ var version = "1.0.3";
585
585
  //#endregion
586
586
  //#region src/Native.ts
587
587
  /** Native 的实现。 */
@@ -770,6 +770,8 @@ function extractPluginId(p) {
770
770
  //#endregion
771
771
  //#region src/MiniProgram.ts
772
772
  const CLOSE_STEP_TIMEOUT = 2e3;
773
+ const CURRENT_PAGE_RETRIES = 3;
774
+ const CURRENT_PAGE_RETRY_DELAY = 400;
773
775
  function sleep(ms) {
774
776
  return new Promise((resolve) => setTimeout(resolve, ms));
775
777
  }
@@ -792,6 +794,9 @@ function isFnStr(value) {
792
794
  const trimmed = trim(value);
793
795
  return startWith(trimmed, "function") || startWith(trimmed, "() =>");
794
796
  }
797
+ function isCurrentPageProtocolTimeout(error) {
798
+ return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.getCurrentPage";
799
+ }
795
800
  /** MiniProgram 的实现。 */
796
801
  var MiniProgram = class extends EventEmitter {
797
802
  appBindings = /* @__PURE__ */ new Map();
@@ -830,12 +835,20 @@ var MiniProgram = class extends EventEmitter {
830
835
  return await this.changeRoute("switchTab", url);
831
836
  }
832
837
  async currentPage() {
833
- const { pageId, path, query } = await this.send("App.getCurrentPage");
834
- return Page.create(this.connection, {
835
- id: pageId,
836
- path,
837
- query
838
- }, this.pageMap);
838
+ let lastError;
839
+ for (let attempt = 1; attempt <= CURRENT_PAGE_RETRIES; attempt += 1) try {
840
+ const { pageId, path, query } = await this.send("App.getCurrentPage");
841
+ return Page.create(this.connection, {
842
+ id: pageId,
843
+ path,
844
+ query
845
+ }, this.pageMap);
846
+ } catch (error) {
847
+ lastError = error;
848
+ if (!isCurrentPageProtocolTimeout(error) || attempt >= CURRENT_PAGE_RETRIES) throw error;
849
+ await sleep(CURRENT_PAGE_RETRY_DELAY);
850
+ }
851
+ throw lastError;
839
852
  }
840
853
  async systemInfo() {
841
854
  return await this.callWxMethod("getSystemInfoSync");
@@ -1005,6 +1018,7 @@ const DEFAULT_TIMEOUT = 3e4;
1005
1018
  const DEFAULT_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_AUTOMATOR_RUNTIME_PROVIDER";
1006
1019
  const LEGACY_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_E2E_RUNTIME_PROVIDER";
1007
1020
  const EXTENSION_CONTEXT_INVALIDATED_RE = /Extension context invalidated/i;
1021
+ const WINDOWS_BATCH_CLI_RE = /\.(?:bat|cmd)$/i;
1008
1022
  let localhostListenPatched = false;
1009
1023
  function isExtensionContextInvalidatedError(error) {
1010
1024
  return error instanceof Error && EXTENSION_CONTEXT_INVALIDATED_RE.test(error.message);
@@ -1026,6 +1040,25 @@ function patchNetListenToLoopback() {
1026
1040
  return rawListen.apply(this, args);
1027
1041
  };
1028
1042
  }
1043
+ /** IConnectOptions 的类型定义。 */
1044
+ function shouldUseWindowsCommandShell(cliPath) {
1045
+ return isWindows && WINDOWS_BATCH_CLI_RE.test(cliPath);
1046
+ }
1047
+ function escapeWindowsCmdArg(arg) {
1048
+ const escaped = arg.replace(/"/g, "\"\"").replace(/%/g, "%%");
1049
+ return /[\s"&<>^|()]/.test(arg) ? `"${escaped}"` : escaped;
1050
+ }
1051
+ function resolveWindowsBatchSpawn(cliPath, args) {
1052
+ return {
1053
+ file: process.env.ComSpec || "cmd.exe",
1054
+ args: [
1055
+ "/d",
1056
+ "/s",
1057
+ "/c",
1058
+ `"${[cliPath, ...args].map(escapeWindowsCmdArg).join(" ")}"`
1059
+ ]
1060
+ };
1061
+ }
1029
1062
  function resolveRuntimeProvider(options) {
1030
1063
  return options.runtimeProvider || process.env[DEFAULT_RUNTIME_PROVIDER_ENV] || process.env[LEGACY_RUNTIME_PROVIDER_ENV] || "devtools";
1031
1064
  }
@@ -1045,7 +1078,8 @@ var Launcher = class {
1045
1078
  if (!await import("node:fs/promises").then((fs) => fs.access(projectPath).then(() => true).catch(() => false))) throw new Error(`Project path ${projectPath} doesn't exist`);
1046
1079
  if (!isEmpty(projectConfig)) await this.extendProjectConfig(projectConfig, projectPath);
1047
1080
  let processError = null;
1048
- let exited = false;
1081
+ let processExitCode = null;
1082
+ let processSignal = null;
1049
1083
  args = [
1050
1084
  ...args,
1051
1085
  "auto",
@@ -1058,17 +1092,25 @@ var Launcher = class {
1058
1092
  else if (ticket) args.push("--ticket", ticket);
1059
1093
  if (trustProject) args.push("--trust-project");
1060
1094
  try {
1061
- const child = spawn(cliPath, args, {
1095
+ const spawnTarget = shouldUseWindowsCommandShell(cliPath) ? resolveWindowsBatchSpawn(cliPath, args) : {
1096
+ file: cliPath,
1097
+ args
1098
+ };
1099
+ const child = spawn(spawnTarget.file, spawnTarget.args, {
1062
1100
  stdio: "ignore",
1063
- cwd: cwd || void 0
1101
+ cwd: cwd || void 0,
1102
+ ...shouldUseWindowsCommandShell(cliPath) ? {
1103
+ windowsHide: true,
1104
+ windowsVerbatimArguments: true
1105
+ } : {}
1064
1106
  });
1065
1107
  child.on("error", (error) => {
1066
1108
  processError = error;
1067
1109
  });
1068
- child.on("exit", () => {
1069
- setTimeout(() => {
1070
- exited = true;
1071
- }, 15e3);
1110
+ child.on("exit", (code, signal) => {
1111
+ processExitCode = code;
1112
+ processSignal = signal;
1113
+ if (code !== 0 || signal) processError = /* @__PURE__ */ new Error(`DevTools cli exited unexpectedly with code ${code ?? "null"}${signal ? ` and signal ${signal}` : ""}`);
1072
1114
  });
1073
1115
  child.unref();
1074
1116
  } catch (error) {
@@ -1078,7 +1120,7 @@ var Launcher = class {
1078
1120
  let lastConnectError = null;
1079
1121
  await waitUntil(async () => {
1080
1122
  try {
1081
- if (processError || exited) return true;
1123
+ if (processError) return true;
1082
1124
  const candidate = await this.connectTool({ wsEndpoint: `ws://127.0.0.1:${port}` });
1083
1125
  try {
1084
1126
  await candidate.checkVersion();
@@ -1098,7 +1140,7 @@ var Launcher = class {
1098
1140
  if (!miniProgram) {
1099
1141
  if (processError) throw new Error("Failed to launch wechat web devTools, please make sure cliPath is correctly specified");
1100
1142
  if (lastConnectError) throw lastConnectError;
1101
- if (exited) throw new Error("Failed to launch wechat web devTools, please make sure http port is open");
1143
+ if (processExitCode !== null || processSignal) throw new Error("Failed to launch wechat web devTools, please make sure http port is open");
1102
1144
  throw new Error("Failed connecting to devtools websocket endpoint");
1103
1145
  }
1104
1146
  const resolvedMiniProgram = miniProgram;
@@ -27,9 +27,12 @@ function createAppInstance(definition) {
27
27
  return instance;
28
28
  }
29
29
  //#endregion
30
- //#region ../../mpcore/packages/simulator/src/runtime/componentInstance.ts
30
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/shared.ts
31
31
  const ARRAY_INDEX_PATH_RE$1 = /\[(\d+)\]/g;
32
32
  const ARRAY_INDEX_SEGMENT_RE$1 = /^\d+$/;
33
+ function cloneObject$1(value) {
34
+ return JSON.parse(JSON.stringify(value));
35
+ }
33
36
  function bindFunction$1(target, key, value) {
34
37
  if (typeof value !== "function") {
35
38
  target[key] = value;
@@ -37,14 +40,14 @@ function bindFunction$1(target, key, value) {
37
40
  }
38
41
  target[key] = (...args) => value.apply(target, args);
39
42
  }
40
- function cloneObject$1(value) {
41
- return JSON.parse(JSON.stringify(value));
42
- }
43
43
  function cloneValue$1(value) {
44
44
  if (Array.isArray(value)) return JSON.parse(JSON.stringify(value));
45
45
  if (value && typeof value === "object") return cloneObject$1(value);
46
46
  return value;
47
47
  }
48
+ function cloneRecord(value) {
49
+ return cloneObject$1(value);
50
+ }
48
51
  function parseDataPath$1(path) {
49
52
  return path.replace(ARRAY_INDEX_PATH_RE$1, ".$1").split(".").map((segment) => segment.trim()).filter(Boolean);
50
53
  }
@@ -70,13 +73,70 @@ function assignByPath$1(target, path, value) {
70
73
  const normalizedLeafSegment = isArrayIndexSegment$1(leafSegment) ? Number(leafSegment) : leafSegment;
71
74
  current[normalizedLeafSegment] = value;
72
75
  }
76
+ function mergeRecord(...records) {
77
+ return Object.assign({}, ...records.filter(Boolean));
78
+ }
79
+ //#endregion
80
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/observers.ts
81
+ function runPropertyObservers(definition, instance, changedKeys, previousProperties) {
82
+ const propOptions = definition.properties;
83
+ if (!propOptions || typeof propOptions !== "object" || Array.isArray(propOptions)) return;
84
+ for (const changedKey of changedKeys) {
85
+ const option = propOptions[changedKey];
86
+ if (!option || typeof option !== "object" || Array.isArray(option)) continue;
87
+ const observer = option.observer;
88
+ if (typeof observer !== "function") continue;
89
+ observer.call(instance, instance.properties[changedKey], previousProperties[changedKey]);
90
+ }
91
+ }
92
+ function normalizeObserverPattern(pattern) {
93
+ return pattern.split(",").map((item) => item.trim()).filter(Boolean);
94
+ }
95
+ function resolveObservedValue(instance, pattern) {
96
+ if (pattern === "**") return {
97
+ ...instance.properties,
98
+ ...instance.data
99
+ };
100
+ const segments = parseDataPath$1(pattern);
101
+ if (segments.length === 0) return;
102
+ const [rootSegment, ...restSegments] = segments;
103
+ let current = (Object.hasOwn(instance.properties, rootSegment) ? instance.properties : instance.data)?.[rootSegment];
104
+ for (const segment of restSegments) {
105
+ const normalizedSegment = isArrayIndexSegment$1(segment) ? Number(segment) : segment;
106
+ current = current?.[normalizedSegment];
107
+ }
108
+ return current;
109
+ }
110
+ function matchObserverPattern(pattern, changedKeys) {
111
+ if (pattern === "**") return changedKeys.length > 0;
112
+ const patterns = normalizeObserverPattern(pattern);
113
+ return changedKeys.some((changedKey) => {
114
+ return patterns.some((item) => {
115
+ if (item === "**") return true;
116
+ if (changedKey === item) return true;
117
+ return changedKey.startsWith(`${item}.`) || changedKey.startsWith(`${item}[`);
118
+ });
119
+ });
120
+ }
121
+ function runComponentObservers(definition, instance, changedKeys, previousProperties = {}) {
122
+ if (changedKeys.length === 0) return;
123
+ runPropertyObservers(definition, instance, changedKeys, previousProperties);
124
+ const observers = definition.observers;
125
+ if (observers && typeof observers === "object" && !Array.isArray(observers)) for (const [pattern, handler] of Object.entries(observers)) {
126
+ if (typeof handler !== "function" || !matchObserverPattern(pattern, changedKeys)) continue;
127
+ const args = normalizeObserverPattern(pattern).map((item) => resolveObservedValue(instance, item));
128
+ handler.call(instance, ...args);
129
+ }
130
+ }
131
+ //#endregion
132
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/properties.ts
73
133
  function resolveInitialData$1(definition) {
74
134
  const rawData = definition.data;
75
135
  if (typeof rawData === "function") {
76
136
  const next = rawData.call(definition);
77
- return next && typeof next === "object" && !Array.isArray(next) ? cloneObject$1(next) : {};
137
+ return next && typeof next === "object" && !Array.isArray(next) ? cloneRecord(next) : {};
78
138
  }
79
- return rawData && typeof rawData === "object" && !Array.isArray(rawData) ? cloneObject$1(rawData) : {};
139
+ return rawData && typeof rawData === "object" && !Array.isArray(rawData) ? cloneRecord(rawData) : {};
80
140
  }
81
141
  function isBehaviorDefinition(value) {
82
142
  return !!value && typeof value === "object" && !Array.isArray(value) && value.__isHeadlessBehavior__ === true;
@@ -91,9 +151,6 @@ function flattenBehaviors(definition) {
91
151
  }
92
152
  return flattened;
93
153
  }
94
- function mergeRecord(...records) {
95
- return Object.assign({}, ...records.filter(Boolean));
96
- }
97
154
  function normalizeComponentDefinition(definition) {
98
155
  const behaviors = flattenBehaviors(definition);
99
156
  if (behaviors.length === 0) return definition;
@@ -200,61 +257,13 @@ function resolvePropertyDefaultValue(option) {
200
257
  if (!option || typeof option !== "object" || Array.isArray(option) || !("value" in option)) return;
201
258
  return cloneValue$1(option.value);
202
259
  }
203
- function runPropertyObservers(definition, instance, changedKeys, previousProperties) {
204
- const propOptions = definition.properties;
205
- if (!propOptions || typeof propOptions !== "object" || Array.isArray(propOptions)) return;
206
- for (const changedKey of changedKeys) {
207
- const option = propOptions[changedKey];
208
- if (!option || typeof option !== "object" || Array.isArray(option)) continue;
209
- const observer = option.observer;
210
- if (typeof observer !== "function") continue;
211
- observer.call(instance, instance.properties[changedKey], previousProperties[changedKey]);
212
- }
213
- }
214
- function normalizeObserverPattern(pattern) {
215
- return pattern.split(",").map((item) => item.trim()).filter(Boolean);
216
- }
217
- function resolveObservedValue(instance, pattern) {
218
- if (pattern === "**") return {
219
- ...instance.properties,
220
- ...instance.data
221
- };
222
- const segments = parseDataPath$1(pattern);
223
- if (segments.length === 0) return;
224
- const [rootSegment, ...restSegments] = segments;
225
- let current = (Object.hasOwn(instance.properties, rootSegment) ? instance.properties : instance.data)?.[rootSegment];
226
- for (const segment of restSegments) {
227
- const normalizedSegment = isArrayIndexSegment$1(segment) ? Number(segment) : segment;
228
- current = current?.[normalizedSegment];
229
- }
230
- return current;
231
- }
232
- function matchObserverPattern(pattern, changedKeys) {
233
- if (pattern === "**") return changedKeys.length > 0;
234
- const patterns = normalizeObserverPattern(pattern);
235
- return changedKeys.some((changedKey) => {
236
- return patterns.some((item) => {
237
- if (item === "**") return true;
238
- if (changedKey === item) return true;
239
- return changedKey.startsWith(`${item}.`) || changedKey.startsWith(`${item}[`);
240
- });
241
- });
242
- }
243
- function runComponentObservers(definition, instance, changedKeys, previousProperties = {}) {
244
- if (changedKeys.length === 0) return;
245
- runPropertyObservers(definition, instance, changedKeys, previousProperties);
246
- const observers = definition.observers;
247
- if (observers && typeof observers === "object" && !Array.isArray(observers)) for (const [pattern, handler] of Object.entries(observers)) {
248
- if (typeof handler !== "function" || !matchObserverPattern(pattern, changedKeys)) continue;
249
- const args = normalizeObserverPattern(pattern).map((item) => resolveObservedValue(instance, item));
250
- handler.call(instance, ...args);
251
- }
252
- }
253
260
  function normalizeComponentPropertyValue(definition, key, rawValue) {
254
261
  const option = definition.properties?.[key];
255
262
  if (rawValue == null) return resolvePropertyDefaultValue(option);
256
263
  return coerceComponentPropertyValue(rawValue, option);
257
264
  }
265
+ //#endregion
266
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/index.ts
258
267
  function createComponentInstance(options) {
259
268
  const definition = normalizeComponentDefinition(options.definition);
260
269
  const instance = {
@@ -2830,9 +2839,11 @@ function parseDocument(data, options) {
2830
2839
  return handler.root;
2831
2840
  }
2832
2841
  //#endregion
2833
- //#region ../../mpcore/packages/simulator/src/runtime/render.ts
2834
- const LEADING_SLASH_RE$4 = /^\/+/;
2842
+ //#region ../../mpcore/packages/simulator/src/runtime/render/shared.ts
2835
2843
  const TEMPLATE_INTERPOLATION_RE$1 = /\{\{([^{}]+)\}\}/g;
2844
+ const DATASET_NAME_RE$3 = /-([a-z])/g;
2845
+ const BRACKET_INDEX_RE = /\[(\d+)\]/g;
2846
+ const LEADING_SLASH_RE$4 = /^\/+/;
2836
2847
  const EVENT_BINDING_ATTRS = [
2837
2848
  "bindtap",
2838
2849
  "bind:tap",
@@ -2855,8 +2866,6 @@ const STRUCTURAL_ATTRS = [
2855
2866
  "wx:key"
2856
2867
  ];
2857
2868
  const WX_ELSE_ATTRS = new Set(["wx:elif", "wx:else"]);
2858
- const DATASET_NAME_RE$3 = /-([a-z])/g;
2859
- const BRACKET_INDEX_RE = /\[(\d+)\]/g;
2860
2869
  const CLASS_SPLIT_RE = /\s+/;
2861
2870
  const JS_FILE_RE = /\.js$/;
2862
2871
  function isMustacheOnly(value) {
@@ -2964,21 +2973,46 @@ function resolveComponentAttributeValue(value, scope) {
2964
2973
  }
2965
2974
  return interpolateTemplate$1(value, scope.data);
2966
2975
  }
2967
- function resolveUsingComponents(ownerJsonPath, ownerFilePath) {
2968
- try {
2969
- const usingComponents = JSON.parse(fs.readFileSync(ownerJsonPath, "utf8")).usingComponents;
2970
- if (!usingComponents || typeof usingComponents !== "object" || Array.isArray(usingComponents)) return /* @__PURE__ */ new Map();
2971
- const resolved = /* @__PURE__ */ new Map();
2972
- for (const [alias, rawPath] of Object.entries(usingComponents)) {
2973
- if (typeof rawPath !== "string") continue;
2974
- const basePath = rawPath.startsWith("/") ? rawPath.replace(LEADING_SLASH_RE$4, "") : path.posix.normalize(path.posix.join(path.posix.dirname(ownerFilePath), rawPath));
2975
- resolved.set(alias, basePath.replace(LEADING_SLASH_RE$4, ""));
2976
+ function applyNodeBindings(node, scope) {
2977
+ if (!isTagNode(node)) {
2978
+ if (node.type === "text" && typeof node.data === "string") node.data = interpolateTemplate$1(node.data, scope.data);
2979
+ return;
2980
+ }
2981
+ node.attribs ??= {};
2982
+ node.attribs["data-sim-scope"] = scope.getScopeId();
2983
+ for (const key of STRUCTURAL_ATTRS) delete node.attribs[key];
2984
+ for (const [key, value] of Object.entries({ ...node.attribs })) {
2985
+ if (EVENT_BINDING_ATTRS.includes(key)) {
2986
+ node.attribs["data-sim-tap"] = value;
2987
+ continue;
2976
2988
  }
2977
- return resolved;
2978
- } catch {
2979
- return /* @__PURE__ */ new Map();
2989
+ node.attribs[key] = typeof value === "string" ? String(resolveAttributeValue(value, scope)) : String(value);
2980
2990
  }
2981
2991
  }
2992
+ function createMergedScopeData(pageData, componentProperties, componentData) {
2993
+ return {
2994
+ ...pageData,
2995
+ ...componentProperties,
2996
+ ...componentData
2997
+ };
2998
+ }
2999
+ function evaluateConditionalBranch(node, scope) {
3000
+ const condition = node.attribs?.["wx:if"] ?? node.attribs?.["wx:elif"];
3001
+ if (condition == null) return true;
3002
+ return Boolean(resolveRawValueByPath(scope.data, condition));
3003
+ }
3004
+ function createLoopScope(scope, itemName, indexName, item, index) {
3005
+ return {
3006
+ ...scope,
3007
+ data: {
3008
+ ...scope.data,
3009
+ [indexName]: index,
3010
+ [itemName]: item
3011
+ }
3012
+ };
3013
+ }
3014
+ //#endregion
3015
+ //#region ../../mpcore/packages/simulator/src/runtime/render/component.ts
2982
3016
  function resolveComponentRegistryEntry(context, ownerJsonPath, ownerFilePath, alias) {
2983
3017
  const componentBasePath = resolveUsingComponents(ownerJsonPath, ownerFilePath).get(alias);
2984
3018
  if (!componentBasePath) return null;
@@ -2993,6 +3027,21 @@ function resolveComponentRegistryEntry(context, ownerJsonPath, ownerFilePath, al
2993
3027
  absoluteTemplatePath
2994
3028
  };
2995
3029
  }
3030
+ function resolveUsingComponents(ownerJsonPath, ownerFilePath) {
3031
+ try {
3032
+ const usingComponents = JSON.parse(fs.readFileSync(ownerJsonPath, "utf8")).usingComponents;
3033
+ if (!usingComponents || typeof usingComponents !== "object" || Array.isArray(usingComponents)) return /* @__PURE__ */ new Map();
3034
+ const resolved = /* @__PURE__ */ new Map();
3035
+ for (const [alias, rawPath] of Object.entries(usingComponents)) {
3036
+ if (typeof rawPath !== "string") continue;
3037
+ const basePath = rawPath.startsWith("/") ? rawPath.replace(LEADING_SLASH_RE$4, "") : path.posix.normalize(path.posix.join(path.posix.dirname(ownerFilePath), rawPath));
3038
+ resolved.set(alias, basePath.replace(LEADING_SLASH_RE$4, ""));
3039
+ }
3040
+ return resolved;
3041
+ } catch {
3042
+ return /* @__PURE__ */ new Map();
3043
+ }
3044
+ }
2996
3045
  function collectComponentEventBindings(hostNode) {
2997
3046
  const eventBindings = /* @__PURE__ */ new Map();
2998
3047
  for (const [key, value] of Object.entries(hostNode.attribs ?? {})) {
@@ -3043,22 +3092,6 @@ function buildComponentTrigger(componentScopeId, context, hostNode) {
3043
3092
  }
3044
3093
  };
3045
3094
  }
3046
- function applyNodeBindings(node, scope) {
3047
- if (!isTagNode(node)) {
3048
- if (node.type === "text" && typeof node.data === "string") node.data = interpolateTemplate$1(node.data, scope.data);
3049
- return;
3050
- }
3051
- node.attribs ??= {};
3052
- node.attribs["data-sim-scope"] = scope.getScopeId();
3053
- for (const key of STRUCTURAL_ATTRS) delete node.attribs[key];
3054
- for (const [key, value] of Object.entries({ ...node.attribs })) {
3055
- if (EVENT_BINDING_ATTRS.includes(key)) {
3056
- node.attribs["data-sim-tap"] = value;
3057
- continue;
3058
- }
3059
- node.attribs[key] = typeof value === "string" ? String(resolveAttributeValue(value, scope)) : String(value);
3060
- }
3061
- }
3062
3095
  function syncComponentProperties(instance, definition, nextProperties, bindingExpressions, changedPageKeys) {
3063
3096
  const changedRootKeys = [];
3064
3097
  const previousProperties = {};
@@ -3081,31 +3114,62 @@ function syncComponentProperties(instance, definition, nextProperties, bindingEx
3081
3114
  if (changedRootKeys.length === 0) return;
3082
3115
  runComponentObservers(definition, instance, changedRootKeys, previousProperties);
3083
3116
  }
3084
- function createMergedScopeData(pageData, componentProperties, componentData) {
3117
+ function createComponentScope(clonedNode, scope, componentScopeId, componentInstance) {
3118
+ const ownerScopeId = scope.getScopeId().includes("/") ? scope.getScopeId() : void 0;
3085
3119
  return {
3086
- ...pageData,
3087
- ...componentProperties,
3088
- ...componentData
3120
+ alias: clonedNode.name,
3121
+ classList: String(clonedNode.attribs?.class ?? "").split(CLASS_SPLIT_RE).map((item) => item.trim()).filter(Boolean),
3122
+ data: createMergedScopeData(scope.data, componentInstance.properties, componentInstance.data),
3123
+ dataset: collectDataset$3(clonedNode),
3124
+ eventBindings: collectComponentEventBindings(clonedNode),
3125
+ getMethod: (methodName) => {
3126
+ const method = componentInstance?.[methodName];
3127
+ return typeof method === "function" ? method : void 0;
3128
+ },
3129
+ getScopeId: () => componentScopeId,
3130
+ hostId: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
3131
+ id: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
3132
+ listenerScopeId: scope.getScopeId(),
3133
+ ownerScopeId
3089
3134
  };
3090
3135
  }
3091
- function isTruthy(value) {
3092
- return Boolean(value);
3093
- }
3094
- function createLoopScope(scope, itemName, indexName, item, index) {
3136
+ function resolveComponentProperties(clonedNode, scope) {
3137
+ const nextProperties = {};
3138
+ const bindingExpressions = {};
3139
+ for (const [key, value] of Object.entries(clonedNode.attribs ?? {})) {
3140
+ if (key.startsWith("bind")) continue;
3141
+ if (isMustacheOnly(String(value))) bindingExpressions[key] = String(value).trim().slice(2, -2).trim();
3142
+ nextProperties[key] = resolveComponentAttributeValue(String(value), scope);
3143
+ }
3095
3144
  return {
3096
- ...scope,
3097
- data: {
3098
- ...scope.data,
3099
- [indexName]: index,
3100
- [itemName]: item
3101
- }
3145
+ nextProperties,
3146
+ bindingExpressions
3102
3147
  };
3103
3148
  }
3104
- function evaluateConditionalBranch(node, scope) {
3105
- const condition = node.attribs?.["wx:if"] ?? node.attribs?.["wx:elif"];
3106
- if (condition == null) return true;
3107
- return isTruthy(resolveRawValueByPath(scope.data, condition));
3149
+ function createRuntimeComponentInstance(componentScopeId, context, clonedNode, componentEntry, nextProperties, ownerScopeId) {
3150
+ const componentInstance = createComponentInstance({
3151
+ definition: componentEntry.definition,
3152
+ properties: nextProperties,
3153
+ triggerEvent: buildComponentTrigger(componentScopeId, context, clonedNode)
3154
+ });
3155
+ componentInstance.createIntersectionObserver = (options) => context.session.createIntersectionObserver(componentInstance, options);
3156
+ componentInstance.createMediaQueryObserver = () => context.session.createMediaQueryObserver(componentInstance);
3157
+ componentInstance.selectComponent = (selector) => context.session.selectComponentWithin(componentScopeId, selector);
3158
+ componentInstance.selectAllComponents = (selector) => context.session.selectAllComponentsWithin(componentScopeId, selector);
3159
+ componentInstance.selectOwnerComponent = () => ownerScopeId ? context.session.selectOwnerComponent(componentScopeId) : null;
3160
+ runComponentLifecycle(componentInstance, "created");
3161
+ runComponentObservers(componentInstance.__definition__ ?? componentEntry.definition, componentInstance, Object.keys(nextProperties), {});
3162
+ componentInstance.__propertySnapshots = Object.fromEntries(Object.entries(componentInstance.properties).map(([key, propertyValue]) => [key, cloneValue$1(propertyValue)]));
3163
+ runComponentLifecycle(componentInstance, "attached");
3164
+ context.componentCache.set(componentScopeId, componentInstance);
3165
+ return componentInstance;
3166
+ }
3167
+ function renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, seenComponentScopes) {
3168
+ const componentDocument = parseTemplateDocument(readTemplateSource(componentEntry.absoluteTemplatePath));
3169
+ return renderNodeTree((componentDocument.children ?? [])[0] ?? componentDocument, componentScope, context, path.resolve(context.project.miniprogramRootPath, `${componentEntry.filePath.replace(JS_FILE_RE, "")}.json`), componentEntry.filePath, componentScopeId, seenComponentScopes);
3108
3170
  }
3171
+ //#endregion
3172
+ //#region ../../mpcore/packages/simulator/src/runtime/render/index.ts
3109
3173
  function expandNodeByFor(node, scope) {
3110
3174
  const forExpression = node.attribs?.["wx:for"];
3111
3175
  if (!forExpression) return [{
@@ -3175,51 +3239,14 @@ function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, inst
3175
3239
  if (componentEntry) {
3176
3240
  const componentScopeId = `${instancePath}/${clonedNode.name}`;
3177
3241
  const ownerScopeId = scope.getScopeId().includes("/") ? scope.getScopeId() : void 0;
3178
- const nextProperties = {};
3179
- const bindingExpressions = {};
3180
- for (const [key, value] of Object.entries(clonedNode.attribs ?? {})) {
3181
- if (key.startsWith("bind")) continue;
3182
- if (isMustacheOnly(String(value))) bindingExpressions[key] = String(value).trim().slice(2, -2).trim();
3183
- nextProperties[key] = resolveComponentAttributeValue(String(value), scope);
3184
- }
3242
+ const { nextProperties, bindingExpressions } = resolveComponentProperties(clonedNode, scope);
3185
3243
  let componentInstance = context.componentCache.get(componentScopeId);
3186
- if (!componentInstance) {
3187
- componentInstance = createComponentInstance({
3188
- definition: componentEntry.definition,
3189
- properties: nextProperties,
3190
- triggerEvent: buildComponentTrigger(componentScopeId, context, clonedNode)
3191
- });
3192
- componentInstance.createIntersectionObserver = (options) => context.session.createIntersectionObserver(componentInstance, options);
3193
- componentInstance.createMediaQueryObserver = () => context.session.createMediaQueryObserver(componentInstance);
3194
- componentInstance.selectComponent = (selector) => context.session.selectComponentWithin(componentScopeId, selector);
3195
- componentInstance.selectAllComponents = (selector) => context.session.selectAllComponentsWithin(componentScopeId, selector);
3196
- componentInstance.selectOwnerComponent = () => ownerScopeId ? context.session.selectOwnerComponent(componentScopeId) : null;
3197
- runComponentLifecycle(componentInstance, "created");
3198
- runComponentObservers(componentInstance.__definition__ ?? componentEntry.definition, componentInstance, Object.keys(nextProperties), {});
3199
- componentInstance.__propertySnapshots = Object.fromEntries(Object.entries(componentInstance.properties).map(([key, propertyValue]) => [key, cloneValue$1(propertyValue)]));
3200
- runComponentLifecycle(componentInstance, "attached");
3201
- context.componentCache.set(componentScopeId, componentInstance);
3202
- } else syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, nextProperties, bindingExpressions, context.changedPageKeys);
3244
+ if (!componentInstance) componentInstance = createRuntimeComponentInstance(componentScopeId, context, clonedNode, componentEntry, nextProperties, ownerScopeId);
3245
+ else syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, nextProperties, bindingExpressions, context.changedPageKeys);
3203
3246
  seenComponentScopes.add(componentScopeId);
3204
- const componentScope = {
3205
- alias: clonedNode.name,
3206
- classList: String(clonedNode.attribs?.class ?? "").split(CLASS_SPLIT_RE).map((item) => item.trim()).filter(Boolean),
3207
- data: createMergedScopeData(scope.data, componentInstance.properties, componentInstance.data),
3208
- dataset: collectDataset$3(clonedNode),
3209
- eventBindings: collectComponentEventBindings(clonedNode),
3210
- getMethod: (methodName) => {
3211
- const method = componentInstance?.[methodName];
3212
- return typeof method === "function" ? method : void 0;
3213
- },
3214
- getScopeId: () => componentScopeId,
3215
- hostId: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
3216
- id: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
3217
- listenerScopeId: scope.getScopeId(),
3218
- ownerScopeId
3219
- };
3247
+ const componentScope = createComponentScope(clonedNode, scope, componentScopeId, componentInstance);
3220
3248
  context.componentScopes.set(componentScopeId, componentScope);
3221
- const componentDocument = parseTemplateDocument(readTemplateSource(componentEntry.absoluteTemplatePath));
3222
- const renderedComponentRoot = renderNodeTree((componentDocument.children ?? [])[0] ?? componentDocument, componentScope, context, path.resolve(context.project.miniprogramRootPath, `${componentEntry.filePath.replace(JS_FILE_RE, "")}.json`), componentEntry.filePath, componentScopeId, seenComponentScopes);
3249
+ const renderedComponentRoot = renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, seenComponentScopes);
3223
3250
  if (renderedComponentRoot.attribs) renderedComponentRoot.attribs["data-sim-component"] = clonedNode.name;
3224
3251
  if (!componentInstance.__ready__) {
3225
3252
  runComponentLifecycle(componentInstance, "ready");
@@ -3291,7 +3318,7 @@ function registerComponentDefinition(registries, definition) {
3291
3318
  return definition;
3292
3319
  }
3293
3320
  //#endregion
3294
- //#region ../../mpcore/packages/simulator/src/host/wx.ts
3321
+ //#region ../../mpcore/packages/simulator/src/host/wx/index.ts
3295
3322
  function invokeWxApi(operation, option) {
3296
3323
  try {
3297
3324
  const result = operation();
@@ -3534,10 +3561,7 @@ function createHeadlessWx(driver) {
3534
3561
  chooseMedia: (option) => invokeWxApi(() => driver.chooseMedia(option ?? {}), option),
3535
3562
  chooseVideo: (option) => invokeWxApi(() => driver.chooseVideo(option ?? {}), option),
3536
3563
  compressImage: (option) => invokeWxApi(() => driver.compressImage(option), option),
3537
- clearStorage: (option) => invokeWxApi(() => {
3538
- driver.clearStorageSync();
3539
- return { errMsg: "clearStorage:ok" };
3540
- }, option),
3564
+ clearStorage: (option) => invokeWxApi(() => ({ errMsg: (driver.clearStorageSync(), "clearStorage:ok") }), option),
3541
3565
  clearStorageSync: () => driver.clearStorageSync(),
3542
3566
  createAnimation: (option) => driver.createAnimation(option),
3543
3567
  createCanvasContext: (canvasId, component) => driver.createCanvasContext(canvasId, component),
@@ -5515,10 +5539,12 @@ function inferImageType(filePath, fileContent) {
5515
5539
  }
5516
5540
  function inferImageSize(fileContent) {
5517
5541
  try {
5518
- const payload = JSON.parse(fileContent);
5542
+ const config = JSON.parse(fileContent)?.config;
5543
+ const destHeight = config && "destHeight" in config ? config.destHeight : void 0;
5544
+ const destWidth = config && "destWidth" in config ? config.destWidth : void 0;
5519
5545
  return {
5520
- height: payload?.config?.destHeight ?? payload?.config?.height ?? 0,
5521
- width: payload?.config?.destWidth ?? payload?.config?.width ?? 0
5546
+ height: destHeight ?? config?.height ?? 0,
5547
+ width: destWidth ?? config?.width ?? 0
5522
5548
  };
5523
5549
  } catch {
5524
5550
  return {
@@ -6299,7 +6325,7 @@ function createHeadlessWxState() {
6299
6325
  width
6300
6326
  };
6301
6327
  }),
6302
- type: mediaTypes.length > 1 ? "mix" : mediaTypes[0]
6328
+ type: mediaTypes.length > 1 ? "mix" : mediaTypes[0] ?? "image"
6303
6329
  };
6304
6330
  },
6305
6331
  chooseVideo(option) {
@@ -7826,7 +7852,7 @@ var HeadlessTestingPageHandle = class {
7826
7852
  }
7827
7853
  };
7828
7854
  //#endregion
7829
- //#region ../../mpcore/packages/simulator/src/testing/sessionHandle.ts
7855
+ //#region ../../mpcore/packages/simulator/src/testing/sessionHandle/shared.ts
7830
7856
  const LEADING_SLASH_RE = /^\/+/;
7831
7857
  const DEFAULT_WAIT_TIMEOUT = 1e3;
7832
7858
  const DEFAULT_WAIT_INTERVAL = 10;
@@ -7837,6 +7863,28 @@ function resolvePageScopeId(scopeId) {
7837
7863
  const pagePathIndex = scopeId.indexOf("/page/");
7838
7864
  return pagePathIndex >= 0 ? scopeId.slice(0, pagePathIndex) : scopeId;
7839
7865
  }
7866
+ function normalizeNonEmptyInput(value, label) {
7867
+ const normalizedValue = value.trim();
7868
+ if (!normalizedValue) throw new Error(`${label} must be a non-empty string in headless testing runtime.`);
7869
+ return normalizedValue;
7870
+ }
7871
+ async function waitForDelay(ms = 0) {
7872
+ if (ms <= 0) return;
7873
+ await new Promise((resolve) => setTimeout(resolve, ms));
7874
+ }
7875
+ async function pollUntil(check, errorMessage, options = {}) {
7876
+ const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? DEFAULT_WAIT_TIMEOUT)) : DEFAULT_WAIT_TIMEOUT;
7877
+ const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? DEFAULT_WAIT_INTERVAL)) : DEFAULT_WAIT_INTERVAL;
7878
+ const deadline = Date.now() + timeout;
7879
+ while (true) {
7880
+ const result = await check();
7881
+ if (result != null) return result;
7882
+ if (Date.now() >= deadline) throw new Error(errorMessage);
7883
+ await waitForDelay(interval);
7884
+ }
7885
+ }
7886
+ //#endregion
7887
+ //#region ../../mpcore/packages/simulator/src/testing/sessionHandle/scope.ts
7840
7888
  var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
7841
7889
  constructor(scopeId, project, session) {
7842
7890
  this.scopeId = scopeId;
@@ -7848,8 +7896,7 @@ var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
7848
7896
  return scopeId ? new HeadlessTestingScopeHandle(scopeId, this.project, this.session) : null;
7849
7897
  }
7850
7898
  async callMethod(methodName, ...args) {
7851
- const normalizedMethodName = methodName.trim();
7852
- if (!normalizedMethodName) throw new Error("Method name must be a non-empty string in headless testing runtime.");
7899
+ const normalizedMethodName = normalizeNonEmptyInput(methodName, "Method name");
7853
7900
  return this.session.callScopeMethodDirect(this.scopeId, normalizedMethodName, ...args);
7854
7901
  }
7855
7902
  async snapshot() {
@@ -7866,75 +7913,37 @@ var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
7866
7913
  return this.createScopeHandle(this.session.selectOwnerComponent(this.scopeId));
7867
7914
  }
7868
7915
  async waitForOwnerComponent(options = {}) {
7869
- const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? 1e3)) : 1e3;
7870
- const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? 10)) : 10;
7871
- const deadline = Date.now() + timeout;
7872
- while (true) {
7873
- const owner = await this.ownerComponent();
7874
- if (owner) return owner;
7875
- if (Date.now() >= deadline) throw new Error("Timed out waiting for owner component in headless testing runtime.");
7876
- await new Promise((resolve) => setTimeout(resolve, interval));
7877
- }
7916
+ return await pollUntil(async () => await this.ownerComponent(), "Timed out waiting for owner component in headless testing runtime.", options);
7878
7917
  }
7879
7918
  async selectComponent(selector) {
7880
- const normalizedSelector = selector.trim();
7881
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7919
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7882
7920
  const component = this.session.selectComponentWithin(this.scopeId, normalizedSelector);
7883
7921
  return this.createScopeHandle(component);
7884
7922
  }
7885
7923
  async selectAllComponents(selector) {
7886
- const normalizedSelector = selector.trim();
7887
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7924
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7888
7925
  return this.session.selectAllComponentsWithin(this.scopeId, normalizedSelector).map((component) => this.createScopeHandle(component)).filter((handle) => Boolean(handle));
7889
7926
  }
7890
7927
  async waitForComponent(selector, options = {}) {
7891
- const normalizedSelector = selector.trim();
7892
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7893
- const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? 1e3)) : 1e3;
7894
- const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? 10)) : 10;
7895
- const deadline = Date.now() + timeout;
7896
- while (true) {
7897
- const component = await this.selectComponent(normalizedSelector);
7898
- if (component) return component;
7899
- if (Date.now() >= deadline) throw new Error(`Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`);
7900
- await new Promise((resolve) => setTimeout(resolve, interval));
7901
- }
7928
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7929
+ return await pollUntil(async () => await this.selectComponent(normalizedSelector), `Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`, options);
7902
7930
  }
7903
7931
  async waitForComponents(selector, count = 1, options = {}) {
7904
- const normalizedSelector = selector.trim();
7905
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7932
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7906
7933
  const normalizedCount = Number.isFinite(count) ? Math.max(1, Math.trunc(count)) : 1;
7907
- const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? 1e3)) : 1e3;
7908
- const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? 10)) : 10;
7909
- const deadline = Date.now() + timeout;
7910
- while (true) {
7934
+ return await pollUntil(async () => {
7911
7935
  const components = await this.selectAllComponents(normalizedSelector);
7912
- if (components.length >= normalizedCount) return components;
7913
- if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${normalizedCount} component(s) matching "${normalizedSelector}" in headless testing runtime.`);
7914
- await new Promise((resolve) => setTimeout(resolve, interval));
7915
- }
7936
+ return components.length >= normalizedCount ? components : null;
7937
+ }, `Timed out waiting for ${normalizedCount} component(s) matching "${normalizedSelector}" in headless testing runtime.`, options);
7916
7938
  }
7917
7939
  };
7940
+ //#endregion
7941
+ //#region ../../mpcore/packages/simulator/src/testing/sessionHandle/index.ts
7918
7942
  var HeadlessTestingSessionHandle = class {
7919
7943
  constructor(project, session) {
7920
7944
  this.project = project;
7921
7945
  this.session = session;
7922
7946
  }
7923
- async waitFor(ms = 0) {
7924
- if (ms <= 0) return;
7925
- await new Promise((resolve) => setTimeout(resolve, ms));
7926
- }
7927
- async pollUntil(check, errorMessage, options = {}) {
7928
- const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? DEFAULT_WAIT_TIMEOUT)) : DEFAULT_WAIT_TIMEOUT;
7929
- const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? DEFAULT_WAIT_INTERVAL)) : DEFAULT_WAIT_INTERVAL;
7930
- const deadline = Date.now() + timeout;
7931
- while (true) {
7932
- const result = await check();
7933
- if (result != null) return result;
7934
- if (Date.now() >= deadline) throw new Error(errorMessage);
7935
- await this.waitFor(interval);
7936
- }
7937
- }
7938
7947
  async close() {}
7939
7948
  async currentPage() {
7940
7949
  const current = this.session.getCurrentPages().at(-1);
@@ -7944,32 +7953,27 @@ var HeadlessTestingSessionHandle = class {
7944
7953
  return this.session.getCurrentPages().map((page) => new HeadlessTestingPageHandle(this.project, page, this.session));
7945
7954
  }
7946
7955
  async scopeSnapshot(scopeId) {
7947
- const normalizedScopeId = scopeId.trim();
7948
- if (!normalizedScopeId) throw new Error("Scope id must be a non-empty string in headless testing runtime.");
7956
+ const normalizedScopeId = normalizeNonEmptyInput(scopeId, "Scope id");
7949
7957
  return this.session.getScopeSnapshot(normalizedScopeId);
7950
7958
  }
7951
7959
  async selectComponent(selector) {
7952
- const normalizedSelector = selector.trim();
7953
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7960
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7954
7961
  const component = this.session.selectComponent(normalizedSelector);
7955
7962
  const scopeId = this.session.getScopeIdForComponent(component);
7956
7963
  return scopeId ? new HeadlessTestingScopeHandle(scopeId, this.project, this.session) : null;
7957
7964
  }
7958
7965
  async selectAllComponents(selector) {
7959
- const normalizedSelector = selector.trim();
7960
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7966
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7961
7967
  return this.session.selectAllComponents(normalizedSelector).map((component) => this.session.getScopeIdForComponent(component)).filter((scopeId) => Boolean(scopeId)).map((scopeId) => new HeadlessTestingScopeHandle(scopeId, this.project, this.session));
7962
7968
  }
7963
7969
  async waitForComponent(selector, options = {}) {
7964
- const normalizedSelector = selector.trim();
7965
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7966
- return await this.pollUntil(async () => await this.selectComponent(normalizedSelector), `Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`, options);
7970
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7971
+ return await pollUntil(async () => await this.selectComponent(normalizedSelector), `Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`, options);
7967
7972
  }
7968
7973
  async waitForComponents(selector, count = 1, options = {}) {
7969
- const normalizedSelector = selector.trim();
7970
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7974
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7971
7975
  const normalizedCount = Number.isFinite(count) ? Math.max(1, Math.trunc(count)) : 1;
7972
- return await this.pollUntil(async () => {
7976
+ return await pollUntil(async () => {
7973
7977
  const components = await this.selectAllComponents(normalizedSelector);
7974
7978
  return components.length >= normalizedCount ? components : null;
7975
7979
  }, `Timed out waiting for ${normalizedCount} component(s) matching "${normalizedSelector}" in headless testing runtime.`, options);
@@ -7981,7 +7985,7 @@ var HeadlessTestingSessionHandle = class {
7981
7985
  async waitForCurrentPage(route, options = {}) {
7982
7986
  const normalizedRoute = route?.trim();
7983
7987
  if (normalizedRoute != null && !normalizedRoute) throw new Error("Route must be a non-empty string in headless testing runtime.");
7984
- return await this.pollUntil(async () => {
7988
+ return await pollUntil(async () => {
7985
7989
  const currentPageInstance = this.session.getCurrentPages().at(-1);
7986
7990
  if (!currentPageInstance) return null;
7987
7991
  const current = new HeadlessTestingPageHandle(this.project, currentPageInstance, this.session);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@weapp-vite/miniprogram-automator",
3
3
  "type": "module",
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "description": "完全兼容微信 miniprogram-automator 的现代化替代实现",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",