@weapp-vite/miniprogram-automator 1.2.5 → 1.2.6

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.mjs CHANGED
@@ -223,6 +223,8 @@ function toStr(value) {
223
223
  }
224
224
  //#endregion
225
225
  //#region src/Element.ts
226
+ const ELEMENT_QUERY_TIMEOUT = 2500;
227
+ const ELEMENT_WXML_TIMEOUT = 8e3;
226
228
  /** Element 的实现。 */
227
229
  var Element = class Element {
228
230
  connection;
@@ -242,9 +244,9 @@ var Element = class Element {
242
244
  this.videoId = options.videoId || null;
243
245
  this.tagName = options.tagName;
244
246
  }
245
- async $(selector) {
247
+ async $(selector, options = {}) {
246
248
  try {
247
- const element = await this.send("Element.getElement", { selector });
249
+ const element = await this.send("Element.getElement", { selector }, { timeout: options.timeout ?? ELEMENT_QUERY_TIMEOUT });
248
250
  return Element.create(this.connection, {
249
251
  ...element,
250
252
  pageId: this.pageId
@@ -253,8 +255,8 @@ var Element = class Element {
253
255
  return null;
254
256
  }
255
257
  }
256
- async $$(selector) {
257
- const { elements } = await this.send("Element.getElements", { selector });
258
+ async $$(selector, options = {}) {
259
+ const { elements } = await this.send("Element.getElements", { selector }, { timeout: options.timeout ?? ELEMENT_QUERY_TIMEOUT });
258
260
  return elements.map((element) => {
259
261
  return Element.create(this.connection, {
260
262
  ...element,
@@ -293,10 +295,10 @@ var Element = class Element {
293
295
  return await this._property(name);
294
296
  }
295
297
  async wxml() {
296
- return (await this.send("Element.getWXML", { type: "inner" })).wxml;
298
+ return (await this.send("Element.getWXML", { type: "inner" }, { timeout: ELEMENT_WXML_TIMEOUT })).wxml;
297
299
  }
298
300
  async outerWxml() {
299
- return (await this.send("Element.getWXML", { type: "outer" })).wxml;
301
+ return (await this.send("Element.getWXML", { type: "outer" }, { timeout: ELEMENT_WXML_TIMEOUT })).wxml;
300
302
  }
301
303
  async style(name) {
302
304
  if (!isStr(name)) throw new Error("name must be a string");
@@ -330,12 +332,12 @@ var Element = class Element {
330
332
  async _property(name) {
331
333
  return await this.getter(name, "getProperties", "properties");
332
334
  }
333
- async send(method, params = {}) {
335
+ async send(method, params = {}, options) {
334
336
  params.elementId = this.id;
335
337
  params.pageId = this.pageId;
336
338
  if (this.nodeId) params.nodeId = this.nodeId;
337
339
  if (this.videoId) params.videoId = this.videoId;
338
- return await this.connection.send(method, params);
340
+ return options ? await this.connection.send(method, params, options) : await this.connection.send(method, params);
339
341
  }
340
342
  async callFunction(functionName, ...args) {
341
343
  const { result } = await this.send("Element.callFunction", {
@@ -699,7 +701,7 @@ async function acquireAutomatorPortLease(preferredPort) {
699
701
  }
700
702
  //#endregion
701
703
  //#region package.json
702
- var version = "1.2.5";
704
+ var version = "1.2.6";
703
705
  //#endregion
704
706
  //#region src/Native.ts
705
707
  /** Native 的实现。 */
@@ -750,7 +752,70 @@ var Native = class extends EventEmitter {
750
752
  }
751
753
  };
752
754
  //#endregion
755
+ //#region src/pageRouteFallback.ts
756
+ function normalizeFallbackIdPart(value) {
757
+ return encodeURIComponent(value).replace(/%/g, "_");
758
+ }
759
+ function resolveFallbackTagName(selector) {
760
+ const tagSelector = selector.trim();
761
+ if (/^[a-z][\w-]*$/i.test(tagSelector)) return tagSelector.toLowerCase();
762
+ return "view";
763
+ }
764
+ /** 将 App-Service 查询到的渲染节点包装为 Element 兼容对象。 */
765
+ function createRouteFallbackElement(connection, elementMap, pageId, selector, node, index) {
766
+ const nodeId = typeof node.id === "string" && node.id ? normalizeFallbackIdPart(node.id) : String(index);
767
+ const elementId = `__weapp_vite_route_fallback_${pageId}_${normalizeFallbackIdPart(selector)}_${nodeId}_${index}`;
768
+ return Element.create(connection, {
769
+ elementId,
770
+ pageId,
771
+ tagName: resolveFallbackTagName(selector)
772
+ }, elementMap);
773
+ }
774
+ //#endregion
753
775
  //#region src/Page.ts
776
+ const PAGE_QUERY_TIMEOUT = 2500;
777
+ const PAGE_DATA_TIMEOUT = 12e3;
778
+ const PAGE_DATA_PROTOCOL_TIMEOUT = 2500;
779
+ const PAGE_SET_DATA_TIMEOUT = 12e3;
780
+ const PAGE_SET_DATA_PROTOCOL_TIMEOUT = 2500;
781
+ const PAGE_CALL_METHOD_TIMEOUT = 12e3;
782
+ const PAGE_CALL_METHOD_PROTOCOL_TIMEOUT = 2500;
783
+ const PAGE_CALL_METHOD_FALLBACK_RETRIES = 3;
784
+ const PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY = 300;
785
+ const PAGE_RENDERED_TIMEOUT = 15e3;
786
+ const PAGE_RENDERED_POLL_DELAY = 220;
787
+ const PAGE_RENDERED_QUERY_TIMEOUT = 5e3;
788
+ const PAGE_ROOT_SELECTORS = [
789
+ "page",
790
+ "body",
791
+ "weapp-app-shell",
792
+ "view"
793
+ ];
794
+ function isProtocolTimeoutError(error, method) {
795
+ return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === method;
796
+ }
797
+ function isPageStackStaleError(error) {
798
+ return (error instanceof Error ? error.message : String(error)).includes("page is not on top of page stack");
799
+ }
800
+ function isPageMetaMissingError$1(error) {
801
+ const message = error instanceof Error ? error.message : String(error);
802
+ return message.includes("getPageMetaByWebviewId") && message.includes("rawPath") && message.includes("is null");
803
+ }
804
+ function isCurrentFrameTimedOutError$1(error) {
805
+ const message = error instanceof Error ? error.message : String(error);
806
+ return message.includes("unexpected current frame status timedout") || message.includes("[loader] unexpected current frame status timedout");
807
+ }
808
+ function isRecoverablePageProtocolError(error, method) {
809
+ return isProtocolTimeoutError(error, method) || isPageMetaMissingError$1(error) || isCurrentFrameTimedOutError$1(error);
810
+ }
811
+ function normalizeRoute(value) {
812
+ return String(value || "").replace(/^\/+/, "").replace(/\/+$/g, "");
813
+ }
814
+ function matchesDataset(node, expected) {
815
+ if (!expected) return true;
816
+ const dataset = node.dataset ?? {};
817
+ return Object.entries(expected).every(([key, value]) => String(dataset[key] ?? "") === String(value));
818
+ }
754
819
  /** Page 的实现。 */
755
820
  var Page = class Page {
756
821
  connection;
@@ -758,12 +823,17 @@ var Page = class Page {
758
823
  query = {};
759
824
  id;
760
825
  elementMap = /* @__PURE__ */ new Map();
826
+ preferRoutePageFallback = false;
761
827
  constructor(connection, options) {
762
828
  this.connection = connection;
763
829
  this.id = options.id;
764
830
  this.path = options.path;
765
831
  this.query = options.query;
766
832
  }
833
+ updateFromOptions(options) {
834
+ this.path = options.path;
835
+ this.query = options.query;
836
+ }
767
837
  async waitFor(condition) {
768
838
  if (isNum(condition)) {
769
839
  await sleep$1(condition);
@@ -775,29 +845,41 @@ var Page = class Page {
775
845
  }
776
846
  if (isStr(condition)) await waitUntil(async () => (await this.$$(condition)).length > 0);
777
847
  }
778
- async $(selector) {
848
+ async $(selector, options = {}) {
849
+ if (options.routeOnly || this.preferRoutePageFallback) return await this.queryRouteElement(selector, options);
779
850
  try {
780
- const element = await this.send("Page.getElement", { selector });
851
+ const element = await this.send("Page.getElement", { selector }, { timeout: options.timeout ?? PAGE_QUERY_TIMEOUT });
781
852
  return Element.create(this.connection, {
782
853
  ...element,
783
854
  pageId: this.id
784
855
  }, this.elementMap);
785
- } catch {
856
+ } catch (error) {
857
+ if (options.fallback !== false && isRecoverablePageProtocolError(error, "Page.getElement")) {
858
+ this.preferRoutePageFallback = true;
859
+ return await this.queryRouteElement(selector, options);
860
+ }
786
861
  return null;
787
862
  }
788
863
  }
789
- async $$(selector) {
790
- const { elements } = await this.send("Page.getElements", { selector });
791
- return elements.map((element) => {
792
- return Element.create(this.connection, {
793
- ...element,
794
- pageId: this.id
795
- }, this.elementMap);
796
- });
864
+ async $$(selector, options = {}) {
865
+ if (options.routeOnly || this.preferRoutePageFallback) return await this.queryRouteElements(selector, options);
866
+ try {
867
+ const { elements } = await this.send("Page.getElements", { selector }, { timeout: options.timeout ?? PAGE_QUERY_TIMEOUT });
868
+ return elements.map((element) => {
869
+ return Element.create(this.connection, {
870
+ ...element,
871
+ pageId: this.id
872
+ }, this.elementMap);
873
+ });
874
+ } catch (error) {
875
+ if (options.fallback === false || !isRecoverablePageProtocolError(error, "Page.getElements")) throw error;
876
+ this.preferRoutePageFallback = true;
877
+ return await this.queryRouteElements(selector, options);
878
+ }
797
879
  }
798
- async getElementByXpath(selector) {
880
+ async getElementByXpath(selector, options = {}) {
799
881
  try {
800
- const element = await this.send("Page.getElementByXpath", { selector });
882
+ const element = await this.send("Page.getElementByXpath", { selector }, { timeout: options.timeout ?? PAGE_QUERY_TIMEOUT });
801
883
  return Element.create(this.connection, {
802
884
  ...element,
803
885
  pageId: this.id
@@ -806,8 +888,8 @@ var Page = class Page {
806
888
  return null;
807
889
  }
808
890
  }
809
- async getElementsByXpath(selector) {
810
- const { elements } = await this.send("Page.getElementsByXpath", { selector });
891
+ async getElementsByXpath(selector, options = {}) {
892
+ const { elements } = await this.send("Page.getElementsByXpath", { selector }, { timeout: options.timeout ?? PAGE_QUERY_TIMEOUT });
811
893
  return elements.map((element) => {
812
894
  return Element.create(this.connection, {
813
895
  ...element,
@@ -818,13 +900,542 @@ var Page = class Page {
818
900
  async xpath(selector) {
819
901
  return await this.getElementByXpath(selector);
820
902
  }
821
- async data(path) {
903
+ async wxml() {
904
+ let lastError;
905
+ for (const selector of PAGE_ROOT_SELECTORS) try {
906
+ const element = await this.$(selector, { timeout: PAGE_QUERY_TIMEOUT });
907
+ if (!element) continue;
908
+ return await element.wxml();
909
+ } catch (error) {
910
+ lastError = error;
911
+ }
912
+ throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Failed to find page element");
913
+ }
914
+ async renderedNodes(selector, options = {}) {
915
+ const { result } = await this.connection.send("App.callFunction", {
916
+ functionDeclaration: `function (route, query, selector, scopeSelectors) {
917
+ function normalizeRoute(value) {
918
+ return String(value || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
919
+ }
920
+ function matchesQuery(page, expectedQuery) {
921
+ if (!expectedQuery || !Object.keys(expectedQuery).length) {
922
+ return true;
923
+ }
924
+ var actualQuery = page && (page.options || page.query || {});
925
+ return Object.keys(expectedQuery).every(function (key) {
926
+ var actualValue = String(actualQuery[key] == null ? '' : actualQuery[key]);
927
+ var expectedValue = String(expectedQuery[key]);
928
+ var decodedActualValue = actualValue;
929
+ try {
930
+ decodedActualValue = decodeURIComponent(actualValue);
931
+ }
932
+ catch (_) {
933
+ }
934
+ return actualValue === expectedValue
935
+ || decodedActualValue === expectedValue
936
+ || actualValue === encodeURIComponent(expectedValue);
937
+ });
938
+ }
939
+ var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
940
+ var expectedRoute = normalizeRoute(route);
941
+ var page = null;
942
+ for (var index = pages.length - 1; index >= 0; index -= 1) {
943
+ var candidate = pages[index];
944
+ var candidateRoute = normalizeRoute(candidate && (candidate.path || candidate.route || candidate.__route__));
945
+ if ((!expectedRoute || candidateRoute === expectedRoute) && matchesQuery(candidate, query)) {
946
+ page = candidate;
947
+ break;
948
+ }
949
+ }
950
+ if (!page) {
951
+ return [];
952
+ }
953
+ function createSelectorQuery(scope) {
954
+ var query = null;
955
+ var createdFromScope = false;
956
+ if (scope && typeof scope.createSelectorQuery === 'function') {
957
+ query = scope.createSelectorQuery();
958
+ createdFromScope = true;
959
+ }
960
+ else if (typeof wx !== 'undefined' && wx && typeof wx.createSelectorQuery === 'function') {
961
+ query = wx.createSelectorQuery();
962
+ }
963
+ else if (typeof page.createSelectorQuery === 'function') {
964
+ query = page.createSelectorQuery();
965
+ }
966
+ if (!query) {
967
+ return null;
968
+ }
969
+ if (!createdFromScope && typeof query.in === 'function') {
970
+ try {
971
+ query = query.in(scope || page);
972
+ }
973
+ catch (_) {
974
+ }
975
+ }
976
+ return query;
977
+ }
978
+ function pushUnique(list, seen, item) {
979
+ if (!item) {
980
+ return;
981
+ }
982
+ var id = item.is || item.id || item.__wxWebviewId__ || item.__wxExparserNodeId__ || String(list.length);
983
+ if (seen[id]) {
984
+ return;
985
+ }
986
+ seen[id] = true;
987
+ list.push(item);
988
+ }
989
+ function collectScopes(root) {
990
+ var scopes = [];
991
+ var seen = {};
992
+ var queue = [];
993
+ pushUnique(scopes, seen, root);
994
+ queue.push(root);
995
+ for (var queueIndex = 0; queueIndex < queue.length && queueIndex < 60; queueIndex += 1) {
996
+ var scope = queue[queueIndex];
997
+ if (!scope || typeof scope.selectAllComponents !== 'function') {
998
+ continue;
999
+ }
1000
+ var children = [];
1001
+ var componentSelectors = Array.isArray(scopeSelectors) ? scopeSelectors.slice() : [];
1002
+ componentSelectors.push(selector, '*', 'weapp-app-shell', 'weapp-layout-default');
1003
+ for (var selectorIndex = 0; selectorIndex < componentSelectors.length; selectorIndex += 1) {
1004
+ if (!componentSelectors[selectorIndex]) {
1005
+ continue;
1006
+ }
1007
+ try {
1008
+ var selected = scope.selectAllComponents(componentSelectors[selectorIndex]);
1009
+ if (Array.isArray(selected)) {
1010
+ children = children.concat(selected);
1011
+ }
1012
+ }
1013
+ catch (_) {
1014
+ }
1015
+ }
1016
+ for (var childIndex = 0; childIndex < children.length; childIndex += 1) {
1017
+ var child = children[childIndex];
1018
+ var previousLength = scopes.length;
1019
+ pushUnique(scopes, seen, child);
1020
+ if (scopes.length > previousLength) {
1021
+ queue.push(child);
1022
+ }
1023
+ }
1024
+ }
1025
+ return scopes;
1026
+ }
1027
+ function queryScope(scope) {
1028
+ return new Promise(function (resolve) {
1029
+ var settled = false;
1030
+ var timer = setTimeout(function () {
1031
+ if (settled) {
1032
+ return;
1033
+ }
1034
+ settled = true;
1035
+ resolve([]);
1036
+ }, 800);
1037
+ function finish(nodes) {
1038
+ if (settled) {
1039
+ return;
1040
+ }
1041
+ settled = true;
1042
+ clearTimeout(timer);
1043
+ resolve(Array.isArray(nodes) ? nodes : nodes ? [nodes] : []);
1044
+ }
1045
+ try {
1046
+ var query = createSelectorQuery(scope);
1047
+ if (!query) {
1048
+ finish([]);
1049
+ return;
1050
+ }
1051
+ query
1052
+ .selectAll(selector)
1053
+ .fields({
1054
+ dataset: true,
1055
+ id: true,
1056
+ rect: true,
1057
+ size: true
1058
+ }, function (nodes) {
1059
+ finish(nodes);
1060
+ })
1061
+ .exec();
1062
+ }
1063
+ catch (_) {
1064
+ finish([]);
1065
+ }
1066
+ });
1067
+ }
1068
+ return new Promise(function (resolve) {
1069
+ try {
1070
+ var scopes = collectScopes(page);
1071
+ var collected = [];
1072
+ var index = 0;
1073
+ function next() {
1074
+ if (index >= scopes.length) {
1075
+ resolve(collected);
1076
+ return;
1077
+ }
1078
+ var scope = scopes[index];
1079
+ index += 1;
1080
+ queryScope(scope).then(function (nodes) {
1081
+ collected = collected.concat(nodes);
1082
+ next();
1083
+ }, function () {
1084
+ next();
1085
+ });
1086
+ }
1087
+ next();
1088
+ }
1089
+ catch (_) {
1090
+ resolve([]);
1091
+ }
1092
+ });
1093
+ }`,
1094
+ args: [
1095
+ normalizeRoute(this.path),
1096
+ this.query,
1097
+ selector,
1098
+ options.componentSelectors ?? []
1099
+ ]
1100
+ }, { timeout: options.timeout ?? PAGE_RENDERED_QUERY_TIMEOUT });
1101
+ return Array.isArray(result) ? result : [];
1102
+ }
1103
+ async renderedSelectorNodes(selectors, options = {}) {
1104
+ const normalizedSelectors = selectors.map((selector) => String(selector || "").trim()).filter(Boolean);
1105
+ if (normalizedSelectors.length === 0) return {};
1106
+ const { result } = await this.connection.send("App.callFunction", {
1107
+ functionDeclaration: `function (route, query, selectors, scopeSelectors) {
1108
+ function normalizeRoute(value) {
1109
+ return String(value || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
1110
+ }
1111
+ function matchesQuery(page, expectedQuery) {
1112
+ if (!expectedQuery || !Object.keys(expectedQuery).length) {
1113
+ return true;
1114
+ }
1115
+ var actualQuery = page && (page.options || page.query || {});
1116
+ return Object.keys(expectedQuery).every(function (key) {
1117
+ var actualValue = String(actualQuery[key] == null ? '' : actualQuery[key]);
1118
+ var expectedValue = String(expectedQuery[key]);
1119
+ var decodedActualValue = actualValue;
1120
+ try {
1121
+ decodedActualValue = decodeURIComponent(actualValue);
1122
+ }
1123
+ catch (_) {
1124
+ }
1125
+ return actualValue === expectedValue
1126
+ || decodedActualValue === expectedValue
1127
+ || actualValue === encodeURIComponent(expectedValue);
1128
+ });
1129
+ }
1130
+ function createSelectorQuery(scope, page) {
1131
+ var query = null;
1132
+ var createdFromScope = false;
1133
+ if (scope && typeof scope.createSelectorQuery === 'function') {
1134
+ query = scope.createSelectorQuery();
1135
+ createdFromScope = true;
1136
+ }
1137
+ else if (typeof wx !== 'undefined' && wx && typeof wx.createSelectorQuery === 'function') {
1138
+ query = wx.createSelectorQuery();
1139
+ }
1140
+ else if (page && typeof page.createSelectorQuery === 'function') {
1141
+ query = page.createSelectorQuery();
1142
+ }
1143
+ if (!query) {
1144
+ return null;
1145
+ }
1146
+ if (!createdFromScope && typeof query.in === 'function') {
1147
+ try {
1148
+ query = query.in(scope || page);
1149
+ }
1150
+ catch (_) {
1151
+ }
1152
+ }
1153
+ return query;
1154
+ }
1155
+ function pushUnique(list, seen, item) {
1156
+ if (!item) {
1157
+ return;
1158
+ }
1159
+ var id = item.is || item.id || item.__wxWebviewId__ || item.__wxExparserNodeId__ || String(list.length);
1160
+ if (seen[id]) {
1161
+ return;
1162
+ }
1163
+ seen[id] = true;
1164
+ list.push(item);
1165
+ }
1166
+ function collectScopes(root, selectorList) {
1167
+ var scopes = [];
1168
+ var seen = {};
1169
+ var queue = [];
1170
+ pushUnique(scopes, seen, root);
1171
+ queue.push(root);
1172
+ for (var queueIndex = 0; queueIndex < queue.length && queueIndex < 30; queueIndex += 1) {
1173
+ var scope = queue[queueIndex];
1174
+ if (!scope || typeof scope.selectAllComponents !== 'function') {
1175
+ continue;
1176
+ }
1177
+ var children = [];
1178
+ var componentSelectors = Array.isArray(scopeSelectors) ? scopeSelectors.slice() : [];
1179
+ for (var selectorIndex = 0; selectorIndex < selectorList.length; selectorIndex += 1) {
1180
+ componentSelectors.push(selectorList[selectorIndex]);
1181
+ }
1182
+ componentSelectors.push('*', 'weapp-app-shell', 'weapp-layout-default');
1183
+ for (var componentIndex = 0; componentIndex < componentSelectors.length; componentIndex += 1) {
1184
+ if (!componentSelectors[componentIndex]) {
1185
+ continue;
1186
+ }
1187
+ try {
1188
+ var selected = scope.selectAllComponents(componentSelectors[componentIndex]);
1189
+ if (Array.isArray(selected)) {
1190
+ children = children.concat(selected);
1191
+ }
1192
+ }
1193
+ catch (_) {
1194
+ }
1195
+ }
1196
+ for (var childIndex = 0; childIndex < children.length; childIndex += 1) {
1197
+ var child = children[childIndex];
1198
+ var previousLength = scopes.length;
1199
+ pushUnique(scopes, seen, child);
1200
+ if (scopes.length > previousLength) {
1201
+ queue.push(child);
1202
+ }
1203
+ }
1204
+ }
1205
+ return scopes;
1206
+ }
1207
+ function queryScope(scope, page, selector) {
1208
+ return new Promise(function (resolve) {
1209
+ var settled = false;
1210
+ var timer = setTimeout(function () {
1211
+ if (settled) {
1212
+ return;
1213
+ }
1214
+ settled = true;
1215
+ resolve([]);
1216
+ }, 450);
1217
+ function finish(nodes) {
1218
+ if (settled) {
1219
+ return;
1220
+ }
1221
+ settled = true;
1222
+ clearTimeout(timer);
1223
+ resolve(Array.isArray(nodes) ? nodes : nodes ? [nodes] : []);
1224
+ }
1225
+ try {
1226
+ var query = createSelectorQuery(scope, page);
1227
+ if (!query) {
1228
+ finish([]);
1229
+ return;
1230
+ }
1231
+ query
1232
+ .selectAll(selector)
1233
+ .fields({
1234
+ dataset: true,
1235
+ id: true,
1236
+ rect: true,
1237
+ size: true
1238
+ }, function (nodes) {
1239
+ finish(nodes);
1240
+ })
1241
+ .exec();
1242
+ }
1243
+ catch (_) {
1244
+ finish([]);
1245
+ }
1246
+ });
1247
+ }
1248
+ var selectorList = Array.isArray(selectors)
1249
+ ? selectors.map(function (selector) {
1250
+ return String(selector || '').trim();
1251
+ }).filter(Boolean)
1252
+ : [];
1253
+ if (!selectorList.length) {
1254
+ return {};
1255
+ }
1256
+ var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
1257
+ var expectedRoute = normalizeRoute(route);
1258
+ var page = null;
1259
+ for (var index = pages.length - 1; index >= 0; index -= 1) {
1260
+ var candidate = pages[index];
1261
+ var candidateRoute = normalizeRoute(candidate && (candidate.path || candidate.route || candidate.__route__));
1262
+ if ((!expectedRoute || candidateRoute === expectedRoute) && matchesQuery(candidate, query)) {
1263
+ page = candidate;
1264
+ break;
1265
+ }
1266
+ }
1267
+ if (!page) {
1268
+ return {};
1269
+ }
1270
+ return new Promise(function (resolve) {
1271
+ try {
1272
+ var result = {};
1273
+ var scopes = collectScopes(page, selectorList);
1274
+ var startedAt = Date.now();
1275
+ var maxQueries = 40;
1276
+ var queryCount = 0;
1277
+ var selectorIndex = 0;
1278
+ var scopeIndex = 0;
1279
+ function next() {
1280
+ if (selectorIndex >= selectorList.length || queryCount >= maxQueries || Date.now() - startedAt > 1800) {
1281
+ resolve(result);
1282
+ return;
1283
+ }
1284
+ var selector = selectorList[selectorIndex];
1285
+ if (scopeIndex >= scopes.length) {
1286
+ selectorIndex += 1;
1287
+ scopeIndex = 0;
1288
+ next();
1289
+ return;
1290
+ }
1291
+ var scope = scopes[scopeIndex];
1292
+ scopeIndex += 1;
1293
+ queryCount += 1;
1294
+ queryScope(scope, page, selector).then(function (nodes) {
1295
+ if (Array.isArray(nodes) && nodes.length > 0) {
1296
+ result[selector] = (result[selector] || []).concat(nodes);
1297
+ selectorIndex += 1;
1298
+ scopeIndex = 0;
1299
+ }
1300
+ next();
1301
+ }, function () {
1302
+ next();
1303
+ });
1304
+ }
1305
+ next();
1306
+ }
1307
+ catch (_) {
1308
+ resolve({});
1309
+ }
1310
+ });
1311
+ }`,
1312
+ args: [
1313
+ normalizeRoute(this.path),
1314
+ this.query,
1315
+ normalizedSelectors,
1316
+ options.componentSelectors ?? []
1317
+ ]
1318
+ }, { timeout: options.timeout ?? PAGE_RENDERED_QUERY_TIMEOUT });
1319
+ return result && typeof result === "object" && !Array.isArray(result) ? result : {};
1320
+ }
1321
+ async waitForRendered(options = {}) {
1322
+ const timeout = options.timeout ?? PAGE_RENDERED_TIMEOUT;
1323
+ const start = Date.now();
1324
+ let lastWxml = "";
1325
+ let lastRenderedNodes = [];
1326
+ let lastError;
1327
+ while (Date.now() - start <= timeout) {
1328
+ try {
1329
+ if (options.selector) {
1330
+ const nodes = await this.renderedNodes(options.selector, {
1331
+ componentSelectors: options.componentSelectors,
1332
+ timeout: Math.min(PAGE_RENDERED_QUERY_TIMEOUT, Math.max(1, timeout - (Date.now() - start)))
1333
+ });
1334
+ lastRenderedNodes = nodes;
1335
+ lastError = void 0;
1336
+ if (nodes.find((node) => matchesDataset(node, options.dataset))) return JSON.stringify({
1337
+ selector: options.selector,
1338
+ nodes
1339
+ });
1340
+ } else {
1341
+ const wxml = await this.wxml();
1342
+ lastWxml = wxml;
1343
+ lastError = void 0;
1344
+ const normalized = wxml.trim();
1345
+ if (options.predicate?.(wxml)) return wxml;
1346
+ if (options.text && normalized.includes(options.text)) return wxml;
1347
+ if (!options.text && !options.predicate && normalized && normalized !== "<text></text>") return wxml;
1348
+ }
1349
+ } catch (error) {
1350
+ lastError = error;
1351
+ }
1352
+ await sleep$1(PAGE_RENDERED_POLL_DELAY);
1353
+ }
1354
+ const reason = lastError instanceof Error ? lastError.message : String(lastError ?? "condition not met");
1355
+ const expected = options.selector ? ` selector=${options.selector} dataset=${JSON.stringify(options.dataset ?? {})}` : options.text ? ` text=${options.text}` : options.predicate ? " predicate=true" : " non-empty wxml";
1356
+ const latest = options.selector ? JSON.stringify(lastRenderedNodes).slice(0, 500) : lastWxml.slice(0, 500);
1357
+ throw new Error(`Timed out waiting page rendered:${expected}; reason=${reason}; latest=${latest}`);
1358
+ }
1359
+ async data(path, options = {}) {
822
1360
  const payload = {};
823
1361
  if (path) payload.path = path;
824
- return (await this.send("Page.getData", payload)).data;
1362
+ const timeout = options.timeout ?? PAGE_DATA_TIMEOUT;
1363
+ if (options.routeOnly || this.preferRoutePageFallback) return await this.readRouteData(path, timeout);
1364
+ try {
1365
+ return (await this.send("Page.getData", payload, { timeout: Math.min(timeout, PAGE_DATA_PROTOCOL_TIMEOUT) })).data;
1366
+ } catch (error) {
1367
+ if (options.fallback === false) throw error;
1368
+ if (!isRecoverablePageProtocolError(error, "Page.getData")) throw error;
1369
+ this.preferRoutePageFallback = true;
1370
+ return await this.readRouteData(path, timeout);
1371
+ }
1372
+ }
1373
+ async readRouteData(path, timeout) {
1374
+ return (await this.connection.send("App.callFunction", {
1375
+ functionDeclaration: `function (route, query, dataPath) {
1376
+ function normalizeRoute(value) {
1377
+ return String(value || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
1378
+ }
1379
+ function matchesQuery(page, expectedQuery) {
1380
+ if (!expectedQuery || !Object.keys(expectedQuery).length) {
1381
+ return true;
1382
+ }
1383
+ var actualQuery = page && (page.options || page.query || {});
1384
+ return Object.keys(expectedQuery).every(function (key) {
1385
+ var actualValue = String(actualQuery[key] == null ? '' : actualQuery[key]);
1386
+ var expectedValue = String(expectedQuery[key]);
1387
+ var decodedActualValue = actualValue;
1388
+ try {
1389
+ decodedActualValue = decodeURIComponent(actualValue);
1390
+ }
1391
+ catch (_) {
1392
+ }
1393
+ return actualValue === expectedValue
1394
+ || decodedActualValue === expectedValue
1395
+ || actualValue === encodeURIComponent(expectedValue);
1396
+ });
1397
+ }
1398
+ function readPath(data, path) {
1399
+ if (!path) {
1400
+ return data;
1401
+ }
1402
+ if (data && Object.prototype.hasOwnProperty.call(data, path)) {
1403
+ return data[path];
1404
+ }
1405
+ return String(path).replace(/\\[(\\d+)\\]/g, '.$1').split('.').filter(Boolean).reduce(function (current, segment) {
1406
+ return current == null ? undefined : current[segment];
1407
+ }, data);
1408
+ }
1409
+ var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
1410
+ var normalizedRoute = normalizeRoute(route);
1411
+ for (var index = pages.length - 1; index >= 0; index -= 1) {
1412
+ var page = pages[index];
1413
+ var pageRoute = normalizeRoute(page.path || page.route || page.__route__);
1414
+ if (pageRoute === normalizedRoute && matchesQuery(page, query)) {
1415
+ return readPath(page.data || {}, dataPath);
1416
+ }
1417
+ }
1418
+ return undefined;
1419
+ }`,
1420
+ args: [
1421
+ this.path,
1422
+ this.query,
1423
+ path
1424
+ ]
1425
+ }, { timeout })).result;
825
1426
  }
826
1427
  async setData(data) {
827
- await this.send("Page.setData", { data });
1428
+ if (this.preferRoutePageFallback) {
1429
+ await this.setRouteData(data);
1430
+ return;
1431
+ }
1432
+ try {
1433
+ await this.send("Page.setData", { data }, { timeout: PAGE_SET_DATA_PROTOCOL_TIMEOUT });
1434
+ } catch (error) {
1435
+ if (!isRecoverablePageProtocolError(error, "Page.setData") && !isPageStackStaleError(error)) throw error;
1436
+ this.preferRoutePageFallback = true;
1437
+ await this.setRouteData(data);
1438
+ }
828
1439
  }
829
1440
  async size() {
830
1441
  const [width, height] = await this.windowProperty(["document.documentElement.scrollWidth", "document.documentElement.scrollHeight"]);
@@ -834,10 +1445,146 @@ var Page = class Page {
834
1445
  };
835
1446
  }
836
1447
  async callMethod(method, ...args) {
837
- return (await this.send("Page.callMethod", {
838
- method,
839
- args
840
- })).result;
1448
+ return await this.callMethodWithOptions(method, {}, ...args);
1449
+ }
1450
+ async callMethodWithOptions(method, options = {}, ...args) {
1451
+ if (options.routeOnly || this.preferRoutePageFallback) return await this.callRouteMethod(method, args, options.timeout);
1452
+ try {
1453
+ return (await this.send("Page.callMethod", {
1454
+ method,
1455
+ args
1456
+ }, { timeout: options.timeout ?? PAGE_CALL_METHOD_PROTOCOL_TIMEOUT })).result;
1457
+ } catch (error) {
1458
+ if (options.fallback === false) throw error;
1459
+ if (!isRecoverablePageProtocolError(error, "Page.callMethod") && !isPageStackStaleError(error)) throw error;
1460
+ this.preferRoutePageFallback = true;
1461
+ }
1462
+ return await this.callRouteMethod(method, args, options.timeout);
1463
+ }
1464
+ async queryRouteElement(selector, options) {
1465
+ return (await this.queryRouteElements(selector, options))[0] ?? null;
1466
+ }
1467
+ async queryRouteElements(selector, options) {
1468
+ return (await this.renderedNodes(selector, {
1469
+ componentSelectors: options.componentSelectors,
1470
+ timeout: options.timeout ?? PAGE_QUERY_TIMEOUT
1471
+ })).map((node, index) => createRouteFallbackElement(this.connection, this.elementMap, this.id, selector, node, index));
1472
+ }
1473
+ async callRouteMethod(method, args, timeout = PAGE_CALL_METHOD_TIMEOUT) {
1474
+ let latestResult;
1475
+ for (let attempt = 1; attempt <= PAGE_CALL_METHOD_FALLBACK_RETRIES; attempt += 1) {
1476
+ try {
1477
+ const fallbackResult = (await this.connection.send("App.callFunction", {
1478
+ functionDeclaration: `async function (route, query, method, args) {
1479
+ var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
1480
+ var normalizedRoute = String(route || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
1481
+ function methodResult(value) {
1482
+ return {
1483
+ __weappVitePageMethodFound: true,
1484
+ value: value
1485
+ };
1486
+ }
1487
+ function matchesQuery(page, expectedQuery) {
1488
+ if (!expectedQuery || !Object.keys(expectedQuery).length) {
1489
+ return true;
1490
+ }
1491
+ var actualQuery = page && (page.options || page.query || {});
1492
+ return Object.keys(expectedQuery).every(function (key) {
1493
+ var actualValue = String(actualQuery[key] == null ? '' : actualQuery[key]);
1494
+ var expectedValue = String(expectedQuery[key]);
1495
+ var decodedActualValue = actualValue;
1496
+ try {
1497
+ decodedActualValue = decodeURIComponent(actualValue);
1498
+ }
1499
+ catch (_) {
1500
+ }
1501
+ return actualValue === expectedValue
1502
+ || decodedActualValue === expectedValue
1503
+ || actualValue === encodeURIComponent(expectedValue);
1504
+ });
1505
+ }
1506
+ for (var index = pages.length - 1; index >= 0; index -= 1) {
1507
+ var page = pages[index];
1508
+ var pageRoute = String(page.path || page.route || page.__route__ || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
1509
+ if (pageRoute === normalizedRoute && matchesQuery(page, query) && typeof page[method] === 'function') {
1510
+ return methodResult(await page[method].apply(page, args || []));
1511
+ }
1512
+ }
1513
+ for (var fallbackIndex = pages.length - 1; fallbackIndex >= 0; fallbackIndex -= 1) {
1514
+ var fallbackPage = pages[fallbackIndex];
1515
+ if (matchesQuery(fallbackPage, query) && typeof fallbackPage[method] === 'function') {
1516
+ return methodResult(await fallbackPage[method].apply(fallbackPage, args || []));
1517
+ }
1518
+ }
1519
+ return {
1520
+ __weappVitePageMethodFound: false
1521
+ };
1522
+ }`,
1523
+ args: [
1524
+ this.path,
1525
+ this.query,
1526
+ method,
1527
+ args
1528
+ ]
1529
+ }, { timeout })).result;
1530
+ if (fallbackResult && typeof fallbackResult === "object" && fallbackResult.__weappVitePageMethodFound === true) return fallbackResult.value;
1531
+ latestResult = fallbackResult && typeof fallbackResult === "object" && fallbackResult.__weappVitePageMethodFound === false ? void 0 : fallbackResult;
1532
+ if (latestResult !== void 0 || attempt === PAGE_CALL_METHOD_FALLBACK_RETRIES) return latestResult;
1533
+ } catch (error) {
1534
+ if (!isProtocolTimeoutError(error, "App.callFunction") || attempt === PAGE_CALL_METHOD_FALLBACK_RETRIES) throw error;
1535
+ }
1536
+ await sleep$1(PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY);
1537
+ }
1538
+ return latestResult;
1539
+ }
1540
+ async setRouteData(data) {
1541
+ for (let attempt = 1; attempt <= PAGE_CALL_METHOD_FALLBACK_RETRIES; attempt += 1) {
1542
+ try {
1543
+ await this.connection.send("App.callFunction", {
1544
+ functionDeclaration: `function (route, query, data) {
1545
+ var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
1546
+ var normalizedRoute = String(route || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
1547
+ function matchesQuery(page, expectedQuery) {
1548
+ if (!expectedQuery || !Object.keys(expectedQuery).length) {
1549
+ return true;
1550
+ }
1551
+ var actualQuery = page && (page.options || page.query || {});
1552
+ return Object.keys(expectedQuery).every(function (key) {
1553
+ var actualValue = String(actualQuery[key] == null ? '' : actualQuery[key]);
1554
+ var expectedValue = String(expectedQuery[key]);
1555
+ var decodedActualValue = actualValue;
1556
+ try {
1557
+ decodedActualValue = decodeURIComponent(actualValue);
1558
+ }
1559
+ catch (_) {
1560
+ }
1561
+ return actualValue === expectedValue
1562
+ || decodedActualValue === expectedValue
1563
+ || actualValue === encodeURIComponent(expectedValue);
1564
+ });
1565
+ }
1566
+ for (var index = pages.length - 1; index >= 0; index -= 1) {
1567
+ var page = pages[index];
1568
+ var pageRoute = String(page.path || page.route || page.__route__ || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
1569
+ if (pageRoute === normalizedRoute && matchesQuery(page, query) && typeof page.setData === 'function') {
1570
+ page.setData(data || {});
1571
+ return true;
1572
+ }
1573
+ }
1574
+ return false;
1575
+ }`,
1576
+ args: [
1577
+ this.path,
1578
+ this.query,
1579
+ data
1580
+ ]
1581
+ }, { timeout: PAGE_SET_DATA_TIMEOUT });
1582
+ return;
1583
+ } catch (error) {
1584
+ if (!isProtocolTimeoutError(error, "App.callFunction") || attempt === PAGE_CALL_METHOD_FALLBACK_RETRIES) throw error;
1585
+ }
1586
+ await sleep$1(PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY);
1587
+ }
841
1588
  }
842
1589
  async scrollTop() {
843
1590
  const bodyScrollTop = await this.windowProperty("document.body.scrollTop");
@@ -849,13 +1596,16 @@ var Page = class Page {
849
1596
  const properties = (await this.send("Page.getWindowProperties", { names })).properties;
850
1597
  return isStr(name) ? properties[0] : properties;
851
1598
  }
852
- async send(method, params = {}) {
1599
+ async send(method, params = {}, options) {
853
1600
  params.pageId = this.id;
854
- return await this.connection.send(method, params);
1601
+ return options ? await this.connection.send(method, params, options) : await this.connection.send(method, params);
855
1602
  }
856
1603
  static create(connection, options, pageMap) {
857
1604
  const existing = pageMap.get(options.id);
858
- if (existing) return existing;
1605
+ if (existing) {
1606
+ existing.updateFromOptions(options);
1607
+ return existing;
1608
+ }
859
1609
  const page = new Page(connection, options);
860
1610
  pageMap.set(options.id, page);
861
1611
  return page;
@@ -866,7 +1616,7 @@ var Page = class Page {
866
1616
  /**
867
1617
  * @file 二维码与插件路径工具。
868
1618
  */
869
- const regPluginId = /^plugin-private:\/\/([0-9a-zA-Z]+)\//;
1619
+ const regPluginId = /^(?:plugin-private:\/\/|__plugin__\/)([0-9a-zA-Z]+)\//;
870
1620
  /** decodeQrCode 的方法封装。 */
871
1621
  function decodeQrCode(qrCode) {
872
1622
  return decodeQrCodeFromBase64(qrCode);
@@ -881,7 +1631,7 @@ function printQrCode(content) {
881
1631
  /** isPluginPath 的方法封装。 */
882
1632
  function isPluginPath(p) {
883
1633
  if (typeof p !== "string") return false;
884
- return startWith(p, "plugin-private://");
1634
+ return startWith(p, "plugin-private://") || startWith(p, "__plugin__/");
885
1635
  }
886
1636
  /** extractPluginId 的方法封装。 */
887
1637
  function extractPluginId(p) {
@@ -898,6 +1648,10 @@ const CHANGE_ROUTE_CONTEXT_TIMEOUT = 12e3;
898
1648
  const CHANGE_ROUTE_CALL_TIMEOUT = 12e3;
899
1649
  const CHANGE_ROUTE_READY_TIMEOUT = 15e3;
900
1650
  const CHANGE_ROUTE_POLL_DELAY = 500;
1651
+ const CHANGE_ROUTE_POLL_PROTOCOL_TIMEOUT = 2500;
1652
+ const CHANGE_ROUTE_APP_FUNCTION_TIMEOUT = 15e3;
1653
+ const CHANGE_ROUTE_APP_FUNCTION_RETRIES = 2;
1654
+ const CHANGE_ROUTE_APP_FUNCTION_RETRY_DELAY = 800;
901
1655
  const APP_READY_TIMEOUT = 2e4;
902
1656
  const APP_READY_PROBE_TIMEOUT = 3e3;
903
1657
  const APP_READY_POLL_DELAY = 500;
@@ -935,6 +1689,17 @@ function isCurrentPageProtocolTimeout(error) {
935
1689
  function isPageStackProtocolTimeout(error) {
936
1690
  return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.getPageStack";
937
1691
  }
1692
+ function isCallFunctionProtocolTimeout(error) {
1693
+ return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.callFunction";
1694
+ }
1695
+ function isPageMetaMissingError(error) {
1696
+ const message = error instanceof Error ? error.message : String(error);
1697
+ return message.includes("getPageMetaByWebviewId") && message.includes("rawPath") && message.includes("is null");
1698
+ }
1699
+ function isCurrentFrameTimedOutError(error) {
1700
+ const message = error instanceof Error ? error.message : String(error);
1701
+ return message.includes("unexpected current frame status timedout") || message.includes("[loader] unexpected current frame status timedout");
1702
+ }
938
1703
  function isCallWxMethodProtocolTimeout(error) {
939
1704
  return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.callWxMethod";
940
1705
  }
@@ -944,11 +1709,44 @@ function isCaptureScreenshotRecoverableError(error) {
944
1709
  return error.message.includes("fail to capture screenshot") || error.message.includes("App.captureScreenshot");
945
1710
  }
946
1711
  function isRouteContextProbeError(error) {
947
- return isCurrentPageProtocolTimeout(error) || isPageStackProtocolTimeout(error);
1712
+ return isCurrentPageProtocolTimeout(error) || isPageStackProtocolTimeout(error) || isPageMetaMissingError(error) || isCurrentFrameTimedOutError(error);
1713
+ }
1714
+ function isRoutePollingRecoverableError(error) {
1715
+ return isRouteContextProbeError(error) || isCallFunctionProtocolTimeout(error) || isPageStackProtocolTimeout(error) || isPageMetaMissingError(error);
1716
+ }
1717
+ function resolveAppFunctionTimeout(timeout) {
1718
+ return Math.max(timeout ?? CHANGE_ROUTE_APP_FUNCTION_TIMEOUT, CHANGE_ROUTE_APP_FUNCTION_TIMEOUT);
948
1719
  }
949
1720
  function normalizeRoutePath(value) {
950
1721
  return String(value ?? "").split("?", 1)[0].replace(/^\/+/, "").replace(/\/+$/, "");
951
1722
  }
1723
+ function isPluginNavigationUrl(value) {
1724
+ return String(value ?? "").startsWith("plugin://");
1725
+ }
1726
+ function matchesRouteChange(options) {
1727
+ const normalizedCurrentPath = normalizeRoutePath(options.currentPath);
1728
+ if (!options.expectedRoute || normalizedCurrentPath === options.expectedRoute) return true;
1729
+ if (!isPluginNavigationUrl(options.requestedUrl) || !isPluginPath(options.currentPath)) return false;
1730
+ return normalizedCurrentPath !== normalizeRoutePath(options.previousPath);
1731
+ }
1732
+ function parseRouteQuery(value) {
1733
+ const queryText = String(value ?? "").split("?", 2)[1] ?? "";
1734
+ const query = {};
1735
+ if (!queryText) return query;
1736
+ for (const part of queryText.split("&")) {
1737
+ if (!part) continue;
1738
+ const [rawKey, rawValue = ""] = part.split("=", 2);
1739
+ if (!rawKey) continue;
1740
+ query[decodeURIComponent(rawKey)] = decodeURIComponent(rawValue);
1741
+ }
1742
+ return query;
1743
+ }
1744
+ function resolvePagePayloadId(page, fallbackId) {
1745
+ return page.pageId ?? page.__wxWebviewId__ ?? page.__webviewId__ ?? fallbackId;
1746
+ }
1747
+ function resolvePagePayloadPath(page) {
1748
+ return page.path ?? page.route ?? page.__route__ ?? "";
1749
+ }
952
1750
  function logChangeRouteDebug(message) {
953
1751
  if (!CHANGE_ROUTE_DEBUG_ENABLED) return;
954
1752
  process.stdout.write(`[automator:changeRoute] ${message}\n`);
@@ -997,30 +1795,27 @@ var MiniProgram = class extends EventEmitter {
997
1795
  const retries = options.retries ?? CURRENT_PAGE_RETRIES;
998
1796
  const sendOptions = options.timeout ? { timeout: options.timeout } : void 0;
999
1797
  for (let attempt = 1; attempt <= retries; attempt += 1) try {
1000
- const { pageId, path, query } = await this.send("App.getCurrentPage", {}, sendOptions);
1001
- return Page.create(this.connection, {
1002
- id: pageId,
1003
- path,
1004
- query
1005
- }, this.pageMap);
1798
+ const page = await this.send("App.getCurrentPage", {}, sendOptions);
1799
+ return this.createPageFromPayload(page);
1006
1800
  } catch (error) {
1007
1801
  lastError = error;
1008
- if (!isCurrentPageProtocolTimeout(error)) throw error;
1802
+ if (!isCurrentPageProtocolTimeout(error) && !isPageMetaMissingError(error) && !isCurrentFrameTimedOutError(error)) throw error;
1009
1803
  if (attempt < retries) {
1010
1804
  await sleep(CURRENT_PAGE_RETRY_DELAY);
1011
1805
  continue;
1012
1806
  }
1013
1807
  }
1014
- if (isCurrentPageProtocolTimeout(lastError)) try {
1808
+ if (isCurrentPageProtocolTimeout(lastError) || isPageMetaMissingError(lastError) || isCurrentFrameTimedOutError(lastError)) try {
1015
1809
  const { pageStack } = await this.send("App.getPageStack", {}, sendOptions);
1016
1810
  const page = pageStack[pageStack.length - 1];
1017
- if (page) return Page.create(this.connection, {
1018
- id: page.pageId,
1019
- path: page.path,
1020
- query: page.query
1021
- }, this.pageMap);
1811
+ if (page) return this.createPageFromPayload(page, pageStack.length);
1022
1812
  } catch (error) {
1023
- if (!isPageStackProtocolTimeout(error)) throw error;
1813
+ if (!isPageStackProtocolTimeout(error) && !isPageMetaMissingError(error) && !isCurrentFrameTimedOutError(error)) throw error;
1814
+ }
1815
+ if ((options.appFunctionFallback ?? true) && (isCurrentPageProtocolTimeout(lastError) || isPageMetaMissingError(lastError) || isCurrentFrameTimedOutError(lastError))) {
1816
+ const pageStack = await this.readAppFunctionPageStack(resolveAppFunctionTimeout(sendOptions?.timeout));
1817
+ const page = pageStack[pageStack.length - 1];
1818
+ if (page) return page;
1024
1819
  }
1025
1820
  throw lastError;
1026
1821
  }
@@ -1033,6 +1828,13 @@ var MiniProgram = class extends EventEmitter {
1033
1828
  args
1034
1829
  })).result;
1035
1830
  }
1831
+ async callWxMethodWithOptions(method, options = {}, ...args) {
1832
+ const sendOptions = options.timeout ? { timeout: options.timeout } : void 0;
1833
+ return (await this.send("App.callWxMethod", {
1834
+ method,
1835
+ args
1836
+ }, sendOptions)).result;
1837
+ }
1036
1838
  async callWxMethodWithTimeout(method, timeout, ...args) {
1037
1839
  return (await this.send("App.callWxMethod", {
1038
1840
  method,
@@ -1086,10 +1888,14 @@ var MiniProgram = class extends EventEmitter {
1086
1888
  });
1087
1889
  }
1088
1890
  async evaluate(appFunction, ...args) {
1891
+ return await this.evaluateWithOptions(appFunction, {}, ...args);
1892
+ }
1893
+ async evaluateWithOptions(appFunction, options = {}, ...args) {
1894
+ const sendOptions = options.timeout ? { timeout: options.timeout } : void 0;
1089
1895
  const { result } = await this.send("App.callFunction", {
1090
1896
  functionDeclaration: appFunction.toString(),
1091
1897
  args
1092
- });
1898
+ }, sendOptions);
1093
1899
  return result;
1094
1900
  }
1095
1901
  async pageScrollTo(scrollTop) {
@@ -1219,14 +2025,19 @@ var MiniProgram = class extends EventEmitter {
1219
2025
  async changeRoute(method, url) {
1220
2026
  const currentPage = await this.resolveRouteContextPage();
1221
2027
  logChangeRouteDebug(`start method=${method} url=${url ?? "<none>"} current=${currentPage?.path ?? "<none>"}`);
2028
+ let routeCommandSent = false;
1222
2029
  try {
1223
2030
  if (currentPage && isPluginPath(currentPage.path)) await this.callPluginWxMethod(extractPluginId(currentPage.path), method, { url });
1224
2031
  else await this.callWxMethodWithTimeout(method, CHANGE_ROUTE_CALL_TIMEOUT, { url });
2032
+ routeCommandSent = true;
1225
2033
  } catch (error) {
1226
- if (isCallWxMethodProtocolTimeout(error) || isOperationTimeoutError(error, CHANGE_ROUTE_CALL_TIMEOUT)) logChangeRouteDebug(`call-timeout method=${method} url=${url ?? "<none>"}`);
1227
- else throw error;
2034
+ if (isCallWxMethodProtocolTimeout(error) || isOperationTimeoutError(error, CHANGE_ROUTE_CALL_TIMEOUT) || isPageMetaMissingError(error)) {
2035
+ routeCommandSent = true;
2036
+ logChangeRouteDebug(`call-timeout method=${method} url=${url ?? "<none>"}`);
2037
+ } else throw error;
1228
2038
  }
1229
2039
  const expectedRoute = normalizeRoutePath(url);
2040
+ const expectedRouteQuery = parseRouteQuery(url);
1230
2041
  const startedAt = Date.now();
1231
2042
  let lastPage;
1232
2043
  let lastError;
@@ -1235,7 +2046,12 @@ var MiniProgram = class extends EventEmitter {
1235
2046
  const page = await this.readRoutePollingCurrentPage();
1236
2047
  lastPage = page;
1237
2048
  logChangeRouteDebug(`poll method=${method} url=${url ?? "<none>"} current=${page?.path ?? "<none>"}`);
1238
- if (!expectedRoute || normalizeRoutePath(page?.path) === expectedRoute) {
2049
+ if (matchesRouteChange({
2050
+ currentPath: page?.path,
2051
+ previousPath: currentPage?.path,
2052
+ requestedUrl: url,
2053
+ expectedRoute
2054
+ })) {
1239
2055
  logChangeRouteDebug(`ready method=${method} url=${url ?? "<none>"} current=${page?.path ?? "<none>"}`);
1240
2056
  return page;
1241
2057
  }
@@ -1249,7 +2065,12 @@ var MiniProgram = class extends EventEmitter {
1249
2065
  if (stackTop) {
1250
2066
  lastPage = stackTop;
1251
2067
  logChangeRouteDebug(`stack method=${method} url=${url ?? "<none>"} current=${stackTop.path}`);
1252
- if (!expectedRoute || normalizeRoutePath(stackTop.path) === expectedRoute) {
2068
+ if (matchesRouteChange({
2069
+ currentPath: stackTop.path,
2070
+ previousPath: currentPage?.path,
2071
+ requestedUrl: url,
2072
+ expectedRoute
2073
+ })) {
1253
2074
  logChangeRouteDebug(`stack-ready method=${method} url=${url ?? "<none>"} current=${stackTop.path}`);
1254
2075
  return stackTop;
1255
2076
  }
@@ -1257,7 +2078,7 @@ var MiniProgram = class extends EventEmitter {
1257
2078
  } catch (error) {
1258
2079
  lastError = error;
1259
2080
  logChangeRouteDebug(`stack-error method=${method} url=${url ?? "<none>"} error=${error instanceof Error ? error.message : String(error)}`);
1260
- if (isOperationTimeoutError(error, CHANGE_ROUTE_CONTEXT_TIMEOUT)) {
2081
+ if (isOperationTimeoutError(error, CHANGE_ROUTE_CONTEXT_TIMEOUT) || isRoutePollingRecoverableError(error)) {
1261
2082
  await sleep(CHANGE_ROUTE_POLL_DELAY);
1262
2083
  continue;
1263
2084
  }
@@ -1269,11 +2090,23 @@ var MiniProgram = class extends EventEmitter {
1269
2090
  throw new Error(`Timed out waiting route ${expectedRoute || "<current>"} after ${method}; current page: ${lastPage.path}`);
1270
2091
  }
1271
2092
  logChangeRouteDebug(`timeout method=${method} url=${url ?? "<none>"} current=<none> error=${lastError instanceof Error ? lastError.message : String(lastError)}`);
2093
+ if (isRoutePollingRecoverableError(lastError)) {
2094
+ if (routeCommandSent && expectedRoute) return this.createPageFromPayload({
2095
+ pageId: 1,
2096
+ path: `/${expectedRoute}`,
2097
+ query: expectedRouteQuery
2098
+ });
2099
+ throw new Error(`Timed out waiting route ${expectedRoute || "<current>"} after ${method}; last recoverable error: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
2100
+ }
1272
2101
  throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`Timed out waiting route ${expectedRoute || "<current>"} after ${method}`);
1273
2102
  }
1274
2103
  async resolveRouteContextPage() {
1275
- const currentPageTask = this.currentPage().catch((error) => {
1276
- if (isRouteContextProbeError(error)) return;
2104
+ const currentPageTask = this.currentPage({
2105
+ appFunctionFallback: false,
2106
+ retries: 1,
2107
+ timeout: CHANGE_ROUTE_POLL_PROTOCOL_TIMEOUT
2108
+ }).catch((error) => {
2109
+ if (isRoutePollingRecoverableError(error)) return;
1277
2110
  throw error;
1278
2111
  });
1279
2112
  const timeoutTask = sleep(CHANGE_ROUTE_CONTEXT_TIMEOUT).then(() => void 0);
@@ -1284,22 +2117,51 @@ var MiniProgram = class extends EventEmitter {
1284
2117
  }
1285
2118
  }
1286
2119
  async readRoutePollingCurrentPage() {
1287
- const { pageId, path, query } = await this.send("App.getCurrentPage", {}, { timeout: CHANGE_ROUTE_CONTEXT_TIMEOUT });
2120
+ const page = await this.send("App.getCurrentPage", {}, { timeout: CHANGE_ROUTE_POLL_PROTOCOL_TIMEOUT });
2121
+ return this.createPageFromPayload(page);
2122
+ }
2123
+ async readRoutePollingPageStack() {
2124
+ try {
2125
+ const { pageStack } = await this.send("App.getPageStack", {}, { timeout: CHANGE_ROUTE_POLL_PROTOCOL_TIMEOUT });
2126
+ return this.createPagesFromPayloads(pageStack);
2127
+ } catch (error) {
2128
+ if (!isPageStackProtocolTimeout(error) && !isPageMetaMissingError(error)) throw error;
2129
+ throw error;
2130
+ }
2131
+ }
2132
+ createPageFromPayload(page, fallbackId = 1) {
1288
2133
  return Page.create(this.connection, {
1289
- id: pageId,
1290
- path,
1291
- query
2134
+ id: resolvePagePayloadId(page, fallbackId),
2135
+ path: resolvePagePayloadPath(page),
2136
+ query: page.query ?? page.options ?? {}
1292
2137
  }, this.pageMap);
1293
2138
  }
1294
- async readRoutePollingPageStack() {
1295
- const { pageStack } = await this.send("App.getPageStack", {}, { timeout: CHANGE_ROUTE_CONTEXT_TIMEOUT });
1296
- return pageStack.map((page) => {
1297
- return Page.create(this.connection, {
1298
- id: page.pageId,
1299
- path: page.path,
1300
- query: page.query
1301
- }, this.pageMap);
1302
- });
2139
+ createPagesFromPayloads(pageStack) {
2140
+ return pageStack.map((page, index) => this.createPageFromPayload(page, index + 1));
2141
+ }
2142
+ async readAppFunctionPageStack(timeout = CHANGE_ROUTE_APP_FUNCTION_TIMEOUT, retries = CHANGE_ROUTE_APP_FUNCTION_RETRIES) {
2143
+ let lastError;
2144
+ for (let attempt = 1; attempt <= retries; attempt += 1) try {
2145
+ const { result } = await this.send("App.callFunction", {
2146
+ functionDeclaration: `function () {
2147
+ var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
2148
+ return pages.map(function (page, index) {
2149
+ return {
2150
+ pageId: page.pageId || page.__wxWebviewId__ || page.__webviewId__ || index + 1,
2151
+ path: page.path || page.route || page.__route__ || '',
2152
+ query: page.query || page.options || {}
2153
+ };
2154
+ });
2155
+ }`,
2156
+ args: []
2157
+ }, { timeout });
2158
+ return this.createPagesFromPayloads(Array.isArray(result) ? result : []);
2159
+ } catch (error) {
2160
+ lastError = error;
2161
+ if (!isCallFunctionProtocolTimeout(error) || attempt >= retries) throw error;
2162
+ await sleep(CHANGE_ROUTE_APP_FUNCTION_RETRY_DELAY);
2163
+ }
2164
+ throw lastError;
1303
2165
  }
1304
2166
  async send(method, params = {}, options) {
1305
2167
  return options ? await this.connection.send(method, params, options) : await this.connection.send(method, params);
@@ -1697,7 +2559,8 @@ async function launch(options = {}) {
1697
2559
  }
1698
2560
  async function connect(options) {
1699
2561
  if (typeof options.wsEndpoint !== "string") throw new TypeError("wsEndpoint is required for smartapp runtime connection.");
1700
- return new SmartappDevice("devtools", DEFAULT_CONNECT_TYPE, "devtools-usb", { miniProgram: await connectMiniProgram(options.wsEndpoint) });
2562
+ const miniProgram = await connectMiniProgram(options.wsEndpoint);
2563
+ return new SmartappDevice("devtools", DEFAULT_CONNECT_TYPE, "devtools-usb", { miniProgram });
1701
2564
  }
1702
2565
  async function devices(deviceType, _connectType = DEFAULT_CONNECT_TYPE) {
1703
2566
  const resolvedDeviceType = resolveDeviceType(deviceType);