@skyramp/mcp 0.2.7 → 0.2.150-rc.mntnc

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 (67) hide show
  1. package/build/index.js +5 -2
  2. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -5
  3. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +2 -0
  4. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +114 -0
  5. package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +2 -0
  6. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +292 -0
  7. package/build/prompts/sut-setup/shared.d.ts +39 -0
  8. package/build/prompts/sut-setup/shared.js +132 -0
  9. package/build/prompts/test-maintenance/driftAnalysisSections.js +9 -3
  10. package/build/prompts/test-recommendation/analysisOutputPrompt.js +1 -1
  11. package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
  12. package/build/prompts/testbot/testbot-prompts.js +23 -16
  13. package/build/prompts/testbot/testbot-prompts.test.js +0 -44
  14. package/build/resources/sutSetupResource.d.ts +2 -0
  15. package/build/resources/sutSetupResource.js +45 -0
  16. package/build/resources/testbotResource.js +1 -1
  17. package/build/services/TestDiscoveryService.d.ts +2 -2
  18. package/build/services/TestDiscoveryService.js +22 -12
  19. package/build/services/TestExecutionService.d.ts +9 -0
  20. package/build/services/TestExecutionService.js +158 -26
  21. package/build/services/TestExecutionService.test.js +306 -0
  22. package/build/tools/executeSkyrampTestTool.js +17 -4
  23. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +1 -1
  24. package/build/tools/submitReportTool.js +81 -35
  25. package/build/tools/submitReportTool.test.js +264 -50
  26. package/build/tools/test-management/actionsTool.js +10 -0
  27. package/build/tools/test-management/analyzeChangesTool.d.ts +0 -1
  28. package/build/tools/test-management/analyzeChangesTool.js +84 -41
  29. package/build/tools/test-management/analyzeChangesTool.test.js +4 -34
  30. package/build/tools/test-management/analyzeTestHealthTool.js +6 -3
  31. package/build/types/TestAnalysis.d.ts +2 -1
  32. package/build/types/TestExecution.d.ts +12 -2
  33. package/build/types/TestExecution.js +11 -1
  34. package/build/types/TestbotReport.d.ts +68 -0
  35. package/build/types/TestbotReport.js +1 -0
  36. package/build/types/index.d.ts +4 -0
  37. package/build/types/index.js +3 -0
  38. package/build/utils/AnalysisStateManager.js +10 -1
  39. package/build/utils/routeParsers.d.ts +0 -10
  40. package/build/utils/routeParsers.js +31 -38
  41. package/build/utils/scenarioDrafting.d.ts +1 -1
  42. package/build/utils/scenarioDrafting.js +23 -1
  43. package/build/utils/utils.d.ts +7 -1
  44. package/build/utils/utils.js +10 -0
  45. package/node_modules/playwright/LICENSE +202 -0
  46. package/node_modules/playwright/NOTICE +5 -0
  47. package/node_modules/playwright/README.md +168 -0
  48. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +13 -9
  49. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -3
  50. package/node_modules/playwright/node_modules/playwright-core/LICENSE +202 -0
  51. package/node_modules/playwright/node_modules/playwright-core/NOTICE +5 -0
  52. package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +23 -126
  53. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-B0JOjboO.js → codeMirrorModule-B5kqh2EV.js} +1 -1
  54. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-1anWeyDf.js → defaultSettingsView-CZ9npQ3N.js} +79 -79
  55. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.Cc5029a3.js → index.BCnxj-_b.js} +1 -1
  56. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  57. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.Wo5yvvVh.js → uiMode.1Ym0Ivn8.js} +1 -1
  58. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  59. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  60. package/node_modules/playwright/package.json +1 -1
  61. package/package.json +9 -2
  62. package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
  63. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.bin/node-which +0 -52
  64. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/is-docker +0 -5
  65. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/mime +0 -46
  66. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/yaml +0 -11
  67. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.CP3Y8Zsb.js +0 -2
