@midscene/test 1.13.2-beta-20260923035443.0 → 1.13.2

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.
@@ -10,7 +10,7 @@ import { JSON_SCHEMA, load } from "js-yaml";
10
10
  import { createHash, randomUUID } from "node:crypto";
11
11
  import { mkdir, writeFile } from "node:fs/promises";
12
12
  import { setMaxListeners } from "node:events";
13
- import { TestRunReportAssembler, calculateTestRunHealth } from "@midscene/core/report";
13
+ import { TestRunReportAssembler } from "@midscene/core/report";
14
14
  import { getDebug } from "@midscene/shared/logger";
15
15
  var __webpack_modules__ = {
16
16
  "node:fs" (module) {
@@ -20,7 +20,9 @@ var __webpack_modules__ = {
20
20
  module.exports = __rspack_external_node_path_c5b9b54f;
21
21
  },
22
22
  "./package.json" (module) {
23
- module.exports = JSON.parse('{"rE":"1.13.2-beta-20260923035443.0"}');
23
+ module.exports = {
24
+ rE: "1.13.2"
25
+ };
24
26
  }
25
27
  };
26
28
  var __webpack_module_cache__ = {};
@@ -96,7 +98,7 @@ var external_node_fs_ = __webpack_require__("node:fs");
96
98
  var external_node_path_ = __webpack_require__("node:path");
97
99
  var package_0 = __webpack_require__("./package.json");
98
100
  const GENERATED_COMMENT = '<!-- Generated by `midscene-test nodes`. Do not edit directly. -->';
99
- const sortNodesForReference = (nodes)=>{
101
+ const sortNodesForSpec = (nodes)=>{
100
102
  const priority = (name)=>'aiAct' === name ? 0 : 'aiAssert' === name ? 1 : 2;
101
103
  return [
102
104
  ...nodes
@@ -116,7 +118,7 @@ const renderNodeOverview = (nodes)=>{
116
118
  const renderWorkflowLocations = (location)=>{
117
119
  const sections = [
118
120
  '## Where to use these Nodes',
119
- 'Use these Nodes in Workflow Documents (`.yaml` or `.yml` files) selected by an Execution Project. Case file patterns and the config file path below are relative to this reference file. Case file patterns use glob syntax. A custom `--config` path does not change where case files are selected.',
121
+ 'Use these Nodes in Workflow Documents (`.yaml` or `.yml` files) selected by an Execution Project. Case file patterns and the config file path below are relative to this spec file. Case file patterns use glob syntax. A custom `--config` path does not change where case files are selected.',
120
122
  'Projects created by `midscene-test create` select `cases/**/*.{yaml,yml}` by default, so you can write test cases in `cases/example.yaml` or subdirectories of `cases/`. If an Execution Project has no `files` configuration, Midscene Test searches `**/*.{yaml,yml}` recursively instead. Check `projects[].files` in your configuration before choosing a file location.'
121
123
  ];
122
124
  if (location) {
@@ -206,13 +208,14 @@ const renderNode = (node, warnings)=>{
206
208
  }
207
209
  return sections.join('\n\n');
208
210
  };
209
- const renderNodeReference = (nodes, location)=>{
211
+ const renderNodeSpec = (nodes, location)=>{
210
212
  const warnings = [];
211
- const sortedNodes = sortNodesForReference(nodes);
213
+ const sortedNodes = sortNodesForSpec(nodes);
212
214
  const sections = [
213
215
  GENERATED_COMMENT,
214
- '# Midscene Test Node Reference',
215
- 'This document describes the nodes registered by the current Test Project.',
216
+ '# Midscene Test Node Spec',
217
+ 'This document describes the Nodes available in YAML test files (`.yaml` or `.yml`) for the Midscene Test framework (`@midscene/test`). It lists the Nodes registered by the current Test Project, their inputs, and how to call them in test steps and lifecycle hooks.',
218
+ "Learn about the framework and YAML test authoring:\n\n- [Midscene Test overview](https://midscenejs.com/midscene-test/overview): framework concepts and the role of Nodes.\n- [Create and use test projects](https://midscenejs.com/midscene-test/use): write YAML cases, run tests, and inspect results.\n- [Configure test projects](https://midscenejs.com/midscene-test/configuration): register Nodes, select case files, and manage runtime resources.",
216
219
  renderNodeOverview(sortedNodes),
217
220
  renderWorkflowLocations(location),
218
221
  WORKFLOW_STEP_CONTRACT
@@ -1218,32 +1221,13 @@ const asNotRun = (documentId, collectedCase, projectName, reason)=>({
1218
1221
  notRunReason: reason
1219
1222
  });
1220
1223
  const summarize = (projects)=>{
1221
- const healthCases = projects.flatMap((project)=>{
1222
- const documentAttemptByRunId = new Map(project.documents.map((document)=>[
1223
- document.documentRunId,
1224
- document.attemptIndex ?? 0
1225
- ]));
1226
- return latestById(project.cases, (item)=>item.caseId).map((item)=>({
1227
- status: item.status,
1228
- ...item.run ? {
1229
- run: item.run
1230
- } : {},
1231
- ...item.attempts ? {
1232
- attempts: item.attempts
1233
- } : {},
1234
- retryCount: item.documentRunId ? documentAttemptByRunId.get(item.documentRunId) ?? 0 : 0
1235
- }));
1236
- });
1224
+ const cases = projects.flatMap((project)=>latestById(project.cases, (item)=>item.caseId));
1237
1225
  const documents = projects.flatMap((project)=>latestById(project.documents, (item)=>item.documentId));
1238
- const health = calculateTestRunHealth(healthCases);
1239
1226
  return {
1240
- total: health.total,
1241
- passed: health.passed,
1242
- failed: health.failed,
1243
- notRun: health.notRun,
1244
- passedAfterRetry: health.passedAfterRetry,
1245
- finalPassRate: health.finalPassRate,
1246
- firstPassRate: health.firstPassRate,
1227
+ total: cases.length,
1228
+ passed: cases.filter((item)=>'success' === item.status).length,
1229
+ failed: cases.filter((item)=>'failed' === item.status).length,
1230
+ notRun: cases.filter((item)=>'not-run' === item.status).length,
1247
1231
  filtered: projects.reduce((total, project)=>total + project.filteredCaseCount, 0),
1248
1232
  collectionErrors: projects.reduce((total, project)=>total + project.collectionErrors.length, 0),
1249
1233
  documentFailures: documents.filter((document)=>'failed' === document.status).length,
@@ -1603,24 +1587,44 @@ const runNodesCommand = async (options, io)=>{
1603
1587
  const projectName = options.projectNames?.[0];
1604
1588
  const selectedProjects = projectName ? project.projects.filter((candidate)=>candidate.name === projectName) : project.projects;
1605
1589
  if (0 === selectedProjects.length) throw new Error(`Unknown Midscene project: ${projectName}`);
1606
- const registry = selectedProjects[0].nodes;
1607
- if (selectedProjects.some((candidate)=>candidate.nodes.names().length !== registry.names().length || candidate.nodes.definitions().some((node)=>registry.get(node.name) !== node))) throw new Error('Projects have different Nodes. Use nodes --project <name> to select one.');
1608
- const nodes = sortNodesForReference(registry.definitions());
1609
- const document = renderNodeReference(nodes, {
1610
- configPath: configPath ? (0, external_node_path_.relative)(configSearchRoot, configPath).split(external_node_path_.sep).join('/') : void 0,
1611
- projects: selectedProjects.map((executionProject)=>({
1612
- name: executionProject.name,
1613
- files: executionProject.files ?? DEFAULT_TEST_FILE_SELECTION
1614
- }))
1590
+ const filenames = new Map();
1591
+ const owners = new Map();
1592
+ for (const executionProject of project.projects){
1593
+ const safeName = executionProject.name.normalize('NFC').replace(/[<>:"/\\|?*\u0000-\u001f]/g, '-').replace(/[. ]+$/g, '-');
1594
+ const filename = project.hasExplicitProjects ? `midscene-node-spec.${safeName}.md` : 'midscene-node-spec.md';
1595
+ const key = filename.toLowerCase();
1596
+ const owner = owners.get(key);
1597
+ if (void 0 !== owner) throw new Error(`Projects "${owner}" and "${executionProject.name}" generate the same Node Spec filename: ${filename}`);
1598
+ owners.set(key, executionProject.name);
1599
+ filenames.set(executionProject.name, filename);
1600
+ }
1601
+ const specs = selectedProjects.map((executionProject)=>{
1602
+ const nodes = sortNodesForSpec(executionProject.nodes.definitions());
1603
+ const document = renderNodeSpec(nodes, {
1604
+ configPath: configPath ? (0, external_node_path_.relative)(configSearchRoot, configPath).split(external_node_path_.sep).join('/') : void 0,
1605
+ projects: [
1606
+ {
1607
+ name: executionProject.name,
1608
+ files: executionProject.files ?? DEFAULT_TEST_FILE_SELECTION
1609
+ }
1610
+ ]
1611
+ });
1612
+ return {
1613
+ executionProject,
1614
+ nodes,
1615
+ document
1616
+ };
1615
1617
  });
1616
- for (const warning of document.warnings)io.error(`midscene-test nodes: ${warning}`);
1617
- const referencePath = (0, external_node_path_.resolve)(configSearchRoot, 'midscene-node-reference.md');
1618
- (0, external_node_fs_.writeFileSync)(referencePath, document.markdown);
1619
- if (projectName) io.log(`Execution Project: ${projectName}`);
1620
- io.log(`Registered Nodes (${nodes.length}):`);
1621
- if (0 === nodes.length) io.log('No nodes are registered by the current Test Project.');
1622
- for (const node of nodes)io.log(`- ${node.name}: ${node.description?.trim() || "Description not declared."}`);
1623
- io.log(`\nNode reference generated: ${referencePath}`);
1618
+ for (const { executionProject, nodes, document } of specs){
1619
+ for (const warning of document.warnings)io.error(`midscene-test nodes: ${warning}`);
1620
+ const specPath = (0, external_node_path_.resolve)(configSearchRoot, filenames.get(executionProject.name));
1621
+ (0, external_node_fs_.writeFileSync)(specPath, document.markdown);
1622
+ if (project.hasExplicitProjects || projectName) io.log(`Execution Project: ${executionProject.name}`);
1623
+ io.log(`Registered Nodes (${nodes.length}):`);
1624
+ if (0 === nodes.length) io.log('No nodes are registered by the current Test Project.');
1625
+ for (const node of nodes)io.log(`- ${node.name}: ${node.description?.trim() || "Description not declared."}`);
1626
+ io.log(`\nNode Spec generated: ${specPath}`);
1627
+ }
1624
1628
  };
1625
1629
  async function runTestCli(args, io = defaultCliIO) {
1626
1630
  try {