@midscene/test 1.12.4-beta-20260903111111.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
@@ -204,7 +204,15 @@ const validateDefinition = (options)=>{
204
204
  if ('string' != typeof options.name || 0 === options.name.trim().length) throw new NodeDefinitionError('Node name must be a non-empty string.');
205
205
  validateOptionalText(options.title, 'title', options.name);
206
206
  validateOptionalText(options.description, "description", options.name);
207
+ 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.`, {
208
+ node: options.name,
209
+ field: 'stringInputKey'
210
+ });
207
211
  validateInputSchema(options.inputSchema, options.name);
212
+ if ('string' == typeof options.stringInputKey && options.inputSchema instanceof z.ZodObject && !(options.stringInputKey in options.inputSchema.shape)) throw new NodeDefinitionError(`Node "${options.name}" stringInputKey must reference a field in inputSchema.`, {
213
+ node: options.name,
214
+ field: 'stringInputKey'
215
+ });
208
216
  if ('function' != typeof options.execute) throw new NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
209
217
  node: options.name
210
218
  });
@@ -367,10 +375,11 @@ const platforms = new Set([
367
375
  'web',
368
376
  'android',
369
377
  'ios',
378
+ 'harmony',
370
379
  'computer'
371
380
  ]);
372
381
  const validatePlatform = (value, label)=>{
373
- if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, computer.`);
382
+ if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, harmony, computer.`);
374
383
  return value;
375
384
  };
376
385
  const validateProjectSetup = (value, projectPlatform, label)=>{
@@ -661,88 +670,6 @@ const createProjectRuntime = (options)=>{
661
670
  }
662
671
  };
663
672
  };
664
- const supportedMetaKeys = new Set([
665
- 'timeout',
666
- 'continue-on-error'
667
- ]);
668
- function isMapping(value) {
669
- return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
670
- }
671
- function formatStep(index) {
672
- return `step ${index + 1}`;
673
- }
674
- function normalizeMeta(value, index) {
675
- if (void 0 === value) return {
676
- continueOnError: false
677
- };
678
- if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
679
- index
680
- });
681
- for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
682
- index,
683
- key
684
- });
685
- const timeout = value.timeout;
686
- 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.`, {
687
- index,
688
- timeout
689
- });
690
- const continueOnError = value['continue-on-error'];
691
- if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
692
- index,
693
- continueOnError
694
- });
695
- return {
696
- ...void 0 === timeout ? {} : {
697
- timeoutMs: timeout
698
- },
699
- continueOnError: continueOnError ?? false
700
- };
701
- }
702
- function validateCommonNodeInput(input, index) {
703
- if (void 0 !== input.prompt && 'string' != typeof input.prompt) throw new WorkflowParseError(`${formatStep(index)} "prompt" must be a string.`, {
704
- index,
705
- prompt: input.prompt
706
- });
707
- }
708
- function normalizeStep(value, index = 0) {
709
- if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
710
- index
711
- });
712
- const entries = Object.entries(value);
713
- if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
714
- index,
715
- nodes: Object.keys(value)
716
- });
717
- const [node, rawValue] = entries[0];
718
- if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
719
- index
720
- });
721
- if ('string' == typeof rawValue) return {
722
- node,
723
- input: {
724
- prompt: rawValue
725
- },
726
- meta: {
727
- continueOnError: false
728
- }
729
- };
730
- if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
731
- index,
732
- node
733
- });
734
- const { $: rawMeta, ...input } = rawValue;
735
- validateCommonNodeInput(input, index);
736
- return {
737
- node,
738
- input,
739
- meta: normalizeMeta(rawMeta, index)
740
- };
741
- }
742
- function normalizeSteps(steps) {
743
- if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
744
- return steps.map((step, index)=>normalizeStep(step, index));
745
- }
746
673
  const assertJsonSerializable = (value, label, node)=>{
747
674
  const visit = (candidate, path, seen)=>{
748
675
  if (null === candidate || 'string' == typeof candidate || 'boolean' == typeof candidate) return;
@@ -765,45 +692,6 @@ const assertJsonSerializable = (value, label, node)=>{
765
692
  throw new NodeExecutionError(node, error);
766
693
  }
767
694
  };
768
- const cloneAndFreezeJson = (value)=>{
769
- if (Array.isArray(value)) return Object.freeze(value.map(cloneAndFreezeJson));
770
- if ('object' == typeof value && null !== value) return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child])=>[
771
- key,
772
- cloneAndFreezeJson(child)
773
- ])));
774
- return value;
775
- };
776
- const toHistoryEntry = (result, scope)=>{
777
- assertJsonSerializable(result.input, 'input', result.node);
778
- if (result.output?.data !== void 0) assertJsonSerializable(result.output.data, 'output.data', result.node);
779
- return Object.freeze({
780
- scope,
781
- phase: result.phase,
782
- stepIndex: result.stepIndex,
783
- node: result.node,
784
- input: cloneAndFreezeJson(result.input),
785
- ...'string' == typeof result.input?.prompt ? {
786
- intent: result.input.prompt
787
- } : {},
788
- status: 'success' === result.status ? 'passed' : 'failed',
789
- ...result.output?.summary === void 0 ? {} : {
790
- summary: result.output.summary
791
- },
792
- ...result.output?.data === void 0 ? {} : {
793
- data: cloneAndFreezeJson(result.output.data)
794
- },
795
- ...result.error ? {
796
- error: Object.freeze({
797
- name: result.error.name,
798
- message: result.error.message
799
- })
800
- } : {}
801
- });
802
- };
803
- const createHistory = (prefix, results, scope)=>Object.freeze([
804
- ...prefix,
805
- ...results.map((result)=>toHistoryEntry(result, scope))
806
- ]);
807
695
  function validateNodeOutput(output, node) {
808
696
  if (void 0 === output) return;
809
697
  if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
@@ -825,7 +713,6 @@ function createStepResultBase(step, startedAt, phase, stepIndex) {
825
713
  };
826
714
  }
