@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
@@ -17,7 +17,7 @@ workflow:
17
17
  continue-on-error: false
18
18
  \`\`\`
19
19
 
20
- A string step value is shorthand for \`{ prompt: value }\`. Whether that input is valid depends on the node input schema.`;
20
+ A Node may map a string step value to one declared input field. Each Node section states whether it supports this shorthand and names the target field.`;
21
21
  const sortJsonValue = (value)=>{
22
22
  if (Array.isArray(value)) return value.map(sortJsonValue);
23
23
  if ('object' != typeof value || null === value) return value;
@@ -65,6 +65,8 @@ const renderNode = (node, warnings)=>{
65
65
  warnings.push(`node "${node.name}" has no description`);
66
66
  sections.push("> Description not declared. Do not infer this node's behavior.");
67
67
  }
68
+ if (false === node.stringInputKey || void 0 === node.stringInputKey) sections.push('**String shorthand:** Not supported by this Node.');
69
+ else sections.push(`**String shorthand:** Maps to \`{ ${node.stringInputKey}: value }\`.`);
68
70
  sections.push('### Input Schema');
69
71
  if (node.inputSchema) sections.push(`\`\`\`json\n${JSON.stringify(toJsonSchema(node), null, 2)}\n\`\`\``);
70
72
  else {
@@ -290,7 +292,15 @@ const validateDefinition = (options)=>{
290
292
  if ('string' != typeof options.name || 0 === options.name.trim().length) throw new NodeDefinitionError('Node name must be a non-empty string.');
291
293
  validateOptionalText(options.title, 'title', options.name);
292
294
  validateOptionalText(options.description, "description", options.name);
295
+ 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.`, {
296
+ node: options.name,
297
+ field: 'stringInputKey'
298
+ });
293
299
  validateInputSchema(options.inputSchema, options.name);
300
+ 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.`, {
301
+ node: options.name,
302
+ field: 'stringInputKey'
303
+ });
294
304
  if ('function' != typeof options.execute) throw new NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
295
305
  node: options.name
296
306
  });
@@ -451,10 +461,11 @@ const platforms = new Set([
451
461
  'web',
452
462
  'android',
453
463
  'ios',
464
+ 'harmony',
454
465
  'computer'
455
466
  ]);
456
467
  const validatePlatform = (value, label)=>{
457
- if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, computer.`);
468
+ if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, harmony, computer.`);
458
469
  return value;
459
470
  };
460
471
  const validateProjectSetup = (value, projectPlatform, label)=>{
@@ -748,88 +759,6 @@ const createProjectRuntime = (options)=>{
748
759
  }
749
760
  };
750
761
  };
