@agent-scope/cli 1.20.2 → 1.20.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.
package/dist/cli.js CHANGED
@@ -2333,6 +2333,7 @@ function createManifestCommand() {
2333
2333
 
2334
2334
  // src/instrument/hooks.ts
2335
2335
  import { resolve as resolve5 } from "path";
2336
+ import { serialize } from "@agent-scope/core";
2336
2337
  import { getBrowserEntryScript as getBrowserEntryScript2 } from "@agent-scope/playwright";
2337
2338
  import { Command as Cmd } from "commander";
2338
2339
  import { chromium as chromium2 } from "playwright";
@@ -2374,26 +2375,7 @@ function buildHookInstrumentationScript() {
2374
2375
  // Walk fiber tree and collect hook data per component
2375
2376
  var components = [];
2376
2377
 
2377
- function serializeValue(v) {
2378
- if (v === null) return null;
2379
- if (v === undefined) return undefined;
2380
- var t = typeof v;
2381
- if (t === "string" || t === "number" || t === "boolean") return v;
2382
- if (t === "function") return "[function " + (v.name || "anonymous") + "]";
2383
- if (Array.isArray(v)) {
2384
- try { return v.map(serializeValue); } catch(e) { return "[Array]"; }
2385
- }
2386
- if (t === "object") {
2387
- try {
2388
- var keys = Object.keys(v).slice(0, 5);
2389
- var out = {};
2390
- for (var k of keys) { out[k] = serializeValue(v[k]); }
2391
- if (Object.keys(v).length > 5) out["..."] = "(truncated)";
2392
- return out;
2393
- } catch(e) { return "[Object]"; }
2394
- }
2395
- return String(v);
2396
- }
2378
+ var serializeValue = __SCOPE_SERIALIZE_VALUE__;
2397
2379
 
2398
2380
  // Hook node classifiers (mirrors hooks-extractor.ts logic)
2399
2381
  var HookLayout = 0b00100;
@@ -2614,7 +2596,10 @@ async function runHooksProfiling(componentName, filePath, props) {
2614
2596
  if (renderError !== null) {
2615
2597
  throw new Error(`Component render error: ${renderError}`);
2616
2598
  }
2617
- const instrumentScript = buildHookInstrumentationScript();
2599
+ const instrumentScript = buildHookInstrumentationScript().replace(
2600
+ "__SCOPE_SERIALIZE_VALUE__",
2601
+ serialize.toString()
2602
+ );
2618
2603
  const raw = await page.evaluate(instrumentScript);
2619
2604
  const result = raw;
2620
2605
  if (result.error) {
@@ -2816,44 +2801,79 @@ function buildProfilingCollectScript() {
2816
2801
  `;
2817
2802
  }
2818
2803
  async function replayInteraction(page, steps) {
2819
- for (const step of steps) {
2804
+ let executed = 0;
2805
+ const skippedInteractions = [];
2806
+ const skip = (index, step, reason) => {
2807
+ skippedInteractions.push({ index, action: step.action, target: step.target, reason });
2808
+ };
2809
+ for (const [index, step] of steps.entries()) {
2820
2810
  switch (step.action) {
2821
2811
  case "click":
2822
- if (step.target) {
2823
- await page.click(step.target, { timeout: 5e3 }).catch(() => {
2824
- process.stderr.write(` \u26A0 click target "${step.target}" not found, skipping
2812
+ if (!step.target) {
2813
+ skip(index, step, "missing_target");
2814
+ break;
2815
+ }
2816
+ try {
2817
+ await page.click(step.target, { timeout: 5e3 });
2818
+ executed++;
2819
+ } catch {
2820
+ process.stderr.write(` \u26A0 click target "${step.target}" not found, skipping
2825
2821
  `);
2826
- });
2822
+ skip(index, step, "not_found");
2827
2823
  }
2828
2824
  break;
2829
2825
  case "fill":
2830
- if (step.target && step.value !== void 0) {
2831
- await page.fill(step.target, step.value, { timeout: 5e3 }).catch(() => {
2832
- process.stderr.write(` \u26A0 fill target "${step.target}" not found, skipping
2826
+ if (!step.target) {
2827
+ skip(index, step, "missing_target");
2828
+ break;
2829
+ }
2830
+ if (step.value === void 0) {
2831
+ skip(index, step, "missing_value");
2832
+ break;
2833
+ }
2834
+ try {
2835
+ await page.fill(step.target, step.value, { timeout: 5e3 });
2836
+ executed++;
2837
+ } catch {
2838
+ process.stderr.write(` \u26A0 fill target "${step.target}" not found, skipping
2833
2839
  `);
2834
- });
2840
+ skip(index, step, "not_found");
2835
2841
  }
2836
2842
  break;
2837
2843
  case "hover":
2838
- if (step.target) {
2839
- await page.hover(step.target, { timeout: 5e3 }).catch(() => {
2840
- process.stderr.write(` \u26A0 hover target "${step.target}" not found, skipping
2844
+ if (!step.target) {
2845
+ skip(index, step, "missing_target");
2846
+ break;
2847
+ }
2848
+ try {
2849
+ await page.hover(step.target, { timeout: 5e3 });
2850
+ executed++;
2851
+ } catch {
2852
+ process.stderr.write(` \u26A0 hover target "${step.target}" not found, skipping
2841
2853
  `);
2842
- });
2854
+ skip(index, step, "not_found");
2843
2855
  }
2844
2856
  break;
2845
2857
  case "press":
2846
- if (step.value) {
2847
- await page.keyboard.press(step.value);
2858
+ if (!step.value) {
2859
+ skip(index, step, "missing_value");
2860
+ break;
2848
2861
  }
2862
+ await page.keyboard.press(step.value);
2863
+ executed++;
2849
2864
  break;
2850
2865
  case "wait":
2851
2866
  await page.waitForTimeout(step.delay ?? 500);
2867
+ executed++;
2868
+ break;
2869
+ default:
2870
+ skip(index, step, "unsupported");
2852
2871
  break;
2853
2872
  }
2854
2873
  }
2874
+ return { executed, skippedInteractions };
2855
2875
  }
2856
- function analyzeProfileFlags(totalRenders, wastedRenders, timing, layoutShifts) {
2876
+ function analyzeProfileFlags(totalRenders, wastedRenders, timing, layoutShifts, profileValidity) {
2857
2877
  const flags = /* @__PURE__ */ new Set();
2858
2878
  if (wastedRenders !== null && wastedRenders > 0 && wastedRenders / Math.max(1, totalRenders) > 0.3) {
2859
2879
  flags.add("WASTED_RENDER");
@@ -2864,7 +2884,7 @@ function analyzeProfileFlags(totalRenders, wastedRenders, timing, layoutShifts)
2864
2884
  if (layoutShifts.cumulativeScore > 0.1) {
2865
2885
  flags.add("LAYOUT_SHIFT_DETECTED");
2866
2886
  }
2867
- if (timing.js > 100) {
2887
+ if (profileValidity === "interaction" && timing.js > 100) {
2868
2888
  flags.add("SLOW_INTERACTION");
2869
2889
  }
2870
2890
  return [...flags];
@@ -2903,13 +2923,18 @@ async function runInteractionProfile(componentName, filePath, props, interaction
2903
2923
  throw new Error(`Profiling setup failed: ${setupData.error}`);
2904
2924
  }
2905
2925
  const jsStart = Date.now();
2926
+ let executedInteractions = 0;
2927
+ let skippedInteractions = [];
2906
2928
  if (interaction.length > 0) {
2907
2929
  process.stderr.write(` Replaying ${interaction.length} interaction step(s)\u2026
2908
2930
  `);
2909
- await replayInteraction(page, interaction);
2931
+ const replayResult = await replayInteraction(page, interaction);
2932
+ executedInteractions = replayResult.executed;
2933
+ skippedInteractions = replayResult.skippedInteractions;
2910
2934
  await page.waitForTimeout(300);
2911
2935
  }
2912
2936
  const jsDuration = Date.now() - jsStart;
2937
+ const profileValidity = interaction.length === 0 ? "no_interaction_requested" : executedInteractions === 0 ? "setup_only" : "interaction";
2913
2938
  const collected = await page.evaluate(buildProfilingCollectScript());
2914
2939
  const profileData = collected;
2915
2940
  if (profileData.error) {
@@ -2927,7 +2952,13 @@ async function runInteractionProfile(componentName, filePath, props, interaction
2927
2952
  const totalRenders = profileData.commitCount ?? 0;
2928
2953
  const uniqueComponents = profileData.uniqueComponents ?? 0;
2929
2954
  const wastedRenders = profileData.wastedRenders ?? null;
2930
- const flags = analyzeProfileFlags(totalRenders, wastedRenders, timing, layoutShifts);
2955
+ const flags = analyzeProfileFlags(
2956
+ totalRenders,
2957
+ wastedRenders,
2958
+ timing,
2959
+ layoutShifts,
2960
+ profileValidity
2961
+ );
2931
2962
  return {
2932
2963
  component: componentName,
2933
2964
  totalRenders,
@@ -2941,7 +2972,17 @@ async function runInteractionProfile(componentName, filePath, props, interaction
2941
2972
  timing,
2942
2973
  layoutShifts,
2943
2974
  flags,
2944
- interaction
2975
+ interaction,
2976
+ interactionSteps: {
2977
+ attempted: interaction.length,
2978
+ executed: executedInteractions,
2979
+ skipped: skippedInteractions.length
2980
+ },
2981
+ interactionStepsAttempted: interaction.length,
2982
+ interactionStepsExecuted: executedInteractions,
2983
+ interactionStepsSkipped: skippedInteractions.length,
2984
+ skippedInteractions,
2985
+ profileValidity
2945
2986
  };
2946
2987
  } finally {
2947
2988
  await browser.close();
@@ -3047,6 +3088,7 @@ function flattenSerializedValue(sv) {
3047
3088
  case "string":
3048
3089
  case "number":
3049
3090
  case "boolean":
3091
+ case "date":
3050
3092
  return v.value;
3051
3093
  case "object": {
3052
3094
  if (!Array.isArray(v.entries)) return {};
@@ -3899,7 +3941,7 @@ Examples:
3899
3941
  }
3900
3942
 
3901
3943
  // src/render-commands.ts
3902
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
3944
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
3903
3945
  import { resolve as resolve11 } from "path";
3904
3946
  import {
3905
3947
  ALL_CONTEXT_IDS,
@@ -4157,6 +4199,39 @@ function formatAggregateRenderFailureJson(componentName, failures, scenarioCount
4157
4199
  }
4158
4200
  var MANIFEST_PATH6 = ".reactscope/manifest.json";
4159
4201
  var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
4202
+ function renderArtifactBaseName(componentName, scenarioName) {
4203
+ return scenarioName === void 0 ? componentName : `${componentName}-${scenarioName}`;
4204
+ }
4205
+ function removeStaleRenderError(outputDir, componentName, scenarioName) {
4206
+ const errorPath = resolve11(
4207
+ outputDir,
4208
+ `${renderArtifactBaseName(componentName, scenarioName)}.error.json`
4209
+ );
4210
+ if (existsSync9(errorPath)) {
4211
+ rmSync2(errorPath, { force: true });
4212
+ }
4213
+ }
4214
+ function writeRenderErrorArtifact(outputDir, failure) {
4215
+ const errPath = resolve11(
4216
+ outputDir,
4217
+ `${renderArtifactBaseName(failure.component, failure.scenario)}.error.json`
4218
+ );
4219
+ writeFileSync6(
4220
+ errPath,
4221
+ JSON.stringify(
4222
+ {
4223
+ component: failure.component,
4224
+ scenario: failure.scenario,
4225
+ errorMessage: failure.errorMessage,
4226
+ heuristicFlags: failure.heuristicFlags,
4227
+ propsAtCrash: failure.propsAtCrash
4228
+ },
4229
+ null,
4230
+ 2
4231
+ )
4232
+ );
4233
+ return errPath;
4234
+ }
4160
4235
  var _pool3 = null;
4161
4236
  async function getPool3(viewportWidth, viewportHeight) {
4162
4237
  if (_pool3 === null) {
@@ -4841,20 +4916,12 @@ function registerRenderAll(renderCmd) {
4841
4916
  success: false,
4842
4917
  errorMessage: outcome.error.message
4843
4918
  });
4844
- const errPath = resolve11(outputDir, `${name}.error.json`);
4845
- writeFileSync6(
4846
- errPath,
4847
- JSON.stringify(
4848
- {
4849
- component: name,
4850
- errorMessage: outcome.error.message,
4851
- heuristicFlags: outcome.error.heuristicFlags,
4852
- propsAtCrash: outcome.error.propsAtCrash
4853
- },
4854
- null,
4855
- 2
4856
- )
4857
- );
4919
+ const errPath = writeRenderErrorArtifact(outputDir, {
4920
+ component: name,
4921
+ errorMessage: outcome.error.message,
4922
+ heuristicFlags: outcome.error.heuristicFlags,
4923
+ propsAtCrash: outcome.error.propsAtCrash
4924
+ });
4858
4925
  failures.push({
4859
4926
  component: name,
4860
4927
  stage: "render",
@@ -4867,13 +4934,14 @@ function registerRenderAll(renderCmd) {
4867
4934
  }
4868
4935
  const result = outcome.result;
4869
4936
  results.push({ name, renderTimeMs: result.renderTimeMs, success: true });
4937
+ removeStaleRenderError(outputDir, name);
4870
4938
  if (!isIcon) {
4871
4939
  const pngPath = resolve11(outputDir, `${name}.png`);
4872
4940
  writeFileSync6(pngPath, result.screenshot);
4873
4941
  outputPaths.push(pngPath);
4874
4942
  }
4875
4943
  const jsonPath = resolve11(outputDir, `${name}.json`);
4876
- const renderJson = formatRenderJson(name, {}, result);
4944
+ const renderJson = formatRenderJson(name, renderProps, result);
4877
4945
  const extResult = result;
4878
4946
  if (isIcon && extResult.svgContent) {
4879
4947
  renderJson.svgContent = extResult.svgContent;
@@ -4934,13 +5002,34 @@ function registerRenderAll(renderCmd) {
4934
5002
  concurrency: 2
4935
5003
  });
4936
5004
  const matrixResult = await matrix.render();
4937
- const matrixCells = matrixResult.cells.map((cell) => ({
4938
- axisValues: [scenarioEntries2[cell.axisIndices[0] ?? 0]?.[0] ?? ""],
4939
- screenshot: cell.result.screenshot.toString("base64"),
4940
- width: cell.result.width,
4941
- height: cell.result.height,
4942
- renderTimeMs: cell.result.renderTimeMs
4943
- }));
5005
+ const matrixCells = matrixResult.cells.map((cell) => {
5006
+ const scenarioName = scenarioEntries2[cell.axisIndices[0] ?? 0]?.[0] ?? "";
5007
+ const artifactBaseName = renderArtifactBaseName(name, scenarioName);
5008
+ const scenarioPngPath = resolve11(outputDir, `${artifactBaseName}.png`);
5009
+ const scenarioJsonPath = resolve11(outputDir, `${artifactBaseName}.json`);
5010
+ if (!isIcon) {
5011
+ writeFileSync6(scenarioPngPath, cell.result.screenshot);
5012
+ outputPaths.push(scenarioPngPath);
5013
+ }
5014
+ const scenarioJson = formatRenderJson(
5015
+ `${name}:${scenarioName}`,
5016
+ scenarioPropsMap[scenarioName] ?? {},
5017
+ cell.result
5018
+ );
5019
+ writeFileSync6(scenarioJsonPath, JSON.stringify(scenarioJson, null, 2));
5020
+ outputPaths.push(scenarioJsonPath);
5021
+ removeStaleRenderError(outputDir, name, scenarioName);
5022
+ return {
5023
+ axisValues: [scenarioName],
5024
+ scenario: scenarioName,
5025
+ outputPath: scenarioJsonPath,
5026
+ screenshotPath: isIcon ? void 0 : scenarioPngPath,
5027
+ screenshot: cell.result.screenshot.toString("base64"),
5028
+ width: cell.result.width,
5029
+ height: cell.result.height,
5030
+ renderTimeMs: cell.result.renderTimeMs
5031
+ };
5032
+ });
4944
5033
  const existingJson = JSON.parse(readFileSync9(jsonPath, "utf-8"));
4945
5034
  existingJson.cells = matrixCells;
4946
5035
  existingJson.axisLabels = [scenarioAxis.values];
@@ -5047,7 +5136,7 @@ function createRenderCommand() {
5047
5136
  }
5048
5137
 
5049
5138
  // src/report/baseline.ts
5050
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
5139
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
5051
5140
  import { resolve as resolve12 } from "path";
5052
5141
  import { generateManifest as generateManifest4 } from "@agent-scope/manifest";
5053
5142
  import { BrowserPool as BrowserPool4, safeRender as safeRender3 } from "@agent-scope/render";
@@ -5208,7 +5297,7 @@ async function runBaseline(options = {}) {
5208
5297
  const baselineDir = resolve12(rootDir, outputDir);
5209
5298
  const rendersDir = resolve12(baselineDir, "renders");
5210
5299
  if (existsSync10(baselineDir)) {
5211
- rmSync2(baselineDir, { recursive: true, force: true });
5300
+ rmSync3(baselineDir, { recursive: true, force: true });
5212
5301
  }
5213
5302
  mkdirSync6(rendersDir, { recursive: true });
5214
5303
  let manifest;