@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.
- package/build/index.js +5 -2
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -5
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +2 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +114 -0
- package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +2 -0
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +292 -0
- package/build/prompts/sut-setup/shared.d.ts +39 -0
- package/build/prompts/sut-setup/shared.js +132 -0
- package/build/prompts/test-maintenance/driftAnalysisSections.js +9 -3
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +1 -1
- package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
- package/build/prompts/testbot/testbot-prompts.js +23 -16
- package/build/prompts/testbot/testbot-prompts.test.js +0 -44
- package/build/resources/sutSetupResource.d.ts +2 -0
- package/build/resources/sutSetupResource.js +45 -0
- package/build/resources/testbotResource.js +1 -1
- package/build/services/TestDiscoveryService.d.ts +2 -2
- package/build/services/TestDiscoveryService.js +22 -12
- package/build/services/TestExecutionService.d.ts +9 -0
- package/build/services/TestExecutionService.js +158 -26
- package/build/services/TestExecutionService.test.js +306 -0
- package/build/tools/executeSkyrampTestTool.js +17 -4
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +1 -1
- package/build/tools/submitReportTool.js +81 -35
- package/build/tools/submitReportTool.test.js +264 -50
- package/build/tools/test-management/actionsTool.js +10 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +0 -1
- package/build/tools/test-management/analyzeChangesTool.js +84 -41
- package/build/tools/test-management/analyzeChangesTool.test.js +4 -34
- package/build/tools/test-management/analyzeTestHealthTool.js +6 -3
- package/build/types/TestAnalysis.d.ts +2 -1
- package/build/types/TestExecution.d.ts +12 -2
- package/build/types/TestExecution.js +11 -1
- package/build/types/TestbotReport.d.ts +68 -0
- package/build/types/TestbotReport.js +1 -0
- package/build/types/index.d.ts +4 -0
- package/build/types/index.js +3 -0
- package/build/utils/AnalysisStateManager.js +10 -1
- package/build/utils/routeParsers.d.ts +0 -10
- package/build/utils/routeParsers.js +31 -38
- package/build/utils/scenarioDrafting.d.ts +1 -1
- package/build/utils/scenarioDrafting.js +23 -1
- package/build/utils/utils.d.ts +7 -1
- package/build/utils/utils.js +10 -0
- package/node_modules/playwright/LICENSE +202 -0
- package/node_modules/playwright/NOTICE +5 -0
- package/node_modules/playwright/README.md +168 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +13 -9
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -3
- package/node_modules/playwright/node_modules/playwright-core/LICENSE +202 -0
- package/node_modules/playwright/node_modules/playwright-core/NOTICE +5 -0
- package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +23 -126
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-B0JOjboO.js → codeMirrorModule-B5kqh2EV.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-1anWeyDf.js → defaultSettingsView-CZ9npQ3N.js} +79 -79
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.Cc5029a3.js → index.BCnxj-_b.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.Wo5yvvVh.js → uiMode.1Ym0Ivn8.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +9 -2
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.bin/node-which +0 -52
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/is-docker +0 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/mime +0 -46
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/yaml +0 -11
- 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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
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(
|
|
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
|
|
399
|
+
const report = {
|
|
352
400
|
businessCaseAnalysis: params.businessCaseAnalysis,
|
|
353
401
|
newTestsCreated: sanitizedNewTests,
|
|
354
402
|
additionalRecommendations: dedupedRecommendations,
|
|
355
|
-
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
|
-
}
|
|
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
|
});
|