751
- const supportedMetaKeys = new Set([
752
- 'timeout',
753
- 'continue-on-error'
754
- ]);
755
- function isMapping(value) {
756
- return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
757
- }
758
- function formatStep(index) {
759
- return `step ${index + 1}`;
760
- }
761
- function normalizeMeta(value, index) {
762
- if (void 0 === value) return {
763
- continueOnError: false
764
- };
765
- if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
766
- index
767
- });
768
- for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
769
- index,
770
- key
771
- });
772
- const timeout = value.timeout;
773
- 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.`, {
774
- index,
775
- timeout
776
- });
777
- const continueOnError = value['continue-on-error'];
778
- if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
779
- index,
780
- continueOnError
781
- });
782
- return {
783
- ...void 0 === timeout ? {} : {
784
- timeoutMs: timeout
785
- },
786
- continueOnError: continueOnError ?? false
787
- };
788
- }
789
- function validateCommonNodeInput(input, index) {
790
- if (void 0 !== input.prompt && 'string' != typeof input.prompt) throw new WorkflowParseError(`${formatStep(index)} "prompt" must be a string.`, {
791
- index,
792
- prompt: input.prompt
793
- });
794
- }
795
- function normalizeStep(value, index = 0) {
796
- if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
797
- index
798
- });
799
- const entries = Object.entries(value);
800
- if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
801
- index,
802
- nodes: Object.keys(value)
803
- });
804
- const [node, rawValue] = entries[0];
805
- if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
806
- index
807
- });
808
- if ('string' == typeof rawValue) return {
809
- node,
810
- input: {
811
- prompt: rawValue
812
- },
813
- meta: {
814
- continueOnError: false
815
- }
816
- };
817
- if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
818
- index,
819
- node
820
- });
821
- const { $: rawMeta, ...input } = rawValue;
822
- validateCommonNodeInput(input, index);
823
- return {
824
- node,
825
- input,
826
- meta: normalizeMeta(rawMeta, index)
827
- };
828
- }
829
- function normalizeSteps(steps) {
830
- if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
831
- return steps.map((step, index)=>normalizeStep(step, index));
832
- }
833
762
  const assertJsonSerializable = (value, label, node)=>{
834
763
  const visit = (candidate, path, seen)=>{
835
764
  if (null === candidate || 'string' == typeof candidate || 'boolean' == typeof candidate) return;
@@ -852,45 +781,6 @@ const assertJsonSerializable = (value, label, node)=>{
852
781
  throw new NodeExecutionError(node, error);
853
782
  }
854
783
  };
855
- const cloneAndFreezeJson = (value)=>{
856
- if (Array.isArray(value)) return Object.freeze(value.map(cloneAndFreezeJson));
857
- if ('object' == typeof value && null !== value) return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child])=>[
858
- key,
859
- cloneAndFreezeJson(child)
860
- ])));
861
- return value;
862
- };
863
- const toHistoryEntry = (result, scope)=>{
864
- assertJsonSerializable(result.input, 'input', result.node);
865
- if (result.output?.data !== void 0) assertJsonSerializable(result.output.data, 'output.data', result.node);
866
- return Object.freeze({
867
- scope,
868
- phase: result.phase,
869
- stepIndex: result.stepIndex,
870
- node: result.node,
871
- input: cloneAndFreezeJson(result.input),
872
- ...'string' == typeof result.input?.prompt ? {
873
- intent: result.input.prompt
874
- } : {},
875
- status: 'success' === result.status ? 'passed' : 'failed',
876
- ...result.output?.summary === void 0 ? {} : {
877
- summary: result.output.summary
878
- },
879
- ...result.output?.data === void 0 ? {} : {
880
- data: cloneAndFreezeJson(result.output.data)
881
- },
882
- ...result.error ? {
883
- error: Object.freeze({
884
- name: result.error.name,
885
- message: result.error.message
886
- })
887
- } : {}
888
- });
889
- };
890
- const createHistory = (prefix, results, scope)=>Object.freeze([
891
- ...prefix,
892
- ...results.map((result)=>toHistoryEntry(result, scope))
893
- ]);
894
784
  function validateNodeOutput(output, node) {
895
785
  if (void 0 === output) return;
896
786
  if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
@@ -912,7 +802,6 @@ function createStepResultBase(step, startedAt, phase, stepIndex) {
912
802
  };
913
803
  }
914
804
  async function executeNode(step, execute, phase, stepIndex, options) {
915
- validateCommonNodeInput(step.input, stepIndex);
916
805
  const startedAt = new Date();
917
806
  const abortController = new AbortController();
918
807
  const parentSignal = options.parentSignal;
@@ -996,7 +885,6 @@ async function executeStep(step, node, target, context, execution = {}) {
996
885
  $: step.meta,
997
886
  signal,
998
887
  context,
999
- history: execution.history ?? Object.freeze([]),
1000
888
  onTeardown: (teardown)=>{
1001
889
  if (!execution.onTeardown) throw new NodeExecutionError(step.node, new Error('The current execution scope cannot register teardown.'));
1002
890
  execution.onTeardown(step.node, teardown);
@@ -1033,7 +921,6 @@ function createDocumentRuntime(document, options) {
1033
921
  const startedAt = new Date();
1034
922
  const beforeAll = [];
1035
923
  const afterAll = [];
1036
- const completedNodes = [];
1037
924
  const nodes = {
1038
925
  beforeAll: document.lifecycle.beforeAll.map((step)=>options.resolveNode(step.node)),
1039
926
  afterAll: document.lifecycle.afterAll.map((step)=>options.resolveNode(step.node))
@@ -1064,7 +951,7 @@ function createDocumentRuntime(document, options) {
1064
951
  projectId: document.projectId,
1065
952
  projectName: project.name,
1066
953
  sourcePath: document.sourcePath,
1067
- status: completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
954
+ status: beforeAll.some((step)=>'failed' === step.status) || afterAll.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
1068
955
  startedAt: startedAt.toISOString(),
1069
956
  endedAt: endedAt.toISOString(),
1070
957
  durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
@@ -1093,10 +980,7 @@ function createDocumentRuntime(document, options) {
1093
980
  projectName: project.name,
1094
981
  sourcePath: document.sourcePath,
1095
982
  phase,
1096
- stepIndex,
1097
- completedNodes: Object.freeze([
1098
- ...completedNodes
1099
- ])
983
+ stepIndex
1100
984
  };
1101
985
  const stepInfo = {
1102
986
  scope: 'document',
@@ -1109,7 +993,6 @@ function createDocumentRuntime(document, options) {
1109
993
  scope: 'document',
1110
994
  document: documentContext
1111
995
  }, context, {
1112
- history: createHistory([], completedNodes, 'document'),
1113
996
  signal: executionSignal,
1114
997
  defaultTimeoutMs: options.defaultTimeoutMs,
1115
998
  onTeardown (node, teardown) {
@@ -1132,7 +1015,6 @@ function createDocumentRuntime(document, options) {
1132
1015
  });
1133
1016
  await options.onStepResult?.(stepInfo, result);
1134
1017
  results.push(result);
1135
- completedNodes.push(result);
1136
1018
  if ('failed' === result.status && !result.continuedAfterError) break;
1137
1019
  }
1138
1020
  };
@@ -1140,9 +1022,6 @@ function createDocumentRuntime(document, options) {
1140
1022
  get context () {
1141
1023
  return context;
1142
1024
  },
1143
- get history () {
1144
- return createHistory([], completedNodes, 'document');
1145
- },
1146
1025
  get canRunCases () {
1147
1026
  return started && !signal.aborted && !beforeAll.some((step)=>'failed' === step.status);
1148
1027
  },
@@ -1202,7 +1081,6 @@ async function runCollectedCase(collectedCase, options) {
1202
1081
  const beforeEach = [];
1203
1082
  const steps = [];
1204
1083
  const afterEach = [];
1205
- const completedNodes = [];
1206
1084
  const teardownStack = [];
1207
1085
  let acceptingTeardowns = true;
1208
1086
  const onTeardown = (node, teardown)=>{
@@ -1231,13 +1109,7 @@ async function runCollectedCase(collectedCase, options) {
1231
1109
  sourcePath: collectedCase.sourcePath,
1232
1110
  caseIndex: collectedCase.caseIndex,
1233
1111
  phase,
1234
- stepIndex,
1235
- completedSteps: Object.freeze([
1236
- ...steps
1237
- ]),
1238
- completedNodes: Object.freeze([
1239
- ...completedNodes
1240
- ])
1112
+ stepIndex
1241
1113
  };
1242
1114
  const stepInfo = {
1243
1115
  scope: 'case',
@@ -1250,14 +1122,12 @@ async function runCollectedCase(collectedCase, options) {
1250
1122
  scope: 'case',
1251
1123
  case: caseContext
1252
1124
  }, options.context, {
1253
- history: createHistory(options.documentHistory ?? [], completedNodes, 'case'),
1254
1125
  signal,
1255
1126
  defaultTimeoutMs: options.defaultTimeoutMs,
1256
1127
  onTeardown
1257
1128
  });
1258
1129
  await options.onStepResult?.(stepInfo, result);
1259
1130
  results.push(result);
1260
- completedNodes.push(result);
1261
1131
  if ('failed' === result.status && !result.continuedAfterError) break;
1262
1132
  }
1263
1133
  };
@@ -1297,7 +1167,7 @@ async function runCollectedCase(collectedCase, options) {
1297
1167
  name: collectedCase.definition.name,
1298
1168
  sourcePath: collectedCase.sourcePath,
1299
1169
  caseIndex: collectedCase.caseIndex,
1300
- status: completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
1170
+ status: beforeEach.some((step)=>'failed' === step.status) || steps.some((step)=>'failed' === step.status) || afterEach.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
1301
1171
  beforeEach,
1302
1172
  steps,
1303
1173
  afterEach,
@@ -1367,7 +1237,6 @@ async function runWorkflowDocument(document, options) {
1367
1237
  context: runtime.context,
1368
1238
  projectName,
1369
1239
  attemptIndex,
1370
- documentHistory: runtime.history,
1371
1240
  signal: options.signal,
1372
1241
  defaultTimeoutMs: options.defaultTimeoutMs,
1373
1242
  onStepStart: options.onStepStart,
@@ -1414,6 +1283,89 @@ async function runWorkflowDocument(document, options) {
1414
1283
  };
1415
1284
  }
1416
1285
  const external_js_yaml_namespaceObject = require("js-yaml");
1286
+ const supportedMetaKeys = new Set([
1287
+ 'timeout',
1288
+ 'continue-on-error'
1289
+ ]);
1290
+ function isMapping(value) {
1291
+ return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
1292
+ }
1293
+ function formatStep(index) {
1294
+ return `step ${index + 1}`;
1295
+ }
1296
+ function normalizeMeta(value, index) {
1297
+ if (void 0 === value) return {
1298
+ continueOnError: false
1299
+ };
1300
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
1301
+ index
1302
+ });
1303
+ for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
1304
+ index,
1305
+ key
1306
+ });
1307
+ const timeout = value.timeout;
1308
+ 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.`, {
1309
+ index,
1310
+ timeout
1311
+ });
1312
+ const continueOnError = value['continue-on-error'];
1313
+ if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
1314
+ index,
1315
+ continueOnError
1316
+ });
1317
+ return {
1318
+ ...void 0 === timeout ? {} : {
1319
+ timeoutMs: timeout
1320
+ },
1321
+ continueOnError: continueOnError ?? false
1322
+ };
1323
+ }
1324
+ function normalizeStep(value, index = 0, resolveNode) {
1325
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
1326
+ index
1327
+ });
1328
+ const entries = Object.entries(value);
1329
+ if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
1330
+ index,
1331
+ nodes: Object.keys(value)
1332
+ });
1333
+ const [node, rawValue] = entries[0];
1334
+ if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
1335
+ index
1336
+ });
1337
+ if ('string' == typeof rawValue) {
1338
+ const resolvedNode = resolveNode?.(node);
1339
+ const stringInputKey = resolvedNode ? resolvedNode.stringInputKey : 'prompt';
1340
+ if (false === stringInputKey || void 0 === stringInputKey) throw new WorkflowParseError(`${formatStep(index)} node "${node}" does not accept string shorthand.`, {
1341
+ index,
1342
+ node
1343
+ });
1344
+ return {
1345
+ node,
1346
+ input: {
1347
+ [stringInputKey]: rawValue
1348
+ },
1349
+ meta: {
1350
+ continueOnError: false
1351
+ }
1352
+ };
1353
+ }
1354
+ if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
1355
+ index,
1356
+ node
1357
+ });
1358
+ const { $: rawMeta, ...input } = rawValue;
1359
+ return {
1360
+ node,
1361
+ input,
1362
+ meta: normalizeMeta(rawMeta, index)
1363
+ };
1364
+ }
1365
+ function normalizeSteps(steps, resolveNode) {
1366
+ if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
1367
+ return steps.map((step, index)=>normalizeStep(step, index, resolveNode));
1368
+ }
1417
1369
  const interpolationExpression = /\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}|\$\{([^{}]+)\}/g;
