@skyramp/mcp 0.3.1 → 0.3.2-rc.pom-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 +2 -1
- package/build/prompts/code-reuse.d.ts +7 -1
- package/build/prompts/code-reuse.js +8 -5
- package/build/prompts/code-reuse.test.d.ts +1 -0
- package/build/prompts/code-reuse.test.js +62 -0
- package/build/prompts/pom-aware-code-reuse.d.ts +6 -1
- package/build/prompts/pom-aware-code-reuse.js +100 -53
- package/build/prompts/pom-aware-code-reuse.test.d.ts +1 -0
- package/build/prompts/pom-aware-code-reuse.test.js +11 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +4 -2
- package/build/prompts/test-recommendation/diffExecutionPlan.js +11 -65
- package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +2 -2
- package/build/prompts/test-recommendation/recommendationSections.js +5 -2
- package/build/prompts/test-recommendation/scopeAssessment.js +1 -1
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +26 -1
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +68 -56
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +48 -11
- package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
- package/build/prompts/testbot/testbot-prompts.js +67 -21
- package/build/prompts/testbot/testbot-prompts.test.js +44 -0
- package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +7 -0
- package/build/recommendation/budgeters/diversityBalancedBudgeter.js +71 -0
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.d.ts +1 -0
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.js +75 -0
- package/build/recommendation/budgeters/fixedNBudgeter.d.ts +7 -0
- package/build/recommendation/budgeters/fixedNBudgeter.js +11 -0
- package/build/recommendation/budgeters/fixedNBudgeter.test.d.ts +1 -0
- package/build/recommendation/budgeters/fixedNBudgeter.test.js +66 -0
- package/build/recommendation/budgeters/shared.d.ts +19 -0
- package/build/recommendation/budgeters/shared.js +66 -0
- package/build/recommendation/discriminators.d.ts +31 -0
- package/build/recommendation/discriminators.js +355 -0
- package/build/recommendation/discriminators.test.d.ts +1 -0
- package/build/recommendation/discriminators.test.js +324 -0
- package/build/recommendation/diversity.d.ts +47 -0
- package/build/recommendation/diversity.js +101 -0
- package/build/recommendation/diversity.test.d.ts +1 -0
- package/build/recommendation/diversity.test.js +77 -0
- package/build/recommendation/planRanker.d.ts +50 -0
- package/build/recommendation/planRanker.js +67 -0
- package/build/recommendation/planRanker.test.d.ts +1 -0
- package/build/recommendation/planRanker.test.js +110 -0
- package/build/recommendation/testFixtures.d.ts +25 -0
- package/build/recommendation/testFixtures.js +45 -0
- package/build/resources/testbotResource.js +4 -1
- package/build/services/ScenarioGenerationService.d.ts +5 -0
- package/build/services/ScenarioGenerationService.js +16 -1
- package/build/services/ScenarioGenerationService.test.js +44 -0
- package/build/services/TestExecutionService.d.ts +15 -1
- package/build/services/TestExecutionService.js +210 -55
- package/build/services/TestExecutionService.test.js +397 -0
- package/build/services/TestGenerationService.js +19 -1
- package/build/services/TestGenerationService.test.js +58 -0
- package/build/tool-phases.js +1 -0
- package/build/toolNames.d.ts +19 -0
- package/build/toolNames.js +19 -0
- package/build/tools/code-refactor/codeReuseTool.d.ts +7 -0
- package/build/tools/code-refactor/codeReuseTool.js +130 -4
- package/build/tools/code-refactor/codeReuseTool.test.d.ts +1 -0
- package/build/tools/code-refactor/codeReuseTool.test.js +290 -0
- package/build/tools/executeSkyrampTestTool.js +8 -2
- package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +6 -1
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +110 -17
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +147 -0
- package/build/tools/generate-tests/generateContractRestTool.js +11 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.js +24 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.test.d.ts +1 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.test.js +159 -0
- package/build/tools/generate-tests/planGuard.d.ts +13 -0
- package/build/tools/generate-tests/planGuard.js +78 -0
- package/build/tools/generate-tests/planGuard.test.d.ts +1 -0
- package/build/tools/generate-tests/planGuard.test.js +185 -0
- package/build/tools/generate-tests/scenarioFileIdentity.d.ts +10 -0
- package/build/tools/generate-tests/scenarioFileIdentity.js +46 -0
- package/build/tools/generate-tests/scenarioLint.d.ts +30 -0
- package/build/tools/generate-tests/scenarioLint.js +150 -0
- package/build/tools/generate-tests/scenarioLint.test.d.ts +1 -0
- package/build/tools/generate-tests/scenarioLint.test.js +100 -0
- package/build/tools/submitReportTool.js +78 -0
- package/build/tools/submitReportTool.test.js +255 -0
- package/build/tools/test-management/analyzeChangesTool.js +55 -2
- package/build/tools/test-management/analyzeChangesTool.test.js +12 -0
- package/build/tools/test-management/index.d.ts +1 -0
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +2 -0
- package/build/tools/test-management/registerTestPlanTool.js +329 -0
- package/build/tools/test-management/registerTestPlanTool.test.d.ts +1 -0
- package/build/tools/test-management/registerTestPlanTool.test.js +296 -0
- package/build/types/Recommendation.d.ts +97 -0
- package/build/types/Recommendation.js +48 -0
- package/build/types/RepositoryAnalysis.d.ts +14 -14
- package/build/types/TestExecution.d.ts +2 -0
- package/build/types/TestRecommendation.d.ts +12 -1
- package/build/types/TestRecommendation.js +26 -11
- package/build/types/TestTypes.js +1 -1
- package/build/utils/AnalysisStateManager.d.ts +47 -0
- package/build/utils/docker.test.js +1 -1
- package/build/utils/planMatchKeys.d.ts +61 -0
- package/build/utils/planMatchKeys.js +125 -0
- package/build/utils/pom-scope/import-expansion.d.ts +5 -0
- package/build/utils/pom-scope/import-expansion.js +32 -0
- package/build/utils/pom-scope/index.d.ts +39 -0
- package/build/utils/pom-scope/index.js +120 -0
- package/build/utils/pom-scope/index.test.d.ts +1 -0
- package/build/utils/pom-scope/index.test.js +239 -0
- package/build/utils/pom-scope/pom-files.d.ts +3 -0
- package/build/utils/pom-scope/pom-files.js +48 -0
- package/build/utils/pom-scope/pom-files.test.d.ts +1 -0
- package/build/utils/pom-scope/pom-files.test.js +29 -0
- package/build/utils/pom-scope/scoring.d.ts +20 -0
- package/build/utils/pom-scope/scoring.js +45 -0
- package/build/utils/pom-scope/scoring.test.d.ts +1 -0
- package/build/utils/pom-scope/scoring.test.js +39 -0
- package/build/utils/pom-scope/selector-extractor.d.ts +7 -0
- package/build/utils/pom-scope/selector-extractor.js +57 -0
- package/build/utils/pom-scope/selector-extractor.test.d.ts +1 -0
- package/build/utils/pom-scope/selector-extractor.test.js +67 -0
- package/build/utils/pom-verify/__fixtures__/af-style/asset-list.page.d.ts +5 -0
- package/build/utils/pom-verify/__fixtures__/af-style/asset-list.page.js +5 -0
- package/build/utils/pom-verify/__fixtures__/af-style/pageobjects/asset-list-page.d.ts +5 -0
- package/build/utils/pom-verify/__fixtures__/af-style/pageobjects/asset-list-page.js +9 -0
- package/build/utils/pom-verify/__fixtures__/af-style/report.iframe.page.d.ts +4 -0
- package/build/utils/pom-verify/__fixtures__/af-style/report.iframe.page.js +4 -0
- package/build/utils/pom-verify/__fixtures__/af-style/workflow-footer.page.d.ts +4 -0
- package/build/utils/pom-verify/__fixtures__/af-style/workflow-footer.page.js +4 -0
- package/build/utils/pom-verify/bindings.d.ts +19 -0
- package/build/utils/pom-verify/bindings.js +161 -0
- package/build/utils/pom-verify/bindings.test.d.ts +1 -0
- package/build/utils/pom-verify/bindings.test.js +164 -0
- package/build/utils/pom-verify/calls.d.ts +16 -0
- package/build/utils/pom-verify/calls.js +42 -0
- package/build/utils/pom-verify/calls.test.d.ts +1 -0
- package/build/utils/pom-verify/calls.test.js +61 -0
- package/build/utils/pom-verify/index.d.ts +4 -0
- package/build/utils/pom-verify/index.js +4 -0
- package/build/utils/pom-verify/resolve.d.ts +7 -0
- package/build/utils/pom-verify/resolve.js +27 -0
- package/build/utils/pom-verify/resolve.test.d.ts +1 -0
- package/build/utils/pom-verify/resolve.test.js +68 -0
- package/build/utils/pom-verify/strip.d.ts +9 -0
- package/build/utils/pom-verify/strip.js +89 -0
- package/build/utils/pom-verify/verify.d.ts +14 -0
- package/build/utils/pom-verify/verify.js +158 -0
- package/build/utils/pom-verify/verify.test.d.ts +1 -0
- package/build/utils/pom-verify/verify.test.js +325 -0
- package/build/utils/reportVerification.d.ts +61 -0
- package/build/utils/reportVerification.js +104 -0
- package/build/utils/reportVerification.test.d.ts +1 -0
- package/build/utils/reportVerification.test.js +185 -0
- package/build/utils/scenarioDrafting.js +5 -5
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.d.ts +9 -1
- package/build/utils/workspaceAuth.js +25 -5
- package/build/utils/workspaceAuth.test.js +48 -0
- package/build/workspace/workspace.d.ts +20 -0
- package/build/workspace/workspace.js +4 -0
- package/build/workspace/workspace.test.js +10 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +77 -8
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +101 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-aszq5EdG.js → codeMirrorModule-Bzd72-bG.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-BxS7Jm4s.js → defaultSettingsView-DzxTioTK.js} +101 -101
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.D4JTTy4R.js → index.BGc30U3S.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.DaRMQKOI.js → uiMode.IaDrb29A.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/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +131 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { TestExecutionResult, BatchExecutionResult, TestExecutionOptions, ProgressCallback } from "../types/TestExecution.js";
|
|
2
|
-
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.
|
|
2
|
+
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.35";
|
|
3
3
|
export declare const PLAYWRIGHT_CONFIG_FILES: string[];
|
|
4
4
|
export declare const EXCLUDED_MOUNT_ITEMS: string[];
|
|
5
|
+
export declare const TEST_REPO_CONTAINER_SUBDIR = ".skyramp-test-repo";
|
|
5
6
|
export declare const MOUNT_NULL_ITEMS: string[];
|
|
6
7
|
/**
|
|
7
8
|
* Check if a test type requires browser execution (UI/E2E)
|
|
@@ -35,6 +36,19 @@ export declare function detectSessionFiles(testFilePath: string): string[];
|
|
|
35
36
|
* Skips recursing into directories that are themselves excluded.
|
|
36
37
|
*/
|
|
37
38
|
export declare function findExcludedPaths(dir: string, excludedItems: string[]): string[];
|
|
39
|
+
/**
|
|
40
|
+
* Find every nested `node_modules` directory under `dir` — i.e. all except the
|
|
41
|
+
* top-level `<dir>/node_modules` (which the workspace mount already excludes).
|
|
42
|
+
* Never descends into a `node_modules`. Used to shadow repo-local node_modules
|
|
43
|
+
* that ride into the executor inside a parent directory's wholesale bind-mount;
|
|
44
|
+
* a leaked `@playwright/test` becomes a second physical copy and trips
|
|
45
|
+
* Playwright's "Requiring @playwright/test second time" guard.
|
|
46
|
+
* Skips `.git` (nothing importable lives there) and does not follow symlinks —
|
|
47
|
+
* by design (a symlinked nested node_modules either dangles in-container,
|
|
48
|
+
* falling through to NODE_PATH's global copy, or its real-path target is
|
|
49
|
+
* walked directly).
|
|
50
|
+
*/
|
|
51
|
+
export declare function findNestedNodeModules(dir: string): string[];
|
|
38
52
|
export declare class TestExecutionService {
|
|
39
53
|
private docker;
|
|
40
54
|
private imageReady;
|
|
@@ -20,6 +20,28 @@ const EXECUTION_PROGRESS_INTERVAL = 10000; // 10 seconds between progress update
|
|
|
20
20
|
// so Node.js doesn't throw ERR_INVALID_PACKAGE_CONFIG when reading them.
|
|
21
21
|
const EMPTY_JSON_PATH = path.join(os.tmpdir(), "skyramp-empty.json");
|
|
22
22
|
fs.writeFileSync(EMPTY_JSON_PATH, "{}");
|
|
23
|
+
// Empty directory used to shadow nested node_modules over their leaked mount
|
|
24
|
+
// (a directory target — /dev/null only works for file targets). Per-process
|
|
25
|
+
// mkdtemp guarantees it stays empty; best-effort removal on exit keeps tmp
|
|
26
|
+
// from accumulating one dir per process start on long-lived hosts.
|
|
27
|
+
const EMPTY_DIR_PATH = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-empty-"));
|
|
28
|
+
process.on("exit", () => {
|
|
29
|
+
try {
|
|
30
|
+
fs.rmdirSync(EMPTY_DIR_PATH);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// best-effort: bind mounts from a still-running container or a prior crash may block removal
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
// Packages that must never load from a repo-local (nested) node_modules: a
|
|
37
|
+
// second physical @playwright/test trips Playwright's "Requiring
|
|
38
|
+
// @playwright/test second time" guard, and a host-installed @skyramp/skyramp
|
|
39
|
+
// carries the wrong platform's native library. Shadow ONLY these inside each
|
|
40
|
+
// nested node_modules — customer test-support deps (winston, dotenv, …) keep
|
|
41
|
+
// resolving from the mounted repo. Node treats the empty shadow as a miss and
|
|
42
|
+
// walks up to NODE_PATH's global install (both packages are global in the
|
|
43
|
+
// executor image).
|
|
44
|
+
const SHADOWED_NESTED_PACKAGES = ["@playwright/test", "playwright", "@skyramp/skyramp"];
|
|
23
45
|
// Minimal Playwright config — shadows repo configs that may import dotenv or other
|
|
24
46
|
// dependencies not available in the executor container.
|
|
25
47
|
const MINIMAL_PLAYWRIGHT_CONFIG_PATH = path.join(os.tmpdir(), "skyramp-playwright.config.ts");
|
|
@@ -36,6 +58,11 @@ export const PLAYWRIGHT_CONFIG_FILES = [
|
|
|
36
58
|
export const EXCLUDED_MOUNT_ITEMS = [
|
|
37
59
|
"node_modules",
|
|
38
60
|
];
|
|
61
|
+
// Reserved container subdir under /home/user where a cross-repo test repository
|
|
62
|
+
// (testsRepoDir, SKYR-3819) is mounted. Must live INSIDE /home/user: the generated
|
|
63
|
+
// Playwright config's testDir is anchored there, and a test path outside it is
|
|
64
|
+
// reported as "No tests found".
|
|
65
|
+
export const TEST_REPO_CONTAINER_SUBDIR = ".skyramp-test-repo";
|
|
39
66
|
// Files to shadow with /dev/null recursively so the container ignores them
|
|
40
67
|
export const MOUNT_NULL_ITEMS = [
|
|
41
68
|
"package-lock.json",
|
|
@@ -336,6 +363,45 @@ export function findExcludedPaths(dir, excludedItems) {
|
|
|
336
363
|
}
|
|
337
364
|
return results;
|
|
338
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Find every nested `node_modules` directory under `dir` — i.e. all except the
|
|
368
|
+
* top-level `<dir>/node_modules` (which the workspace mount already excludes).
|
|
369
|
+
* Never descends into a `node_modules`. Used to shadow repo-local node_modules
|
|
370
|
+
* that ride into the executor inside a parent directory's wholesale bind-mount;
|
|
371
|
+
* a leaked `@playwright/test` becomes a second physical copy and trips
|
|
372
|
+
* Playwright's "Requiring @playwright/test second time" guard.
|
|
373
|
+
* Skips `.git` (nothing importable lives there) and does not follow symlinks —
|
|
374
|
+
* by design (a symlinked nested node_modules either dangles in-container,
|
|
375
|
+
* falling through to NODE_PATH's global copy, or its real-path target is
|
|
376
|
+
* walked directly).
|
|
377
|
+
*/
|
|
378
|
+
export function findNestedNodeModules(dir) {
|
|
379
|
+
const results = [];
|
|
380
|
+
const walk = (current) => {
|
|
381
|
+
let entries;
|
|
382
|
+
try {
|
|
383
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return; // best-effort: skip unreadable directory, keep going
|
|
387
|
+
}
|
|
388
|
+
for (const entry of entries) {
|
|
389
|
+
if (!entry.isDirectory())
|
|
390
|
+
continue;
|
|
391
|
+
if (entry.name === ".git")
|
|
392
|
+
continue;
|
|
393
|
+
const full = path.join(current, entry.name);
|
|
394
|
+
if (entry.name === "node_modules") {
|
|
395
|
+
if (current !== dir)
|
|
396
|
+
results.push(full); // skip the top-level node_modules
|
|
397
|
+
continue; // never descend into a node_modules
|
|
398
|
+
}
|
|
399
|
+
walk(full);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
walk(dir);
|
|
403
|
+
return results;
|
|
404
|
+
}
|
|
339
405
|
export class TestExecutionService {
|
|
340
406
|
docker;
|
|
341
407
|
imageReady = null;
|
|
@@ -442,14 +508,59 @@ export class TestExecutionService {
|
|
|
442
508
|
});
|
|
443
509
|
const containerMountPath = "/home/user";
|
|
444
510
|
const dockerSocketPath = "/var/run/docker.sock";
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
511
|
+
const isUnder = (parent, child) => child === parent || child.startsWith(parent + path.sep);
|
|
512
|
+
// Cross-repo test file (SKYR-3819): a test delivered under the run's test
|
|
513
|
+
// repository lives outside workspacePath, so the workspace mounts below never
|
|
514
|
+
// include it and the relative-path computation would escape /home/user. Anchor
|
|
515
|
+
// it under the reserved subdir instead; the test repo is mounted there below.
|
|
516
|
+
const absTestFile = path.resolve(options.testFile);
|
|
517
|
+
const testRepoPath = options.testRepoPath ? path.resolve(options.testRepoPath) : undefined;
|
|
518
|
+
// A root-level test repo would bind-mount large portions of the host
|
|
519
|
+
// (read-write, alongside the Docker socket) into the executor — reject it
|
|
520
|
+
// explicitly rather than falling through to the generic path error below.
|
|
521
|
+
if (testRepoPath &&
|
|
522
|
+
testRepoPath === path.parse(testRepoPath).root &&
|
|
523
|
+
!isUnder(workspacePath, absTestFile)) {
|
|
524
|
+
throw new Error(`testRepoPath must not be the filesystem root, got: ${options.testRepoPath}`);
|
|
525
|
+
}
|
|
526
|
+
const crossRepoTestRoot = !isUnder(workspacePath, absTestFile) && testRepoPath && isUnder(testRepoPath, absTestFile)
|
|
527
|
+
? testRepoPath
|
|
528
|
+
: undefined;
|
|
529
|
+
if (crossRepoTestRoot) {
|
|
530
|
+
try {
|
|
531
|
+
fs.accessSync(crossRepoTestRoot, fs.constants.R_OK);
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
throw new Error(`Cross-repo test repository path does not exist or is not readable: ${crossRepoTestRoot}`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
let testFilePath;
|
|
538
|
+
if (crossRepoTestRoot) {
|
|
539
|
+
testFilePath = path.join(containerMountPath, TEST_REPO_CONTAINER_SUBDIR, path.relative(crossRepoTestRoot, absTestFile));
|
|
540
|
+
}
|
|
541
|
+
else if (!isUnder(workspacePath, absTestFile)) {
|
|
542
|
+
throw new Error(`Test file ${options.testFile} is outside the workspace ${workspacePath}, so the executor container cannot reach it. ` +
|
|
543
|
+
`For tests delivered to a cross-repo test repository, run skyramp_analyze_changes with testsRepoDir first (it is remembered for the run), or move the test under the workspace.`);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
testFilePath = path.resolve(containerMountPath, path.relative(workspacePath, absTestFile));
|
|
547
|
+
}
|
|
448
548
|
// Video recording setup for UI/E2E browser tests (no-op for API tests).
|
|
449
549
|
const { browserTest, videoHostDir, videoContainerDir, playwrightConfigPath, pytestConfigPath } = setupVideoCapture(options, workspacePath, containerMountPath);
|
|
450
550
|
// Prepare Docker command
|
|
451
551
|
const command = [
|
|
452
|
-
"
|
|
552
|
+
"bash",
|
|
553
|
+
"-c",
|
|
554
|
+
// Bare requires from mounted/reused customer code must resolve the shadowed
|
|
555
|
+
// packages (@playwright/test, @skyramp/skyramp — see SHADOWED_NESTED_PACKAGES)
|
|
556
|
+
// from the image's single global install; npm root -g resolves that path
|
|
557
|
+
// regardless of the image's pinned Node version. The current executor image
|
|
558
|
+
// already sets NODE_PATH via ENV; this wrap makes the resolution
|
|
559
|
+
// image-independent rather than relying on that.
|
|
560
|
+
// The wrap assumes `bash` and `npm` exist in the executor image (both
|
|
561
|
+
// verified present; runner.sh already requires bash).
|
|
562
|
+
'export NODE_PATH="$(npm root -g)"; exec /root/runner.sh "$@"',
|
|
563
|
+
"runner",
|
|
453
564
|
options.language,
|
|
454
565
|
testFilePath,
|
|
455
566
|
options.testType,
|
|
@@ -466,20 +577,6 @@ export class TestExecutionService {
|
|
|
466
577
|
},
|
|
467
578
|
],
|
|
468
579
|
};
|
|
469
|
-
// Mount workspace files, skipping EXCLUDED_MOUNT_ITEMS completely.
|
|
470
|
-
//
|
|
471
|
-
// Each workspace entry is bind-mounted at BOTH the canonical /home/user
|
|
472
|
-
// path AND its host-absolute path. The dual mount lets the test resolve
|
|
473
|
-
// any absolute reference the codegen happens to embed (storageState,
|
|
474
|
-
// fixture paths, snapshots) — including the host workspace path that
|
|
475
|
-
// Playwright's TypeScript loader sometimes produces from `__dirname` —
|
|
476
|
-
// without needing source-code detection. EXCLUDED_MOUNT_ITEMS
|
|
477
|
-
// (node_modules) stays excluded at both targets; MOUNT_NULL_ITEMS
|
|
478
|
-
// shadows (package.json → empty JSON, etc.) and PLAYWRIGHT_CONFIG_FILES
|
|
479
|
-
// shadows (minimal config) are applied at both targets too so the
|
|
480
|
-
// protections survive regardless of which path the test resolves to.
|
|
481
|
-
const workspaceFiles = fs.readdirSync(workspacePath);
|
|
482
|
-
const filesToMount = workspaceFiles.filter((file) => !EXCLUDED_MOUNT_ITEMS.includes(file) && !MOUNT_NULL_ITEMS.includes(file));
|
|
483
580
|
// Single Set tracks every mount target we've added so far. Used to dedupe
|
|
484
581
|
// both the workspace-mirror push (when workspacePath happens to equal
|
|
485
582
|
// containerMountPath) and the session-file push below.
|
|
@@ -490,6 +587,92 @@ export class TestExecutionService {
|
|
|
490
587
|
mountedPaths.add(mount.Target);
|
|
491
588
|
hostConfig.Mounts.push(mount);
|
|
492
589
|
};
|
|
590
|
+
// Mount one repo tree (the workspace, or a cross-repo test repository) into
|
|
591
|
+
// the container, skipping EXCLUDED_MOUNT_ITEMS completely.
|
|
592
|
+
//
|
|
593
|
+
// Each top-level entry is bind-mounted at the canonical container path and —
|
|
594
|
+
// when mirrorToHost is set — at its host-absolute path too. The dual mount
|
|
595
|
+
// lets the test resolve any absolute reference the codegen happens to embed
|
|
596
|
+
// (storageState, fixture paths, snapshots) — including the host workspace
|
|
597
|
+
// path that Playwright's TypeScript loader sometimes produces from
|
|
598
|
+
// `__dirname` — without needing source-code detection. EXCLUDED_MOUNT_ITEMS
|
|
599
|
+
// (node_modules) stays excluded at both targets; MOUNT_NULL_ITEMS shadows
|
|
600
|
+
// (package.json → empty JSON, etc.), nested-package shadows, and
|
|
601
|
+
// PLAYWRIGHT_CONFIG_FILES shadows (minimal config) are applied at both
|
|
602
|
+
// targets too so the protections survive regardless of which path the test
|
|
603
|
+
// resolves to.
|
|
604
|
+
//
|
|
605
|
+
// Ordering matters between the broad mounts and the shadows: Docker layers
|
|
606
|
+
// later mounts over earlier ones, so the top-level entry mounts must be
|
|
607
|
+
// pushed BEFORE the shadow mounts that override paths inside them.
|
|
608
|
+
//
|
|
609
|
+
// The three recursive scans (findExcludedPaths × 2 + findNestedNodeModules)
|
|
610
|
+
// are deliberately kept as separate walks rather than fused into one pass:
|
|
611
|
+
// each skips node_modules/.git so the cost is small, and separate walks keep
|
|
612
|
+
// each shadow category's semantics independently testable.
|
|
613
|
+
const mountRepoTree = (hostRoot, containerRoot, mirrorToHost) => {
|
|
614
|
+
// Top-level entries. MOUNT_NULL_ITEMS are excluded here and shadowed
|
|
615
|
+
// recursively below instead. Root-level Playwright configs are excluded
|
|
616
|
+
// here too so the config-shadow loop below (or the pre-mounted generated
|
|
617
|
+
// config, which wins by dedup) supplies the container's config instead —
|
|
618
|
+
// otherwise the entry mount would win the dedup race and the repo's own
|
|
619
|
+
// config (which may import dotenv, or simply isn't the minimal/generated
|
|
620
|
+
// one) would leak into the container.
|
|
621
|
+
const entriesToMount = fs
|
|
622
|
+
.readdirSync(hostRoot)
|
|
623
|
+
.filter((file) => !EXCLUDED_MOUNT_ITEMS.includes(file) &&
|
|
624
|
+
!MOUNT_NULL_ITEMS.includes(file) &&
|
|
625
|
+
!PLAYWRIGHT_CONFIG_FILES.includes(file));
|
|
626
|
+
for (const file of entriesToMount) {
|
|
627
|
+
const source = path.join(hostRoot, file);
|
|
628
|
+
pushMount({ Type: "bind", Target: path.join(containerRoot, file), Source: source });
|
|
629
|
+
if (mirrorToHost) {
|
|
630
|
+
pushMount({ Type: "bind", Target: source, Source: source });
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// Shadow MOUNT_NULL_ITEMS (found recursively) with /dev/null (or empty JSON for .json files).
|
|
634
|
+
for (const absolutePath of findExcludedPaths(hostRoot, MOUNT_NULL_ITEMS)) {
|
|
635
|
+
const rel = path.relative(hostRoot, absolutePath);
|
|
636
|
+
const source = absolutePath.endsWith(".json") ? EMPTY_JSON_PATH : "/dev/null";
|
|
637
|
+
pushMount({ Type: "bind", Source: source, Target: path.join(containerRoot, rel) });
|
|
638
|
+
if (mirrorToHost) {
|
|
639
|
+
pushMount({ Type: "bind", Source: source, Target: absolutePath });
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// Shadow SHADOWED_NESTED_PACKAGES inside every nested node_modules leaked in
|
|
643
|
+
// via a parent-directory bind-mount (see findNestedNodeModules) — the
|
|
644
|
+
// top-level node_modules is already excluded above via EXCLUDED_MOUNT_ITEMS.
|
|
645
|
+
for (const nested of findNestedNodeModules(hostRoot)) {
|
|
646
|
+
for (const pkg of SHADOWED_NESTED_PACKAGES) {
|
|
647
|
+
const pkgPath = path.join(nested, pkg);
|
|
648
|
+
if (!fs.existsSync(pkgPath))
|
|
649
|
+
continue; // only shadow what actually exists
|
|
650
|
+
const rel = path.relative(hostRoot, pkgPath);
|
|
651
|
+
pushMount({
|
|
652
|
+
Type: "bind",
|
|
653
|
+
Source: EMPTY_DIR_PATH,
|
|
654
|
+
Target: path.join(containerRoot, rel),
|
|
655
|
+
ReadOnly: true,
|
|
656
|
+
});
|
|
657
|
+
if (mirrorToHost) {
|
|
658
|
+
pushMount({ Type: "bind", Source: EMPTY_DIR_PATH, Target: pkgPath, ReadOnly: true });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// Mount Playwright config files with the per-execution config (minimal or
|
|
663
|
+
// generated). Shadows ANY repo playwright.config.{ts,js,mjs} recursively so
|
|
664
|
+
// repo configs that import dotenv/etc. don't crash the executor. Applies to
|
|
665
|
+
// ALL tests — including non-browser TypeScript tests
|
|
666
|
+
// (contract/integration/fuzz/smoke/load) which per runner.sh also run via
|
|
667
|
+
// `npx playwright test <file>` and read playwright.config.ts.
|
|
668
|
+
for (const absolutePath of findExcludedPaths(hostRoot, PLAYWRIGHT_CONFIG_FILES)) {
|
|
669
|
+
const rel = path.relative(hostRoot, absolutePath);
|
|
670
|
+
pushMount({ Type: "bind", Source: playwrightConfigPath, Target: path.join(containerRoot, rel) });
|
|
671
|
+
if (mirrorToHost) {
|
|
672
|
+
pushMount({ Type: "bind", Source: playwrightConfigPath, Target: absolutePath });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
};
|
|
493
676
|
// For browser tests, mount generated video-enabled configs at canonical container
|
|
494
677
|
// paths BEFORE the nullPaths loop so they WIN over any /dev/null shadows and work
|
|
495
678
|
// even if the repo has no config file. The pushMount dedup ensures later shadows
|
|
@@ -512,44 +695,16 @@ export class TestExecutionService {
|
|
|
512
695
|
});
|
|
513
696
|
}
|
|
514
697
|
}
|
|
698
|
+
// Mirror workspace mounts at their host-absolute paths unless the workspace
|
|
699
|
+
// already IS the canonical container path (mirroring would double-mount).
|
|
515
700
|
const mirrorAtHostPath = workspacePath !== containerMountPath;
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (mirrorAtHostPath) {
|
|
524
|
-
pushMount({ Type: "bind", Target: source, Source: source });
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
// Mount MOUNT_NULL_ITEMS (found recursively) to /dev/null (or empty JSON for .json files)
|
|
528
|
-
const nullPaths = findExcludedPaths(workspacePath, MOUNT_NULL_ITEMS);
|
|
529
|
-
for (const absolutePath of nullPaths) {
|
|
530
|
-
const rel = path.relative(workspacePath, absolutePath);
|
|
531
|
-
const source = absolutePath.endsWith(".json") ? EMPTY_JSON_PATH : "/dev/null";
|
|
532
|
-
pushMount({
|
|
533
|
-
Type: "bind",
|
|
534
|
-
Source: source,
|
|
535
|
-
Target: path.join(containerMountPath, rel),
|
|
536
|
-
});
|
|
537
|
-
if (mirrorAtHostPath) {
|
|
538
|
-
pushMount({ Type: "bind", Source: source, Target: absolutePath });
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
// Mount Playwright config files with the per-execution config (minimal or generated).
|
|
542
|
-
// Shadows ANY repo playwright.config.{ts,js,mjs} recursively so repo configs that
|
|
543
|
-
// import dotenv/etc. don't crash the executor. Applies to ALL tests — including
|
|
544
|
-
// non-browser TypeScript tests (contract/integration/fuzz/smoke/load) which per
|
|
545
|
-
// runner.sh also run via `npx playwright test <file>` and read playwright.config.ts.
|
|
546
|
-
const playwrightConfigPaths = findExcludedPaths(workspacePath, PLAYWRIGHT_CONFIG_FILES);
|
|
547
|
-
for (const absolutePath of playwrightConfigPaths) {
|
|
548
|
-
const rel = path.relative(workspacePath, absolutePath);
|
|
549
|
-
pushMount({ Type: "bind", Source: playwrightConfigPath, Target: path.join(containerMountPath, rel) });
|
|
550
|
-
if (mirrorAtHostPath) {
|
|
551
|
-
pushMount({ Type: "bind", Source: playwrightConfigPath, Target: absolutePath });
|
|
552
|
-
}
|
|
701
|
+
mountRepoTree(workspacePath, containerMountPath, mirrorAtHostPath);
|
|
702
|
+
// Cross-repo test repository mounts (SKYR-3819): expose the delivered tests
|
|
703
|
+
// under the reserved /home/user subdir — inside Playwright's testDir — with
|
|
704
|
+
// the same exclusions and shadows as the workspace, plus host-absolute
|
|
705
|
+
// mirrors so absolute references embedded in tests resolve.
|
|
706
|
+
if (crossRepoTestRoot) {
|
|
707
|
+
mountRepoTree(crossRepoTestRoot, path.join(containerMountPath, TEST_REPO_CONTAINER_SUBDIR), true);
|
|
553
708
|
}
|
|
554
709
|
// Mount video output directory for browser tests (writable)
|
|
555
710
|
if (browserTest && videoHostDir) {
|