@midscene/test 1.12.4-beta-20260904041024.0 → 1.12.4

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.
Files changed (40) hide show
  1. package/dist/es/android/index.mjs +50 -106
  2. package/dist/es/android/index.mjs.map +1 -1
  3. package/dist/es/cli/index.mjs +99 -149
  4. package/dist/es/cli/index.mjs.map +1 -1
  5. package/dist/es/harmony/index.mjs +119 -0
  6. package/dist/es/harmony/index.mjs.map +1 -0
  7. package/dist/es/index.mjs +36 -87
  8. package/dist/es/index.mjs.map +1 -1
  9. package/dist/es/ios/index.mjs +51 -101
  10. package/dist/es/ios/index.mjs.map +1 -1
  11. package/dist/es/midscene/index.mjs +37 -235
  12. package/dist/es/midscene/index.mjs.map +1 -1
  13. package/dist/es/playwright/index.mjs +14 -10
  14. package/dist/es/playwright/index.mjs.map +1 -1
  15. package/dist/es/test-cli.mjs +102 -150
  16. package/dist/es/test-cli.mjs.map +1 -1
  17. package/dist/lib/android/index.js +151 -178
  18. package/dist/lib/android/index.js.map +1 -1
  19. package/dist/lib/cli/index.js +99 -149
  20. package/dist/lib/cli/index.js.map +1 -1
  21. package/dist/lib/harmony/index.js +188 -0
  22. package/dist/lib/harmony/index.js.map +1 -0
  23. package/dist/lib/index.js +37 -100
  24. package/dist/lib/index.js.map +1 -1
  25. package/dist/lib/ios/index.js +152 -173
  26. package/dist/lib/ios/index.js.map +1 -1
  27. package/dist/lib/midscene/index.js +264 -412
  28. package/dist/lib/midscene/index.js.map +1 -1
  29. package/dist/lib/playwright/index.js +14 -10
  30. package/dist/lib/playwright/index.js.map +1 -1
  31. package/dist/lib/test-cli.js +102 -150
  32. package/dist/lib/test-cli.js.map +1 -1
  33. package/dist/types/android/index.d.ts +27 -101
  34. package/dist/types/cli/index.d.ts +9 -32
  35. package/dist/types/harmony/index.d.ts +124 -0
  36. package/dist/types/index.d.ts +18 -45
  37. package/dist/types/ios/index.d.ts +28 -107
  38. package/dist/types/midscene/index.d.ts +54 -153
  39. package/dist/types/playwright/index.d.ts +9 -63
  40. package/package.json +25 -6
@@ -235,7 +235,15 @@ const validateDefinition = (options)=>{
235
235
  if ('string' != typeof options.name || 0 === options.name.trim().length) throw new NodeDefinitionError('Node name must be a non-empty string.');
236
236
  validateOptionalText(options.title, 'title', options.name);
237
237
  validateOptionalText(options.description, "description", options.name);
238
+ if (void 0 !== options.stringInputKey && false !== options.stringInputKey && ('string' != typeof options.stringInputKey || 0 === options.stringInputKey.trim().length)) throw new NodeDefinitionError(`Node "${options.name}" stringInputKey must be a non-empty string or false.`, {
239
+ node: options.name,
240
+ field: 'stringInputKey'
241
+ });
238
242
  validateInputSchema(options.inputSchema, options.name);
243
+ if ('string' == typeof options.stringInputKey && options.inputSchema instanceof v4_namespaceObject.z.ZodObject && !(options.stringInputKey in options.inputSchema.shape)) throw new NodeDefinitionError(`Node "${options.name}" stringInputKey must reference a field in inputSchema.`, {
244
+ node: options.name,
245
+ field: 'stringInputKey'
246
+ });
239
247
  if ('function' != typeof options.execute) throw new NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
240
248
  node: options.name
241
249
  });
@@ -398,10 +406,11 @@ const platforms = new Set([
398
406
  'web',
399
407
  'android',
400
408
  'ios',
409
+ 'harmony',
401
410
  'computer'
402
411
  ]);