1418
1370
  const exactEnvExpression = /^\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}$/;
1419
1371
  const exactProjectExpression = /^\$\{([^{}]+)\}$/;
@@ -1517,7 +1469,7 @@ function collectWorkflowDocument(source, options) {
1517
1469
  if (!Array.isArray(value)) throw new WorkflowParseError(`Workflow document ${phase} must be an array.`, {
1518
1470
  phase
1519
1471
  });
1520
- return normalizeSteps(value).map((normalized, stepIndex)=>{
1472
+ return normalizeSteps(value, options.resolveNode).map((normalized, stepIndex)=>{
1521
1473
  const resolved = options.resolveNode(normalized.node);
1522
1474
  if (!resolved) throw new WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
1523
1475
  phase,
@@ -1553,7 +1505,7 @@ function collectWorkflowDocument(source, options) {
1553
1505
  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.`, {
1554
1506
  caseIndex
1555
1507
  });
1556
- const steps = normalizeSteps(definition.steps).map((normalized, stepIndex)=>{
1508
+ const steps = normalizeSteps(definition.steps, options.resolveNode).map((normalized, stepIndex)=>{
1557
1509
  if (!options.resolveNode(normalized.node)) throw new WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
1558
1510
  caseIndex,
1559
1511
  stepIndex,