@szc-ft/mcp-szcd-client 0.34.1 → 0.34.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.
@@ -238,6 +238,24 @@ function parseArgs() {
238
238
  case "--fail-on-falsy":
239
239
  parsed.failOnFalsy = true;
240
240
  break;
241
+ case "--filename":
242
+ parsed.filename = args[++i];
243
+ break;
244
+ case "--full-page":
245
+ parsed.fullPage = true;
246
+ break;
247
+ case "--output-dir":
248
+ parsed.outputDir = args[++i];
249
+ break;
250
+ case "--interval":
251
+ parsed.interval = Number.parseInt(args[++i], 10);
252
+ break;
253
+ case "--max-rounds":
254
+ parsed.maxRounds = Number.parseInt(args[++i], 10);
255
+ break;
256
+ case "--no-reload":
257
+ parsed.noReload = true;
258
+ break;
241
259
  case "--help":
242
260
  case "-h":
243
261
  printHelp();
@@ -264,7 +282,7 @@ function printHelp() {
264
282
  node local-browser-executor.js --plan-file <path> [options]
265
283
 
266
284
  选项:
267
- --action <name> 直接执行动作,当前支持 observe/act/assert/evaluate/api-capture/assert-api/run-task/test-case/plan
285
+ --action <name> 直接执行动作,当前支持 observe/act/assert/evaluate/api-capture/assert-api/run-task/test-case/plan/list-pages/diagnose/perf/watch
268
286
  --plan <json> 测试计划 JSON 字符串
269
287
  --plan-file <path> 测试计划 JSON 文件路径
270
288
  --output <path> 结果输出文件路径(默认 stdout)
@@ -324,6 +342,12 @@ function printHelp() {
324
342
  --snapshot-each run-task 每条菜单成功后做一次轻量 observe
325
343
  --screenshot-each run-task 失败时截图(依赖 outputDir,默认关闭)
326
344
  --no-continue-on-fail run-task 第一条失败即终止(默认遇错继续)
345
+ --filename <name> screenshot / diagnose 截图文件名
346
+ --full-page screenshot 全页截图
347
+ --output-dir <dir> screenshot / diagnose / watch 产物目录
348
+ --interval <ms> watch 监控间隔(默认 5000)
349
+ --max-rounds <n> watch 最大轮数(默认 60)
350
+ --no-reload diagnose 不刷新页面(默认刷新)
327
351
  -h, --help 显示帮助
328
352
  `);
329
353
  }
@@ -494,6 +518,11 @@ async function executeAction(args) {
494
518
  frameUrlContains: args.frameUrlContains,
495
519
  frameContentContains: args.frameContentContains,
496
520
  frameIndex: args.frameIndex,
521
+ drawerTriggerSelector: args.drawerTriggerSelector,
522
+ drawerOpenSelector: args.drawerOpenSelector,
523
+ menuScrollContainer: args.menuScrollContainer,
524
+ useNativeHover: args.useNativeHover,
525
+ maxRetryPerStep: args.maxRetryPerStep,
497
526
  });
498
527
  if (!args.observeAfter) {
499
528
  return { type: "act", ...actResult, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
@@ -671,11 +700,180 @@ async function executeAction(args) {
671
700
 
672
701
  return { type: "testCase", ...result, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
673
702
  }
703
+ case "list-pages":
704
+ case "listPages": {
705
+ const pages = await engine._safeGetPages();
706
+ const result = [];
707
+ for (let i = 0; i < pages.length; i++) {
708
+ let title = "";
709
+ try { title = await pages[i].title(); } catch {}
710
+ result.push({ index: i, url: pages[i].url(), title });
711
+ }
712
+ return { type: "list-pages", pages: result, total: result.length, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
713
+ }
714
+ case "screenshot": {
715
+ const filename = args.filename || "screenshot.png";
716
+ const outputDir = args.outputDir || "./";
717
+ if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
718
+ const filepath = path.join(outputDir, filename);
719
+ await engine.page.screenshot({ path: filepath, fullPage: args.fullPage });
720
+ return { type: "screenshot", filepath, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
721
+ }
722
+ case "diagnose": {
723
+ const outputDir = args.outputDir || "./";
724
+ if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
725
+
726
+ const pageErrors = [];
727
+ const consoleMessages = [];
728
+ const failedRequests = [];
729
+ const apiRequests = [];
730
+ const resourceRequests = [];
731
+
732
+ engine.page.on("pageerror", (err) => {
733
+ pageErrors.push({ message: err.message, stack: err.stack?.split("\n").slice(0, 5).join("\n") });
734
+ });
735
+ engine.page.on("console", (msg) => {
736
+ consoleMessages.push({ type: msg.type(), text: msg.text().substring(0, 500) });
737
+ });
738
+ engine.page.on("response", (resp) => {
739
+ const url = resp.url();
740
+ const status = resp.status();
741
+ const type = resp.request().resourceType();
742
+ if (type === "xhr" || type === "fetch") {
743
+ apiRequests.push({ status, url, method: resp.request().method() });
744
+ }
745
+ if (type === "stylesheet" || type === "script") {
746
+ resourceRequests.push({ status, url, type });
747
+ }
748
+ if (status >= 400) {
749
+ failedRequests.push({ status, url, type });
750
+ }
751
+ });
752
+ engine.page.on("requestfailed", (req) => {
753
+ failedRequests.push({ status: "ABORTED", url: req.url(), error: req.failure()?.errorText });
754
+ });
755
+
756
+ if (!args.noReload) {
757
+ try { await engine.page.reload({ waitUntil: "load", timeout: 30000 }); } catch {}
758
+ }
759
+ await new Promise((r) => setTimeout(r, args.wait || 5000));
760
+
761
+ const screenshotPath = path.join(outputDir, args.filename || "diagnose-screenshot.png");
762
+ await engine.page.screenshot({ path: screenshotPath, fullPage: false });
763
+
764
+ const frames = engine.page.frames();
765
+ const iframeInfo = [];
766
+ for (const frame of frames) {
767
+ const url = frame.url();
768
+ if (url === engine.page.url() || url === "about:blank") continue;
769
+ try {
770
+ const info = await frame.evaluate(() => ({
771
+ bodyText: document.body?.innerText?.substring(0, 200) || "",
772
+ antdElements: document.querySelectorAll("[class*='ant-']").length,
773
+ }));
774
+ iframeInfo.push({ url, ...info });
775
+ } catch (e) {
776
+ iframeInfo.push({ url, error: e.message.substring(0, 100) });
777
+ }
778
+ }
779
+
780
+ const errors = consoleMessages.filter((m) => m.type === "error");
781
+ const warnings = consoleMessages.filter((m) => m.type === "warning");
782
+
783
+ return {
784
+ type: "diagnose",
785
+ url: engine.page.url(),
786
+ timestamp: new Date().toISOString(),
787
+ screenshot: screenshotPath,
788
+ pageErrors,
789
+ consoleErrors: errors,
790
+ consoleWarnings: warnings,
791
+ consoleTotal: consoleMessages.length,
792
+ failedRequests,
793
+ apiRequests,
794
+ resourceStatus: resourceRequests.map((r) => ({ status: r.status, type: r.type, name: r.url.split("/").pop() })),
795
+ iframes: iframeInfo,
796
+ summary: {
797
+ jsErrors: pageErrors.length,
798
+ consoleErrors: errors.length,
799
+ failedNetwork: failedRequests.length,
800
+ totalAPI: apiRequests.length,
801
+ failedAPI: apiRequests.filter((r) => r.status >= 400).length,
802
+ totalResources: resourceRequests.length,
803
+ failedResources: resourceRequests.filter((r) => r.status >= 400).length,
804
+ },
805
+ browserInfo: initResult,
806
+ startTime: startedAt,
807
+ endTime: new Date().toISOString(),
808
+ };
809
+ }
810
+ case "perf": {
811
+ const perfMetrics = await engine.page.evaluate(() => {
812
+ const perf = performance.getEntriesByType("navigation")[0];
813
+ const paint = performance.getEntriesByType("paint");
814
+ const fcp = paint.find((p) => p.name === "first-contentful-paint");
815
+ const resources = performance.getEntriesByType("resource");
816
+ return {
817
+ dns: perf?.domainEnd - perf?.domainStart || 0,
818
+ tcp: perf?.connectEnd - perf?.connectStart || 0,
819
+ ttfb: perf?.responseStart - perf?.requestStart || 0,
820
+ domReady: perf?.domContentLoadedEventEnd - perf?.startTime || 0,
821
+ loadComplete: perf?.loadEventEnd - perf?.startTime || 0,
822
+ fcp: fcp?.startTime || null,
823
+ totalResources: resources.length,
824
+ totalTransferSize: resources.reduce((sum, r) => sum + (r.transferSize || 0), 0),
825
+ slowResources: resources.filter((r) => r.duration > 1000).map((r) => ({
826
+ name: r.name.split("/").pop(), duration: Math.round(r.duration), size: r.transferSize,
827
+ })),
828
+ };
829
+ });
830
+
831
+ let webVitals = {};
832
+ try {
833
+ const client = await engine.page.target().createCDPSession();
834
+ await client.send("Performance.enable");
835
+ const metrics = await client.send("Performance.getMetrics");
836
+ const m = {};
837
+ metrics.metrics.forEach((item) => { m[item.name] = item.value; });
838
+ webVitals = {
839
+ layoutCount: m.LayoutCount || 0,
840
+ recalcCount: m.RecalcLayoutCount || 0,
841
+ jsHeapUsed: Math.round((m.JSHeapUsedSize || 0) / 1024 / 1024) + "MB",
842
+ jsHeapTotal: Math.round((m.JSHeapTotalSize || 0) / 1024 / 1024) + "MB",
843
+ taskDuration: (m.TaskDuration || 0).toFixed(2) + "s",
844
+ };
845
+ await client.send("Performance.disable");
846
+ await client.detach();
847
+ } catch {}
848
+
849
+ return { type: "perf", perfMetrics, webVitals, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
850
+ }
851
+ case "watch": {
852
+ const interval = args.interval || 5000;
853
+ const maxRounds = args.maxRounds || 60;
854
+ const outputDir = args.outputDir || "./";
855
+ if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
856
+
857
+ const watchErrors = [];
858
+ engine.page.on("pageerror", (err) => watchErrors.push({ time: new Date().toISOString(), error: err.message }));
859
+ engine.page.on("console", (msg) => {
860
+ if (msg.type() === "error") watchErrors.push({ time: new Date().toISOString(), type: "console", text: msg.text() });
861
+ });
862
+
863
+ for (let i = 1; i <= maxRounds; i++) {
864
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
865
+ const fp = path.join(outputDir, `watch-${String(i).padStart(3, "0")}-${ts}.png`);
866
+ await engine.page.screenshot({ path: fp });
867
+ await new Promise((r) => setTimeout(r, interval));
868
+ }
869
+
870
+ return { type: "watch", rounds: maxRounds, errors: watchErrors, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
871
+ }
674
872
  default:
675
873
  return {
676
874
  error: `未知 action: ${args.action}`,
677
875
  errorCode: "UNKNOWN_ACTION",
678
- supportedActions: ["observe", "act", "assert", "evaluate", "api-capture", "assert-api", "run-task", "test-case", "plan"],
876
+ supportedActions: ["observe", "act", "assert", "evaluate", "api-capture", "assert-api", "run-task", "test-case", "plan", "list-pages", "screenshot", "diagnose", "perf", "watch"],
679
877
  startTime: startedAt,
680
878
  endTime: new Date().toISOString(),
681
879
  };