@skyramp/mcp 0.1.8 → 0.2.0-rc.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.
- package/build/index.js +4 -2
- package/build/playwright/registerPlaywrightTools.js +12 -0
- package/build/playwright/traceRecordingPrompt.js +15 -0
- package/build/prompts/code-reuse.js +106 -7
- package/build/prompts/pom-aware-code-reuse.js +106 -7
- package/build/prompts/startTraceCollectionPrompts.js +37 -15
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +26 -31
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +40 -1
- package/build/prompts/test-maintenance/driftAnalysisSections.js +90 -86
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +286 -163
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +154 -45
- package/build/prompts/test-recommendation/diffExecutionPlan.js +246 -117
- package/build/prompts/test-recommendation/promptPlan.js +290 -0
- package/build/prompts/test-recommendation/promptPlan.test.js +336 -0
- package/build/prompts/test-recommendation/recommendationSections.js +4 -3
- package/build/prompts/test-recommendation/recommendationShared.js +23 -1
- package/build/prompts/test-recommendation/scopeAssessment.js +65 -14
- package/build/prompts/test-recommendation/scopeAssessment.test.js +93 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +36 -12
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +316 -1
- package/build/prompts/testbot/testbot-prompts.js +73 -13
- package/build/prompts/testbot/testbot-prompts.test.js +114 -1
- package/build/resources/testbotResource.js +1 -1
- package/build/services/ScenarioGenerationService.integration.test.js +158 -0
- package/build/services/ScenarioGenerationService.js +47 -4
- package/build/services/ScenarioGenerationService.test.js +158 -22
- package/build/services/TestExecutionService.js +73 -15
- package/build/services/TestExecutionService.test.js +105 -0
- package/build/services/TestGenerationService.js +11 -1
- package/build/tools/executeSkyrampTestTool.js +1 -10
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +16 -4
- package/build/tools/generate-tests/generateIntegrationRestTool.js +2 -0
- package/build/tools/generate-tests/generateUIRestTool.js +2 -0
- package/build/tools/test-management/actionsTool.js +152 -63
- package/build/tools/test-management/analyzeChangesTool.js +178 -64
- package/build/tools/test-management/analyzeChangesTool.test.js +103 -16
- package/build/tools/test-management/analyzeTestHealthTool.js +30 -81
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/uiAnalyzeChangesTool.js +149 -0
- package/build/tools/test-management/uiAnalyzeChangesTool.test.js +100 -0
- package/build/tools/trace/resolveSaveStoragePath.js +16 -0
- package/build/tools/trace/resolveSaveStoragePath.test.js +17 -0
- package/build/tools/trace/resolveSessionPaths.js +39 -0
- package/build/tools/trace/resolveSessionPaths.test.js +103 -0
- package/build/tools/trace/sessionState.js +14 -0
- package/build/tools/trace/sessionState.test.js +17 -0
- package/build/tools/trace/startTraceCollectionTool.js +84 -14
- package/build/tools/trace/stopTraceCollectionTool.js +9 -2
- package/build/types/TestAnalysis.js +50 -0
- package/build/types/TestRecommendation.js +6 -58
- package/build/types/TestTypes.js +1 -1
- package/build/utils/AnalysisStateManager.js +22 -11
- package/build/utils/branchDiff.js +11 -2
- package/build/utils/docker.test.js +1 -1
- package/build/utils/gitStaging.js +52 -3
- package/build/utils/gitStaging.test.js +19 -1
- package/build/utils/repoScanner.js +18 -10
- package/build/utils/repoScanner.test.js +92 -0
- package/build/utils/routeParsers.js +180 -25
- package/build/utils/routeParsers.test.js +180 -1
- package/build/utils/scenarioDrafting.js +220 -17
- package/build/utils/scenarioDrafting.test.js +182 -9
- package/build/utils/sourceRouteExtractor.js +806 -0
- package/build/utils/sourceRouteExtractor.test.js +565 -0
- package/build/utils/uiPageEnumerator.js +319 -0
- package/build/utils/uiPageEnumerator.test.js +422 -0
- package/build/utils/utils.js +27 -0
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.js +33 -4
- package/node_modules/playwright/ThirdPartyNotices.txt +6 -6
- package/node_modules/playwright/lib/dom-analyzer/analyze.js +111 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1210 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.test.js +396 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.test.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.js +254 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.test.js +304 -0
- package/node_modules/playwright/lib/dom-analyzer/crawler.js +384 -0
- package/node_modules/playwright/lib/dom-analyzer/curatedWidgets.js +73 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +85 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.js +90 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.test.js +231 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.fixtures.js +145 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.test.js +41 -0
- package/node_modules/playwright/lib/dom-analyzer/graph.js +36 -0
- package/node_modules/playwright/lib/dom-analyzer/liveFingerprints.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.js +72 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.test.js +182 -0
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.js +150 -0
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.test.js +470 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.js +169 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.test.js +269 -0
- package/node_modules/playwright/lib/dom-analyzer/serialization.js +75 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.js +30 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.test.js +84 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.js +127 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.test.js +212 -0
- package/node_modules/playwright/lib/mcp/browser/browserContextFactory.js +3 -1
- package/node_modules/playwright/lib/mcp/browser/config.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/context.js +17 -1
- package/node_modules/playwright/lib/mcp/browser/tab.js +38 -0
- package/node_modules/playwright/lib/mcp/browser/tools/domAnalyzer.js +261 -0
- package/node_modules/playwright/lib/mcp/browser/tools/keyboard.js +3 -3
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +146 -0
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.test.js +140 -0
- package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +226 -0
- package/node_modules/playwright/lib/mcp/browser/tools/snapshot.js +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/widgetContract.js +168 -0
- package/node_modules/playwright/lib/mcp/browser/tools.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +52 -12
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +64 -13
- package/node_modules/playwright/package.json +1 -1
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.3.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.4.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.5.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.6.tgz +0 -0
- package/package.json +3 -3
- package/build/services/TestHealthService.js +0 -694
- package/build/services/TestHealthService.test.js +0 -241
- package/build/types/TestDriftAnalysis.js +0 -1
- package/build/types/TestHealth.js +0 -4
|
@@ -156,14 +156,25 @@ function filterComments(lines) {
|
|
|
156
156
|
* Detect session file paths referenced in test files
|
|
157
157
|
* Looks for storageState patterns in TypeScript/JavaScript/Python/Java/C# test files
|
|
158
158
|
* Excludes matches found in comments
|
|
159
|
+
*
|
|
160
|
+
* Also handles the codegen pattern `path.join(__dirname, '<filename>')` (TS/JS) —
|
|
161
|
+
* the filename is resolved relative to the test file's directory on the host so
|
|
162
|
+
* the existing absolute-path mount branch makes it visible at the same path
|
|
163
|
+
* inside the container (Playwright's TS loader resolves __dirname to the host
|
|
164
|
+
* workspace path at runtime).
|
|
159
165
|
*/
|
|
160
|
-
function detectSessionFiles(testFilePath) {
|
|
166
|
+
export function detectSessionFiles(testFilePath) {
|
|
161
167
|
try {
|
|
162
168
|
const content = fs.readFileSync(testFilePath, "utf-8");
|
|
163
169
|
const lines = content.split("\n");
|
|
164
170
|
const sessionFiles = [];
|
|
165
171
|
// Pattern for TypeScript/JavaScript: storageState: '/path/to/file' or storageState: "/path/to/file"
|
|
166
172
|
const tsJsPattern = /storageState:\s*['"]([^'"]+)['"]/g;
|
|
173
|
+
// Pattern for TypeScript/JavaScript with path.join(__dirname, 'filename') — covers
|
|
174
|
+
// both the inline form (`storageState: path.join(__dirname, '...')`) and the
|
|
175
|
+
// variable-assignment form (`const X = path.join(__dirname, '...')` then
|
|
176
|
+
// `storageState: X`) the skyramp codegen emits.
|
|
177
|
+
const tsJsPathJoinPattern = /path\.join\s*\(\s*__dirname\s*,\s*['"]([^'"]+)['"]\s*\)/g;
|
|
167
178
|
// Pattern for Python: storage_state='/path/to/file' or storage_state="/path/to/file"
|
|
168
179
|
const pythonPattern = /storage_state\s*=\s*['"]([^'"]+)['"]/g;
|
|
169
180
|
// Pattern for Java: setStorageState(Paths.get("path")) or setStorageState("path")
|
|
@@ -173,6 +184,7 @@ function detectSessionFiles(testFilePath) {
|
|
|
173
184
|
const csharpPattern = /StorageState(?:Path)?\s*=\s*['"]([^'"]+)['"]/g;
|
|
174
185
|
// Filter out comments
|
|
175
186
|
const codeLines = filterComments(lines);
|
|
187
|
+
const testFileDir = path.dirname(testFilePath);
|
|
176
188
|
// Process each non-comment line
|
|
177
189
|
for (const line of codeLines) {
|
|
178
190
|
// Try all patterns on this line
|
|
@@ -181,6 +193,12 @@ function detectSessionFiles(testFilePath) {
|
|
|
181
193
|
while ((match = tsJsPattern.exec(line)) !== null) {
|
|
182
194
|
sessionFiles.push(match[1]);
|
|
183
195
|
}
|
|
196
|
+
tsJsPathJoinPattern.lastIndex = 0;
|
|
197
|
+
while ((match = tsJsPathJoinPattern.exec(line)) !== null) {
|
|
198
|
+
// Resolve relative to the test file's host directory so the absolute-
|
|
199
|
+
// path branch below mounts it at the same path inside the container.
|
|
200
|
+
sessionFiles.push(path.resolve(testFileDir, match[1]));
|
|
201
|
+
}
|
|
184
202
|
pythonPattern.lastIndex = 0;
|
|
185
203
|
while ((match = pythonPattern.exec(line)) !== null) {
|
|
186
204
|
sessionFiles.push(match[1]);
|
|
@@ -357,39 +375,79 @@ export class TestExecutionService {
|
|
|
357
375
|
},
|
|
358
376
|
],
|
|
359
377
|
};
|
|
360
|
-
// Mount workspace files, skipping EXCLUDED_MOUNT_ITEMS completely
|
|
378
|
+
// Mount workspace files, skipping EXCLUDED_MOUNT_ITEMS completely.
|
|
379
|
+
//
|
|
380
|
+
// Each workspace entry is bind-mounted at BOTH the canonical /home/user
|
|
381
|
+
// path AND its host-absolute path. The dual mount lets the test resolve
|
|
382
|
+
// any absolute reference the codegen happens to embed (storageState,
|
|
383
|
+
// fixture paths, snapshots) — including the host workspace path that
|
|
384
|
+
// Playwright's TypeScript loader sometimes produces from `__dirname` —
|
|
385
|
+
// without needing source-code detection. EXCLUDED_MOUNT_ITEMS
|
|
386
|
+
// (node_modules) stays excluded at both targets; MOUNT_NULL_ITEMS
|
|
387
|
+
// shadows (package.json → empty JSON, etc.) and PLAYWRIGHT_CONFIG_FILES
|
|
388
|
+
// shadows (minimal config) are applied at both targets too so the
|
|
389
|
+
// protections survive regardless of which path the test resolves to.
|
|
361
390
|
const workspaceFiles = fs.readdirSync(workspacePath);
|
|
362
391
|
const filesToMount = workspaceFiles.filter((file) => !EXCLUDED_MOUNT_ITEMS.includes(file) && !MOUNT_NULL_ITEMS.includes(file));
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
392
|
+
// Single Set tracks every mount target we've added so far. Used to dedupe
|
|
393
|
+
// both the workspace-mirror push (when workspacePath happens to equal
|
|
394
|
+
// containerMountPath) and the session-file push below.
|
|
395
|
+
const mountedPaths = new Set();
|
|
396
|
+
const pushMount = (mount) => {
|
|
397
|
+
if (mountedPaths.has(mount.Target))
|
|
398
|
+
return;
|
|
399
|
+
mountedPaths.add(mount.Target);
|
|
400
|
+
hostConfig.Mounts.push(mount);
|
|
401
|
+
};
|
|
402
|
+
const mirrorAtHostPath = workspacePath !== containerMountPath;
|
|
403
|
+
for (const file of filesToMount) {
|
|
404
|
+
const source = path.join(workspacePath, file);
|
|
405
|
+
pushMount({
|
|
406
|
+
Type: "bind",
|
|
407
|
+
Target: path.join(containerMountPath, file),
|
|
408
|
+
Source: source,
|
|
409
|
+
});
|
|
410
|
+
if (mirrorAtHostPath) {
|
|
411
|
+
pushMount({ Type: "bind", Target: source, Source: source });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
368
414
|
// Mount MOUNT_NULL_ITEMS (found recursively) to /dev/null (or empty JSON for .json files)
|
|
369
415
|
const nullPaths = findExcludedPaths(workspacePath, MOUNT_NULL_ITEMS);
|
|
370
416
|
for (const absolutePath of nullPaths) {
|
|
371
|
-
const
|
|
417
|
+
const rel = path.relative(workspacePath, absolutePath);
|
|
372
418
|
const source = absolutePath.endsWith(".json") ? EMPTY_JSON_PATH : "/dev/null";
|
|
373
|
-
|
|
419
|
+
pushMount({
|
|
374
420
|
Type: "bind",
|
|
375
421
|
Source: source,
|
|
376
|
-
Target:
|
|
422
|
+
Target: path.join(containerMountPath, rel),
|
|
377
423
|
});
|
|
424
|
+
if (mirrorAtHostPath) {
|
|
425
|
+
pushMount({ Type: "bind", Source: source, Target: absolutePath });
|
|
426
|
+
}
|
|
378
427
|
}
|
|
379
428
|
// Mount Playwright config files with minimal config (shadows repo configs that may
|
|
380
429
|
// import dotenv or other dependencies not available in the executor container)
|
|
381
430
|
const playwrightConfigPaths = findExcludedPaths(workspacePath, PLAYWRIGHT_CONFIG_FILES);
|
|
382
431
|
for (const absolutePath of playwrightConfigPaths) {
|
|
383
|
-
const
|
|
384
|
-
|
|
432
|
+
const rel = path.relative(workspacePath, absolutePath);
|
|
433
|
+
pushMount({
|
|
385
434
|
Type: "bind",
|
|
386
435
|
Source: MINIMAL_PLAYWRIGHT_CONFIG_PATH,
|
|
387
|
-
Target:
|
|
436
|
+
Target: path.join(containerMountPath, rel),
|
|
388
437
|
});
|
|
438
|
+
if (mirrorAtHostPath) {
|
|
439
|
+
pushMount({
|
|
440
|
+
Type: "bind",
|
|
441
|
+
Source: MINIMAL_PLAYWRIGHT_CONFIG_PATH,
|
|
442
|
+
Target: absolutePath,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
389
445
|
}
|
|
390
|
-
// Detect and mount session files
|
|
446
|
+
// Detect and mount session files referenced outside the workspace
|
|
447
|
+
// (anything inside the workspace is already covered by the dual mount
|
|
448
|
+
// above; the session-file loop is the safety net for tests that point
|
|
449
|
+
// at a session in some other directory).
|
|
391
450
|
const sessionFiles = detectSessionFiles(options.testFile);
|
|
392
|
-
const mountedPaths = new Set(); // Track mounted file paths to prevent duplicates
|
|
393
451
|
for (const sessionFile of sessionFiles) {
|
|
394
452
|
let sessionFileSource;
|
|
395
453
|
let sessionFileTarget;
|
|
@@ -142,6 +142,49 @@ describe("buildContainerEnv", () => {
|
|
|
142
142
|
expect(env).toContain("API_KEY=my-key");
|
|
143
143
|
});
|
|
144
144
|
});
|
|
145
|
+
describe("detectSessionFiles", () => {
|
|
146
|
+
// Import after mocks are set up so the fs mock applies
|
|
147
|
+
let detectSessionFiles;
|
|
148
|
+
let mockReadFileSync;
|
|
149
|
+
beforeAll(async () => {
|
|
150
|
+
const mod = await import("./TestExecutionService.js");
|
|
151
|
+
detectSessionFiles = mod.detectSessionFiles;
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
153
|
+
mockReadFileSync = require("fs").readFileSync;
|
|
154
|
+
});
|
|
155
|
+
it("detects string-literal storageState (TS/JS)", () => {
|
|
156
|
+
mockReadFileSync.mockReturnValueOnce(`test.use({ storageState: '/abs/path/session.json' });`);
|
|
157
|
+
expect(detectSessionFiles("/ws/test.spec.ts")).toEqual([
|
|
158
|
+
"/abs/path/session.json",
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
it("detects skyramp codegen path.join(__dirname, '<file>') pattern and resolves to host-absolute path", () => {
|
|
162
|
+
// Reproduces SKYR-3321 generated test shape — must resolve to the host
|
|
163
|
+
// absolute path so the executor's absolute-path mount branch makes the
|
|
164
|
+
// file visible at that same path inside the container.
|
|
165
|
+
mockReadFileSync.mockReturnValueOnce(`
|
|
166
|
+
import path from 'path';
|
|
167
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
168
|
+
const SESSION_STORAGE = path.join(__dirname, 'skyramp_session_storage.json');
|
|
169
|
+
test.use({ storageState: SESSION_STORAGE });
|
|
170
|
+
`);
|
|
171
|
+
expect(detectSessionFiles("/Users/pedro/projects/cisco-xdr-tests/xdr_dashboard.spec.ts")).toEqual([
|
|
172
|
+
"/Users/pedro/projects/cisco-xdr-tests/skyramp_session_storage.json",
|
|
173
|
+
]);
|
|
174
|
+
});
|
|
175
|
+
it("detects inline storageState: path.join(__dirname, '<file>')", () => {
|
|
176
|
+
mockReadFileSync.mockReturnValueOnce(`test.use({ storageState: path.join(__dirname, 'session.json') });`);
|
|
177
|
+
expect(detectSessionFiles("/ws/spec.ts")).toEqual(["/ws/session.json"]);
|
|
178
|
+
});
|
|
179
|
+
it("ignores storageState references inside comments", () => {
|
|
180
|
+
mockReadFileSync.mockReturnValueOnce(`
|
|
181
|
+
// storageState: '/should/not/match.json'
|
|
182
|
+
// path.join(__dirname, 'also-not.json')
|
|
183
|
+
test('x', () => {});
|
|
184
|
+
`);
|
|
185
|
+
expect(detectSessionFiles("/ws/spec.ts")).toEqual([]);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
145
188
|
describe("TestExecutionService.executeTest - Docker env forwarding", () => {
|
|
146
189
|
// Import after mocks are set up
|
|
147
190
|
let TestExecutionService;
|
|
@@ -210,4 +253,66 @@ describe("TestExecutionService.executeTest - Docker env forwarding", () => {
|
|
|
210
253
|
e.startsWith("SKYRAMP_TEST_SERVICE_URL_"));
|
|
211
254
|
expect(envWithBaseUrl).toHaveLength(0);
|
|
212
255
|
});
|
|
256
|
+
// Approach B: every workspace mount is mirrored at the host-absolute path so
|
|
257
|
+
// tests that embed absolute references (storageState, fixtures, snapshots)
|
|
258
|
+
// resolve correctly inside the executor regardless of which path-shape the
|
|
259
|
+
// codegen happens to emit.
|
|
260
|
+
it("mirrors each workspace file mount at both /home/user/<f> and the host-absolute path", async () => {
|
|
261
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
262
|
+
const fs = require("fs");
|
|
263
|
+
fs.readdirSync.mockImplementation((_path, options) => {
|
|
264
|
+
if (options?.withFileTypes) {
|
|
265
|
+
return [
|
|
266
|
+
{ name: "xdr_dashboard.spec.ts", isFile: () => true, isDirectory: () => false },
|
|
267
|
+
{ name: "skyramp_session_storage.json", isFile: () => true, isDirectory: () => false },
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
return ["xdr_dashboard.spec.ts", "skyramp_session_storage.json"];
|
|
271
|
+
});
|
|
272
|
+
const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
|
|
273
|
+
mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
|
|
274
|
+
const service = new TestExecutionService();
|
|
275
|
+
await service.executeTest({
|
|
276
|
+
testFile: "/Users/pedro/projects/cisco-xdr-tests/xdr_dashboard.spec.ts",
|
|
277
|
+
workspacePath: "/Users/pedro/projects/cisco-xdr-tests",
|
|
278
|
+
language: "typescript",
|
|
279
|
+
testType: "ui",
|
|
280
|
+
});
|
|
281
|
+
const dockerOptions = mockRun.mock.calls[0][3];
|
|
282
|
+
const targets = dockerOptions.HostConfig.Mounts.map((m) => m.Target);
|
|
283
|
+
// Canonical /home/user mount
|
|
284
|
+
expect(targets).toContain("/home/user/xdr_dashboard.spec.ts");
|
|
285
|
+
expect(targets).toContain("/home/user/skyramp_session_storage.json");
|
|
286
|
+
// Host-absolute mirror — the fix for absolute paths leaking out of `__dirname`
|
|
287
|
+
expect(targets).toContain("/Users/pedro/projects/cisco-xdr-tests/xdr_dashboard.spec.ts");
|
|
288
|
+
expect(targets).toContain("/Users/pedro/projects/cisco-xdr-tests/skyramp_session_storage.json");
|
|
289
|
+
});
|
|
290
|
+
it("does not double-mount when workspacePath equals /home/user", async () => {
|
|
291
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
292
|
+
const fs = require("fs");
|
|
293
|
+
fs.readdirSync.mockImplementation((_path, options) => {
|
|
294
|
+
if (options?.withFileTypes) {
|
|
295
|
+
return [{ name: "test_file.py", isFile: () => true, isDirectory: () => false }];
|
|
296
|
+
}
|
|
297
|
+
return ["test_file.py"];
|
|
298
|
+
});
|
|
299
|
+
const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
|
|
300
|
+
mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
|
|
301
|
+
const service = new TestExecutionService();
|
|
302
|
+
await service.executeTest({
|
|
303
|
+
testFile: "/home/user/test_file.py",
|
|
304
|
+
workspacePath: "/home/user",
|
|
305
|
+
language: "python",
|
|
306
|
+
testType: "smoke",
|
|
307
|
+
});
|
|
308
|
+
const dockerOptions = mockRun.mock.calls[0][3];
|
|
309
|
+
const targetCounts = {};
|
|
310
|
+
for (const m of dockerOptions.HostConfig.Mounts) {
|
|
311
|
+
targetCounts[m.Target] = (targetCounts[m.Target] ?? 0) + 1;
|
|
312
|
+
}
|
|
313
|
+
// No mount target should appear twice (no host-absolute mirror when workspace == /home/user)
|
|
314
|
+
for (const [t, n] of Object.entries(targetCounts)) {
|
|
315
|
+
expect({ target: t, count: n }).toEqual({ target: t, count: 1 });
|
|
316
|
+
}
|
|
317
|
+
});
|
|
213
318
|
});
|
|
@@ -8,7 +8,8 @@ import { getEntryPoint } from "../utils/telemetry.js";
|
|
|
8
8
|
import { getLanguageSteps } from "../utils/language-helper.js";
|
|
9
9
|
import { logger } from "../utils/logger.js";
|
|
10
10
|
import { normalizeLanguageParams } from "../utils/normalizeParams.js";
|
|
11
|
-
import { stageGeneratedPaths } from "../utils/gitStaging.js";
|
|
11
|
+
import { stageGeneratedPaths, resolveOutputDir } from "../utils/gitStaging.js";
|
|
12
|
+
import { getTestsRepoDir } from "../utils/AnalysisStateManager.js";
|
|
12
13
|
export class TestGenerationService {
|
|
13
14
|
client;
|
|
14
15
|
constructor() {
|
|
@@ -18,6 +19,15 @@ export class TestGenerationService {
|
|
|
18
19
|
try {
|
|
19
20
|
// Normalize language/framework to handle LLM case variations
|
|
20
21
|
normalizeLanguageParams(params);
|
|
22
|
+
// In cross-repo mode, redirect outputDir to the test repo clone.
|
|
23
|
+
const resolved = resolveOutputDir(params.outputDir, getTestsRepoDir());
|
|
24
|
+
if (resolved !== params.outputDir) {
|
|
25
|
+
logger.info("Cross-repo: redirecting outputDir to test repo", {
|
|
26
|
+
original: params.outputDir,
|
|
27
|
+
redirected: resolved,
|
|
28
|
+
});
|
|
29
|
+
params.outputDir = resolved;
|
|
30
|
+
}
|
|
21
31
|
// Log prompt parameter using reusable utility
|
|
22
32
|
logger.info("Generating test", {
|
|
23
33
|
prompt: params.prompt,
|
|
@@ -147,16 +147,7 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
|
|
|
147
147
|
if (stateData && stateData.existingTests) {
|
|
148
148
|
const testIndex = stateData.existingTests.findIndex((t) => t.testFile === params.testFile);
|
|
149
149
|
if (testIndex >= 0) {
|
|
150
|
-
stateData.existingTests[testIndex].execution =
|
|
151
|
-
passed: result.passed,
|
|
152
|
-
duration: result.duration || 0,
|
|
153
|
-
errors: result.errors || [],
|
|
154
|
-
warnings: result.warnings || [],
|
|
155
|
-
crashed: result.crashed || false,
|
|
156
|
-
stdout: result.output || "",
|
|
157
|
-
stderr: result.errors?.join("\n") || "",
|
|
158
|
-
executionTimestamp: new Date().toISOString(),
|
|
159
|
-
};
|
|
150
|
+
stateData.existingTests[testIndex].execution = result;
|
|
160
151
|
await stateManager.writeData(stateData);
|
|
161
152
|
logger.info(`Updated stateFile with execution results for ${params.testFile}`);
|
|
162
153
|
}
|
|
@@ -48,10 +48,22 @@ export const stepSchema = z.object({
|
|
|
48
48
|
.string()
|
|
49
49
|
.optional()
|
|
50
50
|
.refine(isJsonObject, { message: "queryParams must be a JSON object string (e.g. '{\"limit\":\"10\"}')." })
|
|
51
|
-
.describe(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
.describe(`JSON string of URL query parameters. Provide a FLAT object where each key is the exact URL parameter name and each value is a string or number (single value per key).
|
|
52
|
+
|
|
53
|
+
<examples>
|
|
54
|
+
<example label="simple params">{"q": "bear", "limit": 10}</example>
|
|
55
|
+
<example label="bracket-notation keys">{"filter[title][_neq]": "not-a-number"}</example>
|
|
56
|
+
<example label="multi-value as comma-separated">{"tags": "sale,new,featured"}</example>
|
|
57
|
+
<example label="JSON-encoded string value">{"filter": "{\\\"status\\\":\\\"active\\\"}"}</example>
|
|
58
|
+
</examples>
|
|
59
|
+
|
|
60
|
+
For multi-value params (e.g. ?tags=sale,new), provide a comma-separated string: "tags": "sale,new". Arrays are accepted but will be comma-joined into one value — note this produces a single ?tags=sale,new param, not repeated ?tags=sale&tags=new keys.
|
|
61
|
+
|
|
62
|
+
If you provide nested objects, the service JSON-stringifies them as a fallback, but you should inspect the target API source to determine the correct key format and use that directly.
|
|
63
|
+
|
|
64
|
+
Use this for GET request filters, search terms, pagination, sorting — any parameter that belongs in the URL query string.
|
|
65
|
+
|
|
66
|
+
CRITICAL: For search/filter/list endpoints (e.g., GET /products/search?q=bear&limit=10), parameters MUST go here, NOT in requestBody. GET request bodies are non-standard and may be ignored or rejected by servers and frameworks.`),
|
|
55
67
|
responseBody: z
|
|
56
68
|
.string()
|
|
57
69
|
.optional()
|
|
@@ -28,6 +28,8 @@ const integrationTestSchema = z
|
|
|
28
28
|
"When provided, DO NOT also pass apiSchema or endpointURL — the scenario file already contains all endpoint information."),
|
|
29
29
|
...codeRefactoringSchema.shape,
|
|
30
30
|
...baseTestSchema,
|
|
31
|
+
apiSchema: baseTestSchema.apiSchema.describe("MUST be absolute path(/path/to/openapi.json) to the OpenAPI/Swagger schema file or a URL to the OpenAPI/Swagger schema file(e.g. https://demoshop.skyramp.dev/openapi.json). DO NOT TRY TO ASSUME THE OPENAPI SCHEMA IF NOT PROVIDED. NOTE TO AI ASSISTANTS: You do not need to read the contents of this file - simply pass the file path as the backend will read and process it. " +
|
|
32
|
+
"When an OpenAPI schema is provided, this tool automatically derives a CRUD scenario flow (Create → Read → Update → Delete) directly from the schema."),
|
|
31
33
|
output: baseTestSchema.output.describe("Name of the output test file. " +
|
|
32
34
|
"When scenarioFile is provided and user did not specify a name, derive it: " +
|
|
33
35
|
"strip the path and 'scenario_' prefix, replace hyphens/non-alphanum with underscores, append '_integration_test' + language extension. " +
|
|
@@ -96,6 +96,8 @@ This tells you exactly which frontend files changed so you record traces for the
|
|
|
96
96
|
|
|
97
97
|
**Typical pipeline:** Use the \`browser_*\` tools (\`browser_navigate\`, \`browser_click\`, \`browser_type\`, etc.) to record user interactions, then call \`skyramp_export_zip\` to export a trace zip, then pass the absolute path to that zip as \`playwrightInput\` here.
|
|
98
98
|
|
|
99
|
+
**DOM Analyzer tools for blueprint-aware recording:** alongside the basic \`browser_*\` interaction tools, the Skyramp MCP exposes \`browser_blueprint\` (canonical PageBlueprint capture), \`browser_blueprint_diff\` (structured before/after delta), \`browser_widget_contract_lookup\` (interaction recipe for custom widgets by fingerprint), and the sitemap tools \`browser_sitemap_build\` / \`browser_sitemap_query\`. These enable semantic target selection and delta-derived assertions: capture a blueprint before each meaningful action, perform the action, then capture again — the diff between the two grounds your assertions in observable state changes rather than author guesses about what "success" looks like.
|
|
100
|
+
|
|
99
101
|
**CRITICAL: Do NOT use skyramp_start_trace_collection/skyramp_stop_trace_collection for UI test recording — use browser_* tools + skyramp_export_zip instead.**`,
|
|
100
102
|
inputSchema: uiTestSchema,
|
|
101
103
|
_meta: {
|