@weapp-vite/miniprogram-automator 1.2.8 → 1.2.9

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
@@ -93,6 +93,7 @@ interface IElementOptions {
93
93
  nodeId?: string;
94
94
  videoId?: string;
95
95
  pageId: number;
96
+ routeFallback?: boolean;
96
97
  tagName: string;
97
98
  }
98
99
  /** ITouch 的类型定义。 */
@@ -268,6 +269,8 @@ declare class Page {
268
269
  renderedNodes(selector: string, options?: PageQueryOptions): Promise<RenderedNodeSnapshot[]>;
269
270
  renderedSelectorNodes(selectors: string[], options?: PageQueryOptions): Promise<RenderedSelectorNodesSnapshot>;
270
271
  waitForRendered(options?: PageRenderedOptions): Promise<any>;
272
+ private readDirectRenderedNode;
273
+ private findDirectRenderedElement;
271
274
  data(path?: string, options?: PageDataOptions): Promise<any>;
272
275
  private readRouteData;
273
276
  setData(data: any): Promise<void>;
package/dist/index.mjs CHANGED
@@ -358,7 +358,8 @@ var Element = class Element {
358
358
  const existing = elementMap.get(options.elementId);
359
359
  if (existing) return existing;
360
360
  let ElementCtor = Element;
361
- if (options.nodeId) ElementCtor = CustomElement;
361
+ if (options.routeFallback) ElementCtor = RouteFallbackElement;
362
+ else if (options.nodeId) ElementCtor = CustomElement;
362
363
  else switch (options.tagName) {
363
364
  case "input":
364
365
  ElementCtor = InputElement;
@@ -391,6 +392,33 @@ var Element = class Element {
391
392
  return element;
392
393
  }
393
394
  };
395
+ /** App-Service 路由降级元素仅用于只读查询,不能伪装成交互协议元素。 */
396
+ var RouteFallbackElement = class extends Element {
397
+ unsupported(method) {
398
+ throw new Error(`App-Service route fallback 元素不支持 ${method}`);
399
+ }
400
+ async tap() {
401
+ this.unsupported("Element.tap");
402
+ }
403
+ async longpress() {
404
+ this.unsupported("Element.longpress");
405
+ }
406
+ async trigger() {
407
+ this.unsupported("Element.triggerEvent");
408
+ }
409
+ async touchstart() {
410
+ this.unsupported("Element.touchstart");
411
+ }
412
+ async touchmove() {
413
+ this.unsupported("Element.touchmove");
414
+ }
415
+ async touchend() {
416
+ this.unsupported("Element.touchend");
417
+ }
418
+ async dispatchEvent() {
419
+ this.unsupported("Element.dispatchEvent");
420
+ }
421
+ };
394
422
  /** CustomElement 的实现。 */
395
423
  var CustomElement = class extends Element {
396
424
  async setData(data) {
@@ -612,7 +640,7 @@ var Connection = class Connection extends EventEmitter {
612
640
  //#endregion
613
641
  //#region src/headless.ts
614
642
  async function launchHeadlessAutomator(options) {
615
- return await (await import("./launch-DpCZfswN.mjs")).launch({ projectPath: options.projectPath });
643
+ return await (await import("./launch-kobGwssZ.mjs")).launch({ projectPath: options.projectPath });
616
644
  }
617
645
  //#endregion
618
646
  //#region src/launcher/portLease.ts
@@ -711,7 +739,7 @@ async function acquireAutomatorPortLease(preferredPort) {
711
739
  }
712
740
  //#endregion
713
741
  //#region package.json
714
- var version = "1.2.8";
742
+ var version = "1.2.9";
715
743
  //#endregion
716
744
  //#region src/Native.ts
717
745
  /** Native 的实现。 */
@@ -778,10 +806,162 @@ function createRouteFallbackElement(connection, elementMap, pageId, selector, no
778
806
  return Element.create(connection, {
779
807
  elementId,
780
808
  pageId,
809
+ routeFallback: true,
781
810
  tagName: resolveFallbackTagName(selector)
782
811
  }, elementMap);
783
812
  }
784
813
  //#endregion
814
+ //#region src/pageRouteMethod.ts
815
+ const PAGE_CALL_METHOD_FALLBACK_RETRIES$1 = 3;
816
+ const PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY$1 = 300;
817
+ const ROUTE_METHOD_CALL_STORE = "__weappViteAutomatorMethodCalls__";
818
+ function isProtocolTimeoutError$1(error) {
819
+ return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.callFunction";
820
+ }
821
+ function isRouteMethodResult(value) {
822
+ return value !== null && typeof value === "object" && "__weappVitePageMethodFound" in value;
823
+ }
824
+ const ROUTE_METHOD_FUNCTION_DECLARATION = `function (route, query, method, args, callId, cleanupDelay) {
825
+ var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
826
+ var normalizedRoute = String(route || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
827
+ function matchesQuery(page, expectedQuery) {
828
+ if (!expectedQuery || !Object.keys(expectedQuery).length) {
829
+ return true;
830
+ }
831
+ var actualQuery = page && (page.options || page.query || {});
832
+ return Object.keys(expectedQuery).every(function (key) {
833
+ var actualValue = String(actualQuery[key] == null ? '' : actualQuery[key]);
834
+ var expectedValue = String(expectedQuery[key]);
835
+ var decodedActualValue = actualValue;
836
+ try {
837
+ decodedActualValue = decodeURIComponent(actualValue);
838
+ }
839
+ catch (_) {
840
+ }
841
+ return actualValue === expectedValue
842
+ || decodedActualValue === expectedValue
843
+ || actualValue === encodeURIComponent(expectedValue);
844
+ });
845
+ }
846
+ function resolvePage() {
847
+ for (var index = pages.length - 1; index >= 0; index -= 1) {
848
+ var page = pages[index];
849
+ var pageRoute = String(page.path || page.route || page.__route__ || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
850
+ if (pageRoute === normalizedRoute && matchesQuery(page, query) && typeof page[method] === 'function') {
851
+ return page;
852
+ }
853
+ }
854
+ for (var fallbackIndex = pages.length - 1; fallbackIndex >= 0; fallbackIndex -= 1) {
855
+ var fallbackPage = pages[fallbackIndex];
856
+ if (matchesQuery(fallbackPage, query) && typeof fallbackPage[method] === 'function') {
857
+ return fallbackPage;
858
+ }
859
+ }
860
+ }
861
+ var storeOwner;
862
+ if (typeof getApp === 'function') {
863
+ try {
864
+ storeOwner = getApp();
865
+ }
866
+ catch (_) {
867
+ }
868
+ }
869
+ var store = storeOwner && storeOwner[${JSON.stringify(ROUTE_METHOD_CALL_STORE)}];
870
+ if (store && store[callId]) {
871
+ return store[callId];
872
+ }
873
+ var targetPage = resolvePage();
874
+ if (!targetPage) {
875
+ return { __weappVitePageMethodFound: false };
876
+ }
877
+ if (!storeOwner || (typeof storeOwner !== 'object' && typeof storeOwner !== 'function')) {
878
+ storeOwner = targetPage;
879
+ }
880
+ store = storeOwner[${JSON.stringify(ROUTE_METHOD_CALL_STORE)}];
881
+ if (!store || typeof store !== 'object') {
882
+ store = {};
883
+ storeOwner[${JSON.stringify(ROUTE_METHOD_CALL_STORE)}] = store;
884
+ }
885
+ if (store[callId]) {
886
+ return store[callId];
887
+ }
888
+ var result = {
889
+ __weappVitePageMethodFound: true,
890
+ status: 'pending'
891
+ };
892
+ store[callId] = result;
893
+ setTimeout(function () {
894
+ delete store[callId];
895
+ }, cleanupDelay);
896
+ try {
897
+ var value = targetPage[method].apply(targetPage, args || []);
898
+ if (value && typeof value.then === 'function') {
899
+ value.then(function (resolvedValue) {
900
+ result.status = 'fulfilled';
901
+ result.value = resolvedValue;
902
+ }, function (error) {
903
+ result.status = 'rejected';
904
+ result.error = error && error.message ? String(error.message) : String(error);
905
+ });
906
+ }
907
+ else {
908
+ result.status = 'fulfilled';
909
+ result.value = value;
910
+ }
911
+ }
912
+ catch (error) {
913
+ result.status = 'rejected';
914
+ result.error = error && error.message ? String(error.message) : String(error);
915
+ }
916
+ return result;
917
+ }`;
918
+ /** 通过 AppService 调用页面方法,并兼容 DevTools 不等待 Promise 结果的行为。 */
919
+ async function callRouteMethodViaAppService(connection, route, query, method, args, timeout) {
920
+ const callId = uuid();
921
+ const cleanupDelay = timeout + 5e3;
922
+ const maxPendingPolls = Math.max(1, Math.ceil(timeout / PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY$1));
923
+ let pendingPolls = 0;
924
+ let protocolFailures = 0;
925
+ let unavailableReads = 0;
926
+ let latestResult;
927
+ while (true) {
928
+ let fallbackResult;
929
+ try {
930
+ fallbackResult = (await connection.send("App.callFunction", {
931
+ functionDeclaration: ROUTE_METHOD_FUNCTION_DECLARATION,
932
+ args: [
933
+ route,
934
+ query,
935
+ method,
936
+ args,
937
+ callId,
938
+ cleanupDelay
939
+ ]
940
+ }, { timeout })).result;
941
+ } catch (error) {
942
+ protocolFailures += 1;
943
+ if (!isProtocolTimeoutError$1(error) || protocolFailures >= PAGE_CALL_METHOD_FALLBACK_RETRIES$1) throw error;
944
+ await sleep$1(PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY$1);
945
+ continue;
946
+ }
947
+ if (isRouteMethodResult(fallbackResult)) if (!fallbackResult.__weappVitePageMethodFound) {
948
+ unavailableReads += 1;
949
+ if (unavailableReads >= PAGE_CALL_METHOD_FALLBACK_RETRIES$1) return;
950
+ } else if (fallbackResult.status === "pending") {
951
+ pendingPolls += 1;
952
+ if (pendingPolls >= maxPendingPolls) throw new Error(`Timed out waiting for page method ${method} after ${timeout}ms`);
953
+ } else if (fallbackResult.status === "rejected") throw new Error(`Page method ${method} failed: ${fallbackResult.error || "unknown error"}`);
954
+ else return fallbackResult.value;
955
+ else {
956
+ latestResult = fallbackResult;
957
+ if (latestResult !== void 0) return latestResult;
958
+ unavailableReads += 1;
959
+ if (unavailableReads >= PAGE_CALL_METHOD_FALLBACK_RETRIES$1) return latestResult;
960
+ }
961
+ await sleep$1(PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY$1);
962
+ }
963
+ }
964
+ //#endregion
785
965
  //#region src/Page.ts
786
966
  const PAGE_QUERY_TIMEOUT = 2500;
787
967
  const PAGE_DATA_TIMEOUT = 12e3;
@@ -801,6 +981,10 @@ const PAGE_ROOT_SELECTORS = [
801
981
  "weapp-app-shell",
802
982
  "view"
803
983
  ];
984
+ const PAGE_DIRECT_RENDERED_QUERY_TIMEOUT = 800;
985
+ const PAGE_DIRECT_RENDERED_MAX_SCOPES = 12;
986
+ const PAGE_DIRECT_COMPONENT_SELECTORS = ["weapp-app-shell", "weapp-layout-default"];
987
+ const DATASET_UPPERCASE_PATTERN = /[A-Z]/g;
804
988
  function isProtocolTimeoutError(error, method) {
805
989
  return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === method;
806
990
  }
@@ -826,6 +1010,9 @@ function matchesDataset(node, expected) {
826
1010
  const dataset = node.dataset ?? {};
827
1011
  return Object.entries(expected).every(([key, value]) => String(dataset[key] ?? "") === String(value));
828
1012
  }
1013
+ function toDatasetAttributeName(key) {
1014
+ return `data-${key.replace(DATASET_UPPERCASE_PATTERN, (letter) => `-${letter.toLowerCase()}`)}`;
1015
+ }
829
1016
  /** Page 的实现。 */
830
1017
  var Page = class Page {
831
1018
  connection;
@@ -1090,6 +1277,10 @@ var Page = class Page {
1090
1277
  index += 1;
1091
1278
  queryScope(scope).then(function (nodes) {
1092
1279
  collected = collected.concat(nodes);
1280
+ if (nodes.length > 0) {
1281
+ resolve(collected);
1282
+ return;
1283
+ }
1093
1284
  next();
1094
1285
  }, function () {
1095
1286
  next();
@@ -1338,12 +1529,28 @@ var Page = class Page {
1338
1529
  while (Date.now() - start <= timeout) {
1339
1530
  try {
1340
1531
  if (options.selector) {
1341
- const nodes = await this.renderedNodes(options.selector, {
1342
- componentSelectors: options.componentSelectors,
1343
- timeout: Math.min(PAGE_RENDERED_QUERY_TIMEOUT, Math.max(1, timeout - (Date.now() - start)))
1532
+ const remaining = Math.max(1, timeout - (Date.now() - start));
1533
+ let directNode = null;
1534
+ if (!this.preferAppServicePageProtocol) try {
1535
+ directNode = await this.readDirectRenderedNode(options.selector, options.dataset, Math.max(1, Math.floor(remaining / 2)), options.componentSelectors);
1536
+ } catch (error) {
1537
+ lastError = error;
1538
+ }
1539
+ if (directNode && matchesDataset(directNode, options.dataset)) return JSON.stringify({
1540
+ selector: options.selector,
1541
+ nodes: [directNode]
1344
1542
  });
1543
+ let nodes = [];
1544
+ try {
1545
+ nodes = await this.renderedNodes(options.selector, {
1546
+ componentSelectors: options.componentSelectors,
1547
+ timeout: Math.min(PAGE_RENDERED_QUERY_TIMEOUT, Math.max(1, timeout - (Date.now() - start)))
1548
+ });
1549
+ lastError = void 0;
1550
+ } catch (error) {
1551
+ lastError = error;
1552
+ }
1345
1553
  lastRenderedNodes = nodes;
1346
- lastError = void 0;
1347
1554
  if (nodes.find((node) => matchesDataset(node, options.dataset))) return JSON.stringify({
1348
1555
  selector: options.selector,
1349
1556
  nodes
@@ -1367,6 +1574,45 @@ var Page = class Page {
1367
1574
  const latest = options.selector ? JSON.stringify(lastRenderedNodes).slice(0, 500) : lastWxml.slice(0, 500);
1368
1575
  throw new Error(`Timed out waiting page rendered:${expected}; reason=${reason}; latest=${latest}`);
1369
1576
  }
1577
+ async readDirectRenderedNode(selector, expectedDataset, timeout, componentSelectors) {
1578
+ const element = await this.findDirectRenderedElement(selector, timeout, componentSelectors);
1579
+ if (!element) return null;
1580
+ const entries = await Promise.all(Object.keys(expectedDataset ?? {}).map(async (key) => [key, await element.attribute(toDatasetAttributeName(key))]));
1581
+ return { dataset: Object.fromEntries(entries) };
1582
+ }
1583
+ async findDirectRenderedElement(selector, timeout, componentSelectors) {
1584
+ const start = Date.now();
1585
+ const getQueryTimeout = () => Math.min(PAGE_DIRECT_RENDERED_QUERY_TIMEOUT, Math.max(1, timeout - (Date.now() - start)));
1586
+ const directElement = await this.$(selector, {
1587
+ fallback: false,
1588
+ timeout: getQueryTimeout()
1589
+ });
1590
+ if (directElement) return directElement;
1591
+ const scopeSelectors = [...new Set([...componentSelectors ?? [], ...PAGE_DIRECT_COMPONENT_SELECTORS].filter(Boolean))];
1592
+ for (const rootSelector of PAGE_ROOT_SELECTORS) {
1593
+ if (Date.now() - start >= timeout) break;
1594
+ const rootElement = await this.$(rootSelector, {
1595
+ fallback: false,
1596
+ timeout: getQueryTimeout()
1597
+ });
1598
+ if (!rootElement) continue;
1599
+ const queue = [rootElement];
1600
+ const visited = /* @__PURE__ */ new Set();
1601
+ while (queue.length > 0 && visited.size < PAGE_DIRECT_RENDERED_MAX_SCOPES) {
1602
+ if (Date.now() - start >= timeout) return null;
1603
+ const scope = queue.shift();
1604
+ if (visited.has(scope)) continue;
1605
+ visited.add(scope);
1606
+ const nestedElement = await scope.$(selector, { timeout: getQueryTimeout() });
1607
+ if (nestedElement) return nestedElement;
1608
+ for (const componentSelector of scopeSelectors) {
1609
+ const childScope = await scope.$(componentSelector, { timeout: getQueryTimeout() });
1610
+ if (childScope && !visited.has(childScope)) queue.push(childScope);
1611
+ }
1612
+ }
1613
+ }
1614
+ return null;
1615
+ }
1370
1616
  async data(path, options = {}) {
1371
1617
  const payload = {};
1372
1618
  if (path) payload.path = path;
@@ -1482,71 +1728,7 @@ var Page = class Page {
1482
1728
  })).map((node, index) => createRouteFallbackElement(this.connection, this.elementMap, this.id, selector, node, index));
1483
1729
  }
1484
1730
  async callRouteMethod(method, args, timeout = PAGE_CALL_METHOD_TIMEOUT) {
1485
- let latestResult;
1486
- for (let attempt = 1; attempt <= PAGE_CALL_METHOD_FALLBACK_RETRIES; attempt += 1) {
1487
- try {
1488
- const fallbackResult = (await this.connection.send("App.callFunction", {
1489
- functionDeclaration: `async function (route, query, method, args) {
1490
- var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
1491
- var normalizedRoute = String(route || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
1492
- function methodResult(value) {
1493
- return {
1494
- __weappVitePageMethodFound: true,
1495
- value: value
1496
- };
1497
- }
1498
- function matchesQuery(page, expectedQuery) {
1499
- if (!expectedQuery || !Object.keys(expectedQuery).length) {
1500
- return true;
1501
- }
1502
- var actualQuery = page && (page.options || page.query || {});
1503
- return Object.keys(expectedQuery).every(function (key) {
1504
- var actualValue = String(actualQuery[key] == null ? '' : actualQuery[key]);
1505
- var expectedValue = String(expectedQuery[key]);
1506
- var decodedActualValue = actualValue;
1507
- try {
1508
- decodedActualValue = decodeURIComponent(actualValue);
1509
- }
1510
- catch (_) {
1511
- }
1512
- return actualValue === expectedValue
1513
- || decodedActualValue === expectedValue
1514
- || actualValue === encodeURIComponent(expectedValue);
1515
- });
1516
- }
1517
- for (var index = pages.length - 1; index >= 0; index -= 1) {
1518
- var page = pages[index];
1519
- var pageRoute = String(page.path || page.route || page.__route__ || '').replace(/^\\/+/, '').replace(/\\/+$/g, '');
1520
- if (pageRoute === normalizedRoute && matchesQuery(page, query) && typeof page[method] === 'function') {
1521
- return methodResult(await page[method].apply(page, args || []));
1522
- }
1523
- }
1524
- for (var fallbackIndex = pages.length - 1; fallbackIndex >= 0; fallbackIndex -= 1) {
1525
- var fallbackPage = pages[fallbackIndex];
1526
- if (matchesQuery(fallbackPage, query) && typeof fallbackPage[method] === 'function') {
1527
- return methodResult(await fallbackPage[method].apply(fallbackPage, args || []));
1528
- }
1529
- }
1530
- return {
1531
- __weappVitePageMethodFound: false
1532
- };
1533
- }`,
1534
- args: [
1535
- this.path,
1536
- this.query,
1537
- method,
1538
- args
1539
- ]
1540
- }, { timeout })).result;
1541
- if (fallbackResult && typeof fallbackResult === "object" && fallbackResult.__weappVitePageMethodFound === true) return fallbackResult.value;
1542
- latestResult = fallbackResult && typeof fallbackResult === "object" && fallbackResult.__weappVitePageMethodFound === false ? void 0 : fallbackResult;
1543
- if (latestResult !== void 0 || attempt === PAGE_CALL_METHOD_FALLBACK_RETRIES) return latestResult;
1544
- } catch (error) {
1545
- if (!isProtocolTimeoutError(error, "App.callFunction") || attempt === PAGE_CALL_METHOD_FALLBACK_RETRIES) throw error;
1546
- }
1547
- await sleep$1(PAGE_CALL_METHOD_FALLBACK_RETRY_DELAY);
1548
- }
1549
- return latestResult;
1731
+ return await callRouteMethodViaAppService(this.connection, this.path, this.query, method, args, timeout);
1550
1732
  }
1551
1733
  async setRouteData(data) {
1552
1734
  for (let attempt = 1; attempt <= PAGE_CALL_METHOD_FALLBACK_RETRIES; attempt += 1) {
@@ -1689,19 +1871,22 @@ function withTimeout(task, timeoutMs) {
1689
1871
  function isOperationTimeoutError(error, timeoutMs) {
1690
1872
  return error instanceof Error && error.message.includes(`Operation timed out after ${timeoutMs}ms`);
1691
1873
  }
1874
+ function isAutomatorResponseTimeout(error) {
1875
+ return error instanceof Error && /timeout waiting for automator response/i.test(error.message);
1876
+ }
1692
1877
  function isFnStr(value) {
1693
1878
  if (!isStr(value)) return false;
1694
1879
  const trimmed = trim(value);
1695
1880
  return startWith(trimmed, "function") || startWith(trimmed, "() =>");
1696
1881
  }
1697
1882
  function isCurrentPageProtocolTimeout(error) {
1698
- return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.getCurrentPage";
1883
+ return isAutomatorResponseTimeout(error) || error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.getCurrentPage";
1699
1884
  }
1700
1885
  function isPageStackProtocolTimeout(error) {
1701
- return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.getPageStack";
1886
+ return isAutomatorResponseTimeout(error) || error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.getPageStack";
1702
1887
  }
1703
1888
  function isCallFunctionProtocolTimeout(error) {
1704
- return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.callFunction";
1889
+ return isAutomatorResponseTimeout(error) || error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.callFunction";
1705
1890
  }
1706
1891
  function isPageMetaMissingError(error) {
1707
1892
  const message = error instanceof Error ? error.message : String(error);
@@ -1712,7 +1897,7 @@ function isCurrentFrameTimedOutError(error) {
1712
1897
  return message.includes("unexpected current frame status timedout") || message.includes("[loader] unexpected current frame status timedout");
1713
1898
  }
1714
1899
  function isCallWxMethodProtocolTimeout(error) {
1715
- return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.callWxMethod";
1900
+ return isAutomatorResponseTimeout(error) || error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.callWxMethod";
1716
1901
  }
1717
1902
  function isCaptureScreenshotRecoverableError(error) {
1718
1903
  if (!(error instanceof Error)) return false;