827
715
  async function executeNode(step, execute, phase, stepIndex, options) {
828
- validateCommonNodeInput(step.input, stepIndex);
829
716
  const startedAt = new Date();
830
717
  const abortController = new AbortController();
831
718
  const parentSignal = options.parentSignal;
@@ -909,7 +796,6 @@ async function executeStep(step, node, target, context, execution = {}) {
909
796
  $: step.meta,
910
797
  signal,
911
798
  context,
912
- history: execution.history ?? Object.freeze([]),
913
799
  onTeardown: (teardown)=>{
914
800
  if (!execution.onTeardown) throw new NodeExecutionError(step.node, new Error('The current execution scope cannot register teardown.'));
915
801
  execution.onTeardown(step.node, teardown);
@@ -946,7 +832,6 @@ function createDocumentRuntime(document, options) {
946
832
  const startedAt = new Date();
947
833
  const beforeAll = [];
948
834
  const afterAll = [];
949
- const completedNodes = [];
950
835
  const nodes = {
951
836
  beforeAll: document.lifecycle.beforeAll.map((step)=>options.resolveNode(step.node)),
952
837
  afterAll: document.lifecycle.afterAll.map((step)=>options.resolveNode(step.node))
@@ -977,7 +862,7 @@ function createDocumentRuntime(document, options) {
977
862
  projectId: document.projectId,
978
863
  projectName: project.name,
979
864
  sourcePath: document.sourcePath,
980
- status: completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
865
+ status: beforeAll.some((step)=>'failed' === step.status) || afterAll.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
981
866
  startedAt: startedAt.toISOString(),
982
867
  endedAt: endedAt.toISOString(),
983
868
  durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
@@ -1006,10 +891,7 @@ function createDocumentRuntime(document, options) {
1006
891
  projectName: project.name,
1007
892
  sourcePath: document.sourcePath,
1008
893
  phase,
1009
- stepIndex,
1010
- completedNodes: Object.freeze([
1011
- ...completedNodes
1012
- ])
894
+ stepIndex
1013
895
  };
1014
896
  const stepInfo = {
1015
897
  scope: 'document',
@@ -1022,7 +904,6 @@ function createDocumentRuntime(document, options) {
1022
904
  scope: 'document',
1023
905
  document: documentContext
1024
906
  }, context, {
1025
- history: createHistory([], completedNodes, 'document'),
1026
907
  signal: executionSignal,
1027
908
  defaultTimeoutMs: options.defaultTimeoutMs,
1028
909
  onTeardown (node, teardown) {
@@ -1045,7 +926,6 @@ function createDocumentRuntime(document, options) {
1045
926
  });
1046
927
  await options.onStepResult?.(stepInfo, result);
1047
928
  results.push(result);
1048
- completedNodes.push(result);
1049
929
  if ('failed' === result.status && !result.continuedAfterError) break;
1050
930
  }
1051
931
  };
@@ -1053,9 +933,6 @@ function createDocumentRuntime(document, options) {
1053
933
  get context () {
1054
934
  return context;
1055
935
  },
1056
- get history () {
1057
- return createHistory([], completedNodes, 'document');
1058
- },
1059
936
  get canRunCases () {
1060
937
  return started && !signal.aborted && !beforeAll.some((step)=>'failed' === step.status);
1061
938
  },
@@ -1115,7 +992,6 @@ async function runCollectedCase(collectedCase, options) {
1115
992
  const beforeEach = [];
1116
993
  const steps = [];
1117
994
  const afterEach = [];
1118
- const completedNodes = [];
1119
995
  const teardownStack = [];
1120
996
  let acceptingTeardowns = true;
1121
997
  const onTeardown = (node, teardown)=>{
@@ -1144,13 +1020,7 @@ async function runCollectedCase(collectedCase, options) {
1144
1020
  sourcePath: collectedCase.sourcePath,
1145
1021
  caseIndex: collectedCase.caseIndex,
1146
1022
  phase,
1147
- stepIndex,
1148
- completedSteps: Object.freeze([
1149
- ...steps
1150
- ]),
1151
- completedNodes: Object.freeze([
1152
- ...completedNodes
1153
- ])
1023
+ stepIndex
1154
1024
  };
1155
1025
  const stepInfo = {
1156
1026
  scope: 'case',
@@ -1163,14 +1033,12 @@ async function runCollectedCase(collectedCase, options) {
1163
1033
  scope: 'case',
1164
1034
  case: caseContext
1165
1035
  }, options.context, {
1166
- history: createHistory(options.documentHistory ?? [], completedNodes, 'case'),
1167
1036
  signal,
1168
1037
  defaultTimeoutMs: options.defaultTimeoutMs,
1169
1038
  onTeardown
1170
1039
  });
1171
1040
  await options.onStepResult?.(stepInfo, result);
1172
1041
  results.push(result);
1173
- completedNodes.push(result);
1174
1042
  if ('failed' === result.status && !result.continuedAfterError) break;
1175
1043
  }
1176
1044
  };
@@ -1210,7 +1078,7 @@ async function runCollectedCase(collectedCase, options) {
1210
1078
  name: collectedCase.definition.name,
1211
1079
  sourcePath: collectedCase.sourcePath,
1212
1080
  caseIndex: collectedCase.caseIndex,
1213
- status: completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
1081
+ status: beforeEach.some((step)=>'failed' === step.status) || steps.some((step)=>'failed' === step.status) || afterEach.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
1214
1082
  beforeEach,
1215
1083
  steps,
1216
1084
  afterEach,
@@ -1280,7 +1148,6 @@ async function runWorkflowDocument(document, options) {
1280
1148
  context: runtime.context,
1281
1149
  projectName,
1282
1150
  attemptIndex,
1283
- documentHistory: runtime.history,
1284
1151
  signal: options.signal,
1285
1152
  defaultTimeoutMs: options.defaultTimeoutMs,
1286
1153
  onStepStart: options.onStepStart,
@@ -1326,6 +1193,89 @@ async function runWorkflowDocument(document, options) {
1326
1193
  cases
1327
1194
  };
1328
1195
  }
1196
+ const supportedMetaKeys = new Set([
1197
+ 'timeout',
1198
+ 'continue-on-error'
1199
+ ]);
1200
+ function isMapping(value) {
1201
+ return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
1202
+ }
1203
+ function formatStep(index) {
1204
+ return `step ${index + 1}`;
1205
+ }
1206
+ function normalizeMeta(value, index) {
1207
+ if (void 0 === value) return {
1208
+ continueOnError: false
1209
+ };
1210
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
1211
+ index
1212
+ });
1213
+ for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
1214
+ index,
1215
+ key
1216
+ });
1217
+ const timeout = value.timeout;
1218
+ 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.`, {
1219
+ index,
1220
+ timeout
1221
+ });
1222
+ const continueOnError = value['continue-on-error'];
1223
+ if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
1224
+ index,
1225
+ continueOnError
1226
+ });
1227
+ return {
1228
+ ...void 0 === timeout ? {} : {
1229
+ timeoutMs: timeout
1230
+ },
1231
+ continueOnError: continueOnError ?? false
1232
+ };
1233
+ }
1234
+ function normalizeStep(value, index = 0, resolveNode) {
1235
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
1236
+ index
1237
+ });
1238
+ const entries = Object.entries(value);
1239
+ if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
1240
+ index,
1241
+ nodes: Object.keys(value)
1242
+ });
1243
+ const [node, rawValue] = entries[0];
1244
+ if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
1245
+ index
1246
+ });
1247
+ if ('string' == typeof rawValue) {
1248
+ const resolvedNode = resolveNode?.(node);
1249
+ const stringInputKey = resolvedNode ? resolvedNode.stringInputKey : 'prompt';
1250
+ if (false === stringInputKey || void 0 === stringInputKey) throw new WorkflowParseError(`${formatStep(index)} node "${node}" does not accept string shorthand.`, {
1251
+ index,
1252
+ node
1253
+ });
1254
+ return {
1255
+ node,
1256
+ input: {
1257
+ [stringInputKey]: rawValue
1258
+ },
1259
+ meta: {
1260
+ continueOnError: false
1261
+ }
1262
+ };
1263
+ }
1264
+ if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
1265
+ index,
1266
+ node
1267
+ });
1268
+ const { $: rawMeta, ...input } = rawValue;
1269
+ return {
1270
+ node,
1271
+ input,
1272
+ meta: normalizeMeta(rawMeta, index)
1273
+ };
1274
+ }
1275
+ function normalizeSteps(steps, resolveNode) {
1276
+ if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
1277
+ return steps.map((step, index)=>normalizeStep(step, index, resolveNode));
1278
+ }
1329
1279
  const interpolationExpression = /\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}|\$\{([^{}]+)\}/g;
1330
1280
  const exactEnvExpression = /^\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}$/;
1331
1281
  const exactProjectExpression = /^\$\{([^{}]+)\}$/;
@@ -1429,7 +1379,7 @@ function collectWorkflowDocument(source, options) {
1429
1379
  if (!Array.isArray(value)) throw new WorkflowParseError(`Workflow document ${phase} must be an array.`, {
1430
1380
  phase
1431
1381
  });
1432
- return normalizeSteps(value).map((normalized, stepIndex)=>{
1382
+ return normalizeSteps(value, options.resolveNode).map((normalized, stepIndex)=>{
1433
1383
  const resolved = options.resolveNode(normalized.node);
1434
1384
  if (!resolved) throw new WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
1435
1385
  phase,
@@ -1465,7 +1415,7 @@ function collectWorkflowDocument(source, options) {
1465
1415
  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.`, {
1466
1416
  caseIndex
1467
1417
  });
1468
- const steps = normalizeSteps(definition.steps).map((normalized, stepIndex)=>{
1418
+ const steps = normalizeSteps(definition.steps, options.resolveNode).map((normalized, stepIndex)=>{
1469
1419
  if (!options.resolveNode(normalized.node)) throw new WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
1470
1420
  caseIndex,
1471
1421
  stepIndex,