403
412
  const validatePlatform = (value, label)=>{
404
- if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, computer.`);
413
+ if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, harmony, computer.`);
405
414
  return value;
406
415
  };
407
416
  const validateProjectSetup = (value, projectPlatform, label)=>{
@@ -696,88 +705,6 @@ const createProjectRuntime = (options)=>{
696
705
  }
697
706
  };
698
707
  };
699
- const supportedMetaKeys = new Set([
700
- 'timeout',
701
- 'continue-on-error'
702
- ]);
703
- function isMapping(value) {
704
- return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
705
- }
706
- function formatStep(index) {
707
- return `step ${index + 1}`;
708
- }
709
- function normalizeMeta(value, index) {
710
- if (void 0 === value) return {
711
- continueOnError: false
712
- };
713
- if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
714
- index
715
- });
716
- for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
717
- index,
718
- key
719
- });
720
- const timeout = value.timeout;
721
- if (void 0 !== timeout && ('number' != typeof timeout || !Number.isFinite(timeout) || timeout <= 0)) throw new WorkflowParseError(`${formatStep(index)} "$.timeout" must be a positive number of milliseconds.`, {
722
- index,
723
- timeout
724
- });
725
- const continueOnError = value['continue-on-error'];
726
- if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
727
- index,
728
- continueOnError
729
- });
730
- return {
731
- ...void 0 === timeout ? {} : {
732
- timeoutMs: timeout
733
- },
734
- continueOnError: continueOnError ?? false
735
- };
736
- }
737
- function validateCommonNodeInput(input, index) {
738
- if (void 0 !== input.prompt && 'string' != typeof input.prompt) throw new WorkflowParseError(`${formatStep(index)} "prompt" must be a string.`, {
739
- index,
740
- prompt: input.prompt
741
- });
742
- }
743
- function normalizeStep(value, index = 0) {
744
- if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
745
- index
746
- });
747
- const entries = Object.entries(value);
748
- if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
749
- index,
750
- nodes: Object.keys(value)
751
- });
752
- const [node, rawValue] = entries[0];
753
- if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
754
- index
755
- });
756
- if ('string' == typeof rawValue) return {
757
- node,
758
- input: {
759
- prompt: rawValue
760
- },
761
- meta: {
762
- continueOnError: false
763
- }
764
- };
765
- if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
766
- index,
767
- node
768
- });
769
- const { $: rawMeta, ...input } = rawValue;
770
- validateCommonNodeInput(input, index);
771
- return {
772
- node,
773
- input,
774
- meta: normalizeMeta(rawMeta, index)
775
- };
776
- }
777
- function normalizeSteps(steps) {
778
- if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
779
- return steps.map((step, index)=>normalizeStep(step, index));
780
- }
781
708
  const assertJsonSerializable = (value, label, node)=>{
782
709
  const visit = (candidate, path, seen)=>{
783
710
  if (null === candidate || 'string' == typeof candidate || 'boolean' == typeof candidate) return;
@@ -800,45 +727,6 @@ const assertJsonSerializable = (value, label, node)=>{
800
727
  throw new NodeExecutionError(node, error);
801
728
  }
802
729
  };
803
- const cloneAndFreezeJson = (value)=>{
804
- if (Array.isArray(value)) return Object.freeze(value.map(cloneAndFreezeJson));
805
- if ('object' == typeof value && null !== value) return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child])=>[
806
- key,
807
- cloneAndFreezeJson(child)
808
- ])));
809
- return value;
810
- };
811
- const toHistoryEntry = (result, scope)=>{
812
- assertJsonSerializable(result.input, 'input', result.node);
813
- if (result.output?.data !== void 0) assertJsonSerializable(result.output.data, 'output.data', result.node);
814
- return Object.freeze({
815
- scope,
816
- phase: result.phase,
817
- stepIndex: result.stepIndex,
818
- node: result.node,
819
- input: cloneAndFreezeJson(result.input),
820
- ...'string' == typeof result.input?.prompt ? {
821
- intent: result.input.prompt
822
- } : {},
823
- status: 'success' === result.status ? 'passed' : 'failed',
824
- ...result.output?.summary === void 0 ? {} : {
825
- summary: result.output.summary
826
- },
827
- ...result.output?.data === void 0 ? {} : {
828
- data: cloneAndFreezeJson(result.output.data)
829
- },
830
- ...result.error ? {
831
- error: Object.freeze({
832
- name: result.error.name,
833
- message: result.error.message
834
- })
835
- } : {}
836
- });
837
- };
838
- const createHistory = (prefix, results, scope)=>Object.freeze([
839
- ...prefix,
840
- ...results.map((result)=>toHistoryEntry(result, scope))
841
- ]);
842
730
  function validateNodeOutput(output, node) {
843
731
  if (void 0 === output) return;
844
732
  if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
@@ -860,7 +748,6 @@ function createStepResultBase(step, startedAt, phase, stepIndex) {
860
748
  };
861
749
  }