@@ -67,7 +67,7 @@ CRITICAL: For search/filter/list endpoints (e.g., GET /products/search?q=bear&li
67
67
  responseBody: z
68
68
  .string()
69
69
  .optional()
70
- .describe("JSON string of the expected response body"),
70
+ .describe("JSON string of the expected response body. For steps that produce an id used by later steps, include that id field with a realistic UUID generated fresh for this scenario — the backend chains by value-matching any subsequent step path or requestBody that contains the same UUID."),
71
71
  statusCode: z
72
72
  .number()
73
73
  .optional()
@@ -6,6 +6,9 @@ import { AnalyticsService } from "../services/AnalyticsService.js";
6
6
  import { TEST_CATEGORIES, externalCategory } from "../types/TestRecommendation.js";
7
7
  import { TestType, HttpMethod } from "../types/TestTypes.js";
8
8
  import { DriftAction } from "../types/TestAnalysis.js";
9
+ import { TestExecutionStatus } from "../types/TestExecution.js";
10
+ import { StateManager } from "../utils/AnalysisStateManager.js";
11
+ import { toolError } from "../utils/utils.js";
9
12
  // Drift actions that actually modify a test file. VERIFY and IGNORE are
10
13
  // no-ops (the test was assessed but left unchanged), so they must not count
11
14
  // toward "tests maintained" telemetry.
@@ -21,6 +24,7 @@ const testResultSchema = z.object({
21
24
  endpoint: z.string().describe("HTTP verb and path, e.g. 'GET /api/v1/products'"),
22
25
  status: z.enum(["Pass", "Fail", "Skipped"]).describe("Test execution result"),
23
26
  details: z.string().describe("One sentence — no embedded newlines, no markdown. e.g. '10.8s, products_contract_test.py' or 'failed: <one-line error summary>, products_contract_test.py'"),
27
+ videoPath: z.string().optional().describe("Path to video recording (UI/E2E tests only)"),
24
28
  });
25
29
  // Structured grounding for UI recommendations. Lifted verbatim from a
26
30
  // `browser_blueprint` element captured during this run. Source of truth for
@@ -226,13 +230,25 @@ const testMaintenanceSchema = z.object({
226
230
  fileName: z.string().describe("Test file that was maintained, e.g. 'products_smoke_test.py'"),
227
231
  action: z.nativeEnum(DriftAction).describe("The drift action assigned to this test during maintenance triage."),
228
232
  description: z.string().describe("What was changed and why"),
229
- beforeStatus: z.enum(["Pass", "Fail", "Error"]).describe("Test result BEFORE modification"),
230
- beforeDetails: z.string().describe("Execution output/timing before modification, or 'baseline from CI workflow <name>' if a parallel workflow provided the baseline"),
231
- afterStatus: z.enum(["Pass", "Fail", "Error", "Skipped"]).describe("Test result AFTER modification"),
232
- afterDetails: z.string().describe("Execution output/timing after modification"),
233
- })
234
- .refine(m => ![DriftAction.Verify, DriftAction.Ignore].includes(m.action) || m.afterStatus === "Skipped", { message: "VERIFY and IGNORE entries must have afterStatus: 'Skipped' these actions do not modify the test file" })
235
- .refine(m => ![DriftAction.Update, DriftAction.Regenerate, DriftAction.Delete].includes(m.action) || m.afterStatus !== "Skipped", { message: "UPDATE, REGENERATE, and DELETE entries must have a real afterStatus (not Skipped) — these actions modify the test file and must be executed" });
233
+ beforeDetails: z.string().describe("One line only — no embedded newlines, no raw HTTP headers or JSON blobs. " +
234
+ "For passing runs: count and timing, e.g. '4 passed in 15.09s'. " +
235
+ "For failing runs: failure name and one-line root cause, e.g. " +
236
+ "'FAILED test_foo assert 403 got 200, auth middleware not enforced'. " +
237
+ "Empty string for VERIFY/IGNORE entries where no before-execution was run."),
238
+ afterDetails: z.string().describe("One line only no embedded newlines, no raw HTTP headers or JSON blobs. " +
239
+ "For passing runs: count and timing, e.g. '5 passed in 10.96s'. " +
240
+ "For failing runs: failure name and one-line root cause, e.g. " +
241
+ "'FAILED test_foo — check_schema fails, order_id=1 has discount from prior PATCH test'. " +
242
+ "Empty string for VERIFY/IGNORE/DELETE entries where no after-execution was run."),
243
+ // Server-populated from stateFile execution records — never supplied by the LLM.
244
+ beforeStatus: z.nativeEnum(TestExecutionStatus),
245
+ afterStatus: z.nativeEnum(TestExecutionStatus),
246
+ });
247
+ // LLM input contract: status fields are server-populated from stateFile.
248
+ const testMaintenanceLLMSchema = testMaintenanceSchema.omit({
249
+ beforeStatus: true,
250
+ afterStatus: true,
251
+ });
236
252
  /**
237
253
  * Derive per-run analytics counts from a submitted report. These power the
238
254
  * alpha-launch dashboards (tests generated/maintained, suite growth, bugs vs
@@ -256,7 +272,7 @@ function computeReportMetrics(params) {
256
272
  const recommendations = params.additionalRecommendations ?? [];
257
273
  const countBy = (items, pred) => items.filter(pred).length;
258
274
  const changedMaintenance = (params.testMaintenance ?? []).filter(isMaintenanceChange);
259
- const maintenanceRecovered = countBy(changedMaintenance, (m) => m.beforeStatus !== "Pass" && m.afterStatus === "Pass");
275
+ const maintenanceRecovered = countBy(changedMaintenance, (m) => (m.beforeStatus === TestExecutionStatus.Fail || m.beforeStatus === TestExecutionStatus.Error) && m.afterStatus === TestExecutionStatus.Pass);
260
276
  return {
261
277
  testsGenerated: String(params.newTestsCreated.length),
262
278
  testsMaintained: String(changedMaintenance.length),
@@ -272,6 +288,23 @@ function computeReportMetrics(params) {
272
288
  issuesLow: String(countBy(params.issuesFound, (i) => i.severity === "low")),
273
289
  };
274
290
  }
291
+ function deduplicateById(items) {
292
+ const seen = new Set();
293
+ const result = [];
294
+ for (const item of items) {
295
+ let id = item.testId;
296
+ if (seen.has(id)) {
297
+ let suffix = 2;
298
+ while (seen.has(`${id}-${suffix}`))
299
+ suffix++;
300
+ id = `${id}-${suffix}`;
301
+ item.testId = id;
302
+ }
303
+ seen.add(id);
304
+ result.push(item);
305
+ }
306
+ return result;
307
+ }
275
308
  export function registerSubmitReportTool(server) {
276
309
  server.registerTool(TOOL_NAME, {
277
310
  description: "Submit the final testbot report. Call this tool once after completing all test analysis, generation, and execution. " +
@@ -292,9 +325,9 @@ export function registerSubmitReportTool(server) {
292
325
  .default([])
293
326
  .describe("Recommended tests that were not generated (lower priority). Only include recommendations that add distinct coverage beyond generated tests — do not pad with variants testing the same endpoint and flow."),
294
327
  testMaintenance: z
295
- .array(testMaintenanceSchema)
328
+ .array(testMaintenanceLLMSchema)
296
329
  .optional()
297
- .describe("One entry per test assessed in the drift analysis step. Omit this field when no existing tests were found."),
330
+ .describe("One entry per test assessed in the drift analysis step, including IGNORE and VERIFY entries — these provide triage transparency even when no edits were needed. Omit this field entirely when no existing tests were found."),
298
331
  testResults: z
299
332
  .array(testResultSchema)
300
333
  .describe("List of ALL test execution results. One entry per test executed."),
@@ -317,6 +350,12 @@ export function registerSubmitReportTool(server) {
317
350
  .describe("Succinct commit message (if possible, under 72 chars) summarizing what Testbot did, " +
318
351
  "e.g. 'add contract tests for /products endpoint' or 'update smoke tests for order API changes'. " +
319
352
  "Used as both the git commit subject and the side PR title — the consumer applies truncation as needed."),
353
+ stateFile: z
354
+ .string()
355
+ .describe("Path to the state file returned by skyramp_analyze_changes. Required — " +
356
+ "contains execution results for maintenance scoring (existingTests), " +
357
+ "discovered endpoints for coverage validation (newEndpoints), and " +
358
+ "UI page context for frontend test grounding (uiContext)."),
320
359
  },
321
360
  _meta: {
322
361
  keywords: ["report", "summary", "testbot", "submit"],
@@ -324,40 +363,50 @@ export function registerSubmitReportTool(server) {
324
363
  }, async (params) => {
325
364
  const startTime = Date.now();
326
365
  let errorResult;
327
- const deduplicateById = (items) => {
328
- const seen = new Set();
329
- const result = [];
330
- for (const item of items) {
331
- let id = item.testId;
332
- if (seen.has(id)) {
333
- let suffix = 2;
334
- while (seen.has(`${id}-${suffix}`))
335
- suffix++;
336
- id = `${id}-${suffix}`;
337
- item.testId = id;
338
- }
339
- seen.add(id);
340
- result.push(item);
341
- }
342
- return result;
343
- };
344
366
  const dedupedNewTests = deduplicateById([...params.newTestsCreated]);
345
367
  const dedupedRecommendations = deduplicateById([...(params.additionalRecommendations ?? [])]);
368
+ let testMaintenance;
369
+ if (params.testMaintenance) {
370
+ try {
371
+ const stateData = await StateManager.fromStatePath(params.stateFile).readData();
372
+ if (!stateData) {
373
+ errorResult = toolError(`stateFile not found or empty: ${params.stateFile}`);
374
+ return errorResult;
375
+ }
376
+ // Status fields are stateFile-authoritative. Details are LLM-supplied when present
377
+ // (single-line, with root-cause context) and fall back to server-side extraction.
378
+ testMaintenance = params.testMaintenance.map((m) => {
379
+ const recorded = stateData.existingTests?.find((t) => t.testFile.endsWith(m.fileName) || path.basename(t.testFile) === path.basename(m.fileName));
380
+ const defaultBeforeStatus = MAINTENANCE_CHANGE_ACTIONS.has(m.action) ? TestExecutionStatus.Unknown : TestExecutionStatus.Skipped;
381
+ const defaultAfterStatus = m.action === DriftAction.Delete ? TestExecutionStatus.Skipped
382
+ : MAINTENANCE_CHANGE_ACTIONS.has(m.action) ? TestExecutionStatus.Unknown : TestExecutionStatus.Skipped;
383
+ const beforeStatus = recorded?.executionBefore?.status ?? defaultBeforeStatus;
384
+ const afterStatus = recorded?.executionAfter?.status ?? defaultAfterStatus;
385
+ logger.info(`${m.fileName}: before=${beforeStatus} after=${afterStatus}`);
386
+ return { ...m, beforeStatus, afterStatus };
387
+ });
388
+ }
389
+ catch (err) {
390
+ errorResult = toolError(`Failed to load stateFile for maintenance enrichment: ${err.message}`);
391
+ return errorResult;
392
+ }
393
+ }
346
394
  // Strip generation-artifact fields from newTestsCreated before writing.
347
395
  // scenarioFile, traceFile, frontendTrace are internal paths used during
348
396
  // generation — downstream scoring scripts don't expect them and fail if
349
397
  // they encounter these string fields while traversing the object.
350
398
  const sanitizedNewTests = dedupedNewTests.map(({ scenarioFile: _sf, traceFile: _tf, frontendTrace: _ft, ...rest }) => rest);
351
- const reportJson = JSON.stringify({
399
+ const report = {
352
400
  businessCaseAnalysis: params.businessCaseAnalysis,
353
401
  newTestsCreated: sanitizedNewTests,
354
402
  additionalRecommendations: dedupedRecommendations,
355
- testMaintenance: params.testMaintenance,
403
+ testMaintenance,
356
404
  testResults: params.testResults,
357
405
  issuesFound: params.issuesFound,
358
406
  nextSteps: params.nextSteps ?? [],
359
407
  commitMessage: (params.commitMessage ?? "").replace(/[\r\n]+/g, " ").trim() || DEFAULT_COMMIT_MESSAGE,
360
- }, null, 2);
408
+ };
409
+ const reportJson = JSON.stringify(report, null, 2);
361
410
  logger.info("Submitting testbot report", {
362
411
  outputFile: params.summaryOutputFile,
363
412
  payloadBytes: reportJson.length,
@@ -384,10 +433,7 @@ export function registerSubmitReportTool(server) {
384
433
  const elapsed = Date.now() - startTime;
385
434
  const errorMessage = `Failed to write report: ${error.message}`;
386
435
  logger.error(errorMessage, { error, elapsedMs: elapsed });
387
- errorResult = {
388
- content: [{ type: "text", text: errorMessage }],
389
- isError: true,
390
- };
436
+ errorResult = toolError(errorMessage);
391
437
  return errorResult;
392
438
  }
393
439
  finally {
@@ -395,7 +441,7 @@ export function registerSubmitReportTool(server) {
395
441
  summary_output_file: params.summaryOutputFile,
396
442
  testResultCount: String(params.testResults.length),
397
443
  payloadBytes: String(reportJson.length),
398
- ...computeReportMetrics(params),
444
+ ...computeReportMetrics({ ...params, testMaintenance }),
399
445
  }).catch(() => { });
400
446
  }
401
447
  });