@midscene/cli 1.13.1-beta-20260922030100.0 → 1.13.1

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.
@@ -15,6 +15,7 @@ import { stripVTControlCharacters } from "node:util";
15
15
  import node_process from "node:process";
16
16
  import { createRequire } from "node:module";
17
17
  import { pathToFileURL } from "node:url";
18
+ import { serializeError } from "@midscene/shared/agent-tools/error-formatter";
18
19
  var __webpack_modules__ = {
19
20
  "../../node_modules/.pnpm/ansi-styles@4.3.0/node_modules/ansi-styles/index.js" (module, __unused_rspack_exports, __webpack_require__) {
20
21
  module = __webpack_require__.nmd(module);
@@ -2635,6 +2636,7 @@ function printExecutionPlan(config) {
2635
2636
  console.log(` Continue on error: ${config.continueOnError}`);
2636
2637
  console.log(` Retry: ${config.retry ?? 0}`);
2637
2638
  console.log(` Share browser context: ${config.shareBrowserContext ?? false}`);
2639
+ console.log(` Reuse browser page: ${config.reusePage ?? false}`);
2638
2640
  console.log(` Summary output: ${config.summary}`);
2639
2641
  }
2640
2642
  function printExecutionFinished() {
@@ -3329,11 +3331,22 @@ const assertBrowserContextUsage = (setupContext, allContexts, shareBrowserContex
3329
3331
  }
3330
3332
  if (setupContext && !shareBrowserContext && 'puppeteer-web' === resolveBatchRuntimeTarget(setupContext.executionConfig)) throw new Error(`Puppeteer Web setup "${setupContext.file}" requires shareBrowserContext: true so its browser state can be shared with the main files.`);
3331
3333
  };
3332
- const createSharedBrowserRuntime = async (browser, browserContextOptions)=>{
3334
+ const createSharedBrowserRuntime = async (browser, browserContextOptions, reusePage = false)=>{
3333
3335
  const browserContext = await browser.createBrowserContext(browserContextOptions);
3334
- return {
3335
- browserContext
3336
- };
3336
+ try {
3337
+ const page = reusePage ? await browserContext.newPage() : void 0;
3338
+ return {
3339
+ browserContext,
3340
+ page
3341
+ };
3342
+ } catch (error) {
3343
+ try {
3344
+ await browserContext.close();
3345
+ } catch (cleanupError) {
3346
+ batchWarning('failed to close the shared browser context after page creation failed; preserving the original error', cleanupError);
3347
+ }
3348
+ throw error;
3349
+ }
3337
3350
  };
3338
3351
  class YamlBatchExecutor {
3339
3352
  async run(options = {}) {
@@ -3418,10 +3431,11 @@ class YamlBatchExecutor {
3418
3431
  sharedRuntime.current = null;
3419
3432
  await staleRuntime.browserContext.close();
3420
3433
  }
3421
- sharedRuntime.current = await createSharedBrowserRuntime(browser, browserContextOptions);
3434
+ sharedRuntime.current = await createSharedBrowserRuntime(browser, browserContextOptions, this.config.reusePage);
3422
3435
  for (const context of allContexts){
3423
3436
  context.options.browser = browser;
3424
3437
  context.options.browserContext = sharedRuntime.current.browserContext;
3438
+ if (sharedRuntime.current.page) context.options.page = sharedRuntime.current.page;
3425
3439
  }
3426
3440
  };
3427
3441
  await resetSetupRuntime();
@@ -3701,6 +3715,8 @@ class YamlBatchExecutor {
3701
3715
  }, (_, index)=>`batch-occurrence-${index + 1}`);
3702
3716
  if (this.caseIds.length !== occurrenceCount) throw new Error(`Batch occurrence identity mismatch: expected ${occurrenceCount} case ID(s), received ${this.caseIds.length}`);
3703
3717
  if (new Set(this.caseIds).size !== this.caseIds.length) throw new Error('Batch occurrence case IDs must be unique');
3718
+ if (config.reusePage && !config.shareBrowserContext) throw new Error('reusePage requires shareBrowserContext: true');
3719
+ if (config.reusePage && 1 !== config.concurrent) throw new Error('reusePage requires concurrent: 1');
3704
3720
  }
3705
3721
  }
3706
3722
  async function runYamlBatch(config, options = {}) {
@@ -3950,27 +3966,23 @@ const createYamlProgressReporter = ()=>({
3950
3966
  }
3951
3967
  });
3952
3968
  const RSTEST_YAML_CASE_IDS_META_KEY = 'midsceneYamlCaseIds';
3953
- const formatRunError = (error)=>error.stack || `${error.name}: ${error.message}`;
3969
+ const formatRunError = (error)=>error.stack || `${error.name || 'Error'}: ${error.message}`;
3954
3970
  const collectRunErrors = (result)=>{
3955
3971
  const messages = [];
3956
3972
  const push = (error, label)=>{
3957
3973
  const formatted = formatRunError(error);
3958
3974
  messages.push(label ? `${label}: ${formatted}` : formatted);
3959
3975
  };
3960
- for (const file of result.files ?? []){
3976
+ for (const file of result.results){
3961
3977
  for (const error of file.errors ?? [])push(error, file.name || file.testPath);
3962
3978
  for (const testResult of file.results ?? [])for (const error of testResult.errors ?? [])push(error, testResult.name);
3963
3979
  }
3964
- for (const error of result.unhandledErrors ?? [])push(error);
3980
+ for (const error of result.unhandledErrors)push(error);
3965
3981
  return Array.from(new Set(messages));
3966
3982
  };
3967
3983
  const rstest_runner_errorMessage = (error)=>error.message || error.name || 'YAML case failed';
3968
3984
  const mapRunErrorsToCases = (project, result)=>{
3969
- const casesById = new Map(project.cases.map((item)=>[
3970
- item.caseId,
3971
- item
3972
- ]));
3973
- const knownCaseIds = new Set(casesById.keys());
3985
+ const knownCaseIds = new Set(project.cases.map((item)=>item.caseId));
3974
3986
  const errors = new Map();
3975
3987
  const add = (caseId, message)=>{
3976
3988
  if (knownCaseIds.has(caseId) && message && !errors.has(caseId)) errors.set(caseId, message);
@@ -3993,7 +4005,7 @@ const mapRunErrorsToCases = (project, result)=>{
3993
4005
  const value = testResult.meta?.[RSTEST_YAML_CASE_IDS_META_KEY];
3994
4006
  return Array.isArray(value) ? value.filter((item)=>'string' == typeof item) : [];
3995
4007
  };
3996
- for (const file of result.files ?? []){
4008
+ for (const file of result.results){
3997
4009
  const moduleCaseIds = findModuleCaseIds(file);
3998
4010
  for (const error of file.errors ?? [])addCases(moduleCaseIds, rstest_runner_errorMessage(error));
3999
4011
  for (const testResult of file.results ?? []){
@@ -4001,18 +4013,13 @@ const mapRunErrorsToCases = (project, result)=>{
4001
4013
  for (const error of testResult.errors ?? [])addCases(caseIds.length ? caseIds : moduleCaseIds, rstest_runner_errorMessage(error));
4002
4014
  }
4003
4015
  }
4004
- if (1 === project.modules.length && result.unhandledErrors?.length) {
4016
+ if (result.unhandledErrors.length) {
4005
4017
  const message = rstest_runner_errorMessage(result.unhandledErrors[0]);
4006
- for (const caseId of project.modules[0].caseIds){
4007
- const item = casesById.get(caseId);
4008
- if (!errors.has(caseId) || item && !existsSync(item.resultFile)) errors.set(caseId, message);
4009
- }
4018
+ for (const item of project.cases)if (!errors.has(item.caseId) && !existsSync(item.resultFile)) errors.set(item.caseId, message);
4010
4019
  }
4011
4020
  return errors;
4012
4021
  };
4013
- const recordUnreportedCaseFailures = (project, result)=>{
4014
- if (!project.cases.length) return;
4015
- const caseErrors = mapRunErrorsToCases(project, result);
4022
+ const writePendingCaseFailures = (project, caseErrors)=>{
4016
4023
  for (const item of project.cases){
4017
4024
  if (existsSync(item.resultFile)) continue;
4018
4025
  const error = caseErrors.get(item.caseId);
@@ -4034,13 +4041,13 @@ const recordUnreportedCaseFailures = (project, result)=>{
4034
4041
  }
4035
4042
  };
4036
4043
  async function runRstestYamlProject(options) {
4037
- const [{ runRstest }, { rspack }] = await Promise.all([
4044
+ const [{ createRstest }, { rspack }] = await Promise.all([
4038
4045
  import("@rstest/core/api"),
4039
4046
  import(pathToFileURL(resolvePackageFromRstestCore('@rsbuild/core')).href)
4040
4047
  ]);
4041
4048
  const { project } = options;
4042
4049
  const maxConcurrency = void 0 !== project.maxConcurrency ? Math.max(1, project.maxConcurrency) : void 0;
4043
- const inlineConfig = {
4050
+ const config = {
4044
4051
  root: project.projectDir,
4045
4052
  include: project.modules.map((item)=>item.id),
4046
4053
  testEnvironment: 'node',
@@ -4071,18 +4078,31 @@ async function runRstestYamlProject(options) {
4071
4078
  }
4072
4079
  }
4073
4080
  };
4074
- const result = await runRstest({
4075
- cwd: options.cwd || project.projectDir,
4076
- inlineConfig
4077
- });
4078
- if (!result.ok) {
4079
- recordUnreportedCaseFailures(project, result);
4081
+ let result;
4082
+ try {
4083
+ const rstest = await createRstest({
4084
+ cwd: options.cwd || project.projectDir,
4085
+ config
4086
+ });
4087
+ result = await rstest.run();
4088
+ } catch (error) {
4089
+ const serialized = serializeError(error);
4090
+ writePendingCaseFailures(project, new Map(project.cases.map((item)=>[
4091
+ item.caseId,
4092
+ serialized.message
4093
+ ])));
4094
+ if ('pipe' !== options.stdio) console.error(`\nYAML execution failed:\n${formatRunError(serialized)}`);
4095
+ return 1;
4096
+ }
4097
+ const ok = 'pass' === result.status;
4098
+ if (!ok) {
4099
+ writePendingCaseFailures(project, mapRunErrorsToCases(project, result));
4080
4100
  if ('pipe' !== options.stdio) {
4081
4101
  const runErrors = collectRunErrors(result);
4082
4102
  if (runErrors.length) console.error(`\nYAML execution failed:\n${runErrors.join('\n\n')}`);
4083
4103
  }
4084
4104
  }
4085
- return result.ok ? 0 : 1;
4105
+ return ok ? 0 : 1;
4086
4106
  }
4087
4107
  const defaultInProcessRunner = (config)=>new BatchRunner(config).run();
4088
4108
  async function runConfigInMainProcess(config, commandOptions) {