862
750
  async function executeNode(step, execute, phase, stepIndex, options) {
863
- validateCommonNodeInput(step.input, stepIndex);
864
751
  const startedAt = new Date();
865
752
  const abortController = new AbortController();
866
753
  const parentSignal = options.parentSignal;
@@ -944,7 +831,6 @@ async function executeStep(step, node, target, context, execution = {}) {
944
831
  $: step.meta,
945
832
  signal,
946
833
  context,
947
- history: execution.history ?? Object.freeze([]),
948
834
  onTeardown: (teardown)=>{
949
835
  if (!execution.onTeardown) throw new NodeExecutionError(step.node, new Error('The current execution scope cannot register teardown.'));
950
836
  execution.onTeardown(step.node, teardown);
@@ -981,7 +867,6 @@ function createDocumentRuntime(document, options) {
981
867
  const startedAt = new Date();
982
868
  const beforeAll = [];
983
869
  const afterAll = [];
984
- const completedNodes = [];
985
870
  const nodes = {
986
871
  beforeAll: document.lifecycle.beforeAll.map((step)=>options.resolveNode(step.node)),
987
872
  afterAll: document.lifecycle.afterAll.map((step)=>options.resolveNode(step.node))
@@ -1012,7 +897,7 @@ function createDocumentRuntime(document, options) {
1012
897
  projectId: document.projectId,
1013
898
  projectName: project.name,
1014
899
  sourcePath: document.sourcePath,
1015
- status: completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
900
+ status: beforeAll.some((step)=>'failed' === step.status) || afterAll.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
1016
901
  startedAt: startedAt.toISOString(),
1017
902
  endedAt: endedAt.toISOString(),
1018
903
  durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
@@ -1041,10 +926,7 @@ function createDocumentRuntime(document, options) {
1041
926
  projectName: project.name,
1042
927
  sourcePath: document.sourcePath,
1043
928
  phase,
1044
- stepIndex,
1045
- completedNodes: Object.freeze([
1046
- ...completedNodes
1047
- ])
929
+ stepIndex
1048
930
  };
1049
931
  const stepInfo = {
1050
932
  scope: 'document',
@@ -1057,7 +939,6 @@ function createDocumentRuntime(document, options) {
1057
939
  scope: 'document',
1058
940
  document: documentContext
1059
941
  }, context, {
1060
- history: createHistory([], completedNodes, 'document'),
1061
942
  signal: executionSignal,
1062
943
  defaultTimeoutMs: options.defaultTimeoutMs,
1063
944
  onTeardown (node, teardown) {
@@ -1080,7 +961,6 @@ function createDocumentRuntime(document, options) {
1080
961
  });
1081
962
  await options.onStepResult?.(stepInfo, result);
1082
963
  results.push(result);
1083
- completedNodes.push(result);
1084
964
  if ('failed' === result.status && !result.continuedAfterError) break;
1085
965
  }
1086
966
  };
@@ -1088,9 +968,6 @@ function createDocumentRuntime(document, options) {
1088
968
  get context () {
1089
969
  return context;
1090
970
  },
1091
- get history () {
1092
- return createHistory([], completedNodes, 'document');
1093
- },
1094
971
  get canRunCases () {
1095
972
  return started && !signal.aborted && !beforeAll.some((step)=>'failed' === step.status);
1096
973
  },
@@ -1150,7 +1027,6 @@ async function runCollectedCase(collectedCase, options) {
1150
1027
  const beforeEach = [];
1151
1028
  const steps = [];
1152
1029
  const afterEach = [];
1153
- const completedNodes = [];
1154
1030
  const teardownStack = [];
1155
1031
  let acceptingTeardowns = true;
1156
1032
  const onTeardown = (node, teardown)=>{
@@ -1179,13 +1055,7 @@ async function runCollectedCase(collectedCase, options) {
1179
1055
  sourcePath: collectedCase.sourcePath,
1180
1056
  caseIndex: collectedCase.caseIndex,
1181
1057
  phase,
1182
- stepIndex,
1183
- completedSteps: Object.freeze([
1184
- ...steps
1185
- ]),
1186
- completedNodes: Object.freeze([
1187
- ...completedNodes
1188
- ])
1058
+ stepIndex
1189
1059
  };
1190
1060
  const stepInfo = {
1191
1061
  scope: 'case',
@@ -1198,14 +1068,12 @@ async function runCollectedCase(collectedCase, options) {
1198
1068
  scope: 'case',
1199
1069
  case: caseContext
1200
1070
  }, options.context, {
1201
- history: createHistory(options.documentHistory ?? [], completedNodes, 'case'),
1202
1071
  signal,
1203
1072
  defaultTimeoutMs: options.defaultTimeoutMs,
1204
1073
  onTeardown
1205
1074
  });
1206
1075
  await options.onStepResult?.(stepInfo, result);
1207
1076
  results.push(result);
1208
- completedNodes.push(result);
1209
1077
  if ('failed' === result.status && !result.continuedAfterError) break;
1210
1078
  }
1211
1079
  };
@@ -1245,7 +1113,7 @@ async function runCollectedCase(collectedCase, options) {
1245
1113
  name: collectedCase.definition.name,
1246
1114
  sourcePath: collectedCase.sourcePath,
1247
1115
  caseIndex: collectedCase.caseIndex,
1248
- status: completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
1116
+ status: beforeEach.some((step)=>'failed' === step.status) || steps.some((step)=>'failed' === step.status) || afterEach.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
1249
1117
  beforeEach,
1250
1118
  steps,
1251
1119
  afterEach,
@@ -1315,7 +1183,6 @@ async function runWorkflowDocument(document, options) {
1315
1183
  context: runtime.context,
1316
1184
  projectName,
1317
1185
  attemptIndex,
1318
- documentHistory: runtime.history,
1319
1186
  signal: options.signal,
1320
1187
  defaultTimeoutMs: options.defaultTimeoutMs,
1321
1188
  onStepStart: options.onStepStart,
@@ -1362,6 +1229,89 @@ async function runWorkflowDocument(document, options) {
1362
1229
  };
1363
1230
  }
1364
1231
  const external_js_yaml_namespaceObject = require("js-yaml");
1232
+ const supportedMetaKeys = new Set([
1233
+ 'timeout',
1234
+ 'continue-on-error'
1235
+ ]);
1236
+ function isMapping(value) {
1237
+ return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
1238
+ }
1239
+ function formatStep(index) {
1240
+ return `step ${index + 1}`;
1241
+ }
1242
+ function normalizeMeta(value, index) {
1243
+ if (void 0 === value) return {
1244
+ continueOnError: false
1245
+ };
1246
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
1247
+ index
1248
+ });
1249
+ for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
1250
+ index,
1251
+ key
1252
+ });
1253
+ const timeout = value.timeout;
1254
+ if (void 0 !== timeout && ('number' != typeof timeout || !Number.isFinite(timeout) || timeout <= 0)) throw new WorkflowParseError(`${formatStep(index)} "$.timeout" must be a positive number of milliseconds.`, {
1255
+ index,
1256
+ timeout
1257
+ });
1258
+ const continueOnError = value['continue-on-error'];
1259
+ if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
1260
+ index,
1261
+ continueOnError
1262
+ });
1263
+ return {
1264
+ ...void 0 === timeout ? {} : {
1265
+ timeoutMs: timeout
1266
+ },
1267
+ continueOnError: continueOnError ?? false
1268
+ };
1269
+ }
1270
+ function normalizeStep(value, index = 0, resolveNode) {
1271
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
1272
+ index
1273
+ });
1274
+ const entries = Object.entries(value);
1275
+ if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
1276
+ index,
1277
+ nodes: Object.keys(value)
1278
+ });
1279
+ const [node, rawValue] = entries[0];
1280
+ if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
1281
+ index
1282
+ });
1283
+ if ('string' == typeof rawValue) {
1284
+ const resolvedNode = resolveNode?.(node);
1285
+ const stringInputKey = resolvedNode ? resolvedNode.stringInputKey : 'prompt';
1286
+ if (false === stringInputKey || void 0 === stringInputKey) throw new WorkflowParseError(`${formatStep(index)} node "${node}" does not accept string shorthand.`, {
1287
+ index,
1288
+ node
1289
+ });
1290
+ return {
1291
+ node,
1292
+ input: {
1293
+ [stringInputKey]: rawValue
1294
+ },
1295
+ meta: {
1296
+ continueOnError: false
1297
+ }
1298
+ };
1299
+ }
1300
+ if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
1301
+ index,
1302
+ node
1303
+ });
1304
+ const { $: rawMeta, ...input } = rawValue;
1305
+ return {
1306
+ node,
1307
+ input,
1308
+ meta: normalizeMeta(rawMeta, index)
1309
+ };
1310
+ }
1311
+ function normalizeSteps(steps, resolveNode) {
1312
+ if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
1313
+ return steps.map((step, index)=>normalizeStep(step, index, resolveNode));
1314
+ }
1365
1315
  const interpolationExpression = /\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}|\$\{([^{}]+)\}/g;
1366
1316
  const exactEnvExpression = /^\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}$/;
1367
1317
  const exactProjectExpression = /^\$\{([^{}]+)\}$/;
@@ -1465,7 +1415,7 @@ function collectWorkflowDocument(source, options) {
1465
1415
  if (!Array.isArray(value)) throw new WorkflowParseError(`Workflow document ${phase} must be an array.`, {
1466
1416
  phase
1467
1417
  });
1468
- return normalizeSteps(value).map((normalized, stepIndex)=>{
1418
+ return normalizeSteps(value, options.resolveNode).map((normalized, stepIndex)=>{
1469
1419
  const resolved = options.resolveNode(normalized.node);
1470
1420
  if (!resolved) throw new WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
1471
1421
  phase,
@@ -1501,7 +1451,7 @@ function collectWorkflowDocument(source, options) {
1501
1451
  if (!Array.isArray(tags) || tags.some((tag)=>'string' != typeof tag || 0 === tag.trim().length)) throw new WorkflowParseError(`Case ${caseIndex + 1} tags must be an array of non-empty strings.`, {
1502
1452
  caseIndex
1503
1453
  });
1504
- const steps = normalizeSteps(definition.steps).map((normalized, stepIndex)=>{
1454
+ const steps = normalizeSteps(definition.steps, options.resolveNode).map((normalized, stepIndex)=>{
1505
1455
  if (!options.resolveNode(normalized.node)) throw new WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
1506
1456
  caseIndex,
1507
1457
  stepIndex,