Skip to content

Commit e17c31b

Browse files
committed
feat: Enhance test file handling by supporting specific file paths and improving validation
1 parent 828370d commit e17c31b

File tree

9 files changed

+103
-46
lines changed

9 files changed

+103
-46
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ process.on("exit", () => {
5151
export { setLogger } from "./logger.js";
5252
export { BrowserStackMcpServer } from "./server-factory.js";
5353
export { trackMCP } from "./lib/instrumentation.js";
54+
export const PackageJsonVersion = packageJson.version;

src/lib/inmemory-store.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
export const signedUrlMap = new Map<string, object>();
22
export const testFilePathsMap = new Map<string, string[]>();
3+
4+
let _storedPercyResults: any = null;
5+
6+
export const storedPercyResults = {
7+
get: () => _storedPercyResults,
8+
set: (value: any) => {
9+
_storedPercyResults = value;
10+
},
11+
};

src/tools/automate-utils/fetch-screenshots.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ async function convertUrlsToBase64(
5959
): Promise<Array<{ url: string; base64: string }>> {
6060
const screenshots = await Promise.all(
6161
urls.map(async (url) => {
62-
const response = await apiClient.get({
63-
url,
64-
responseType: "arraybuffer"
62+
const response = await apiClient.get({
63+
url,
64+
responseType: "arraybuffer",
6565
});
6666
// Axios returns response.data as a Buffer for binary data
6767
const base64 = Buffer.from(response.data).toString("base64");

src/tools/list-test-files.ts

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,57 @@
11
import { listTestFiles } from "./percy-snapshot-utils/detect-test-files.js";
2-
import { testFilePathsMap } from "../lib/inmemory-store.js";
2+
import { testFilePathsMap, storedPercyResults } from "../lib/inmemory-store.js";
3+
import { updateFileAndStep } from "./percy-snapshot-utils/utils.js";
4+
import { percyWebSetupInstructions } from "./sdk-utils/percy-web/handler.js";
35
import crypto from "crypto";
46
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
57

6-
export async function addListTestFiles(args: any): Promise<CallToolResult> {
7-
const { dirs, language, framework } = args;
8-
let testFiles: string[] = [];
9-
10-
if (!dirs || dirs.length === 0) {
8+
export async function addListTestFiles(): Promise<CallToolResult> {
9+
const storedResults = storedPercyResults.get();
10+
if (!storedResults) {
1111
throw new Error(
12-
"No directories provided to add the test files. Please provide test directories to add percy snapshot commands.",
12+
"No Framework details found. Please call expandPercyVisualTesting first to fetch the framework details.",
1313
);
1414
}
1515

16-
for (const dir of dirs) {
17-
const files = await listTestFiles({
18-
language,
19-
framework,
20-
baseDir: dir,
21-
});
16+
const language = storedResults.detectedLanguage;
17+
const framework = storedResults.detectedTestingFramework;
18+
19+
// Use stored paths from setUpPercy
20+
const dirs = storedResults.folderPaths;
21+
const files = storedResults.filePaths;
22+
23+
let testFiles: string[] = [];
24+
25+
if (files && files.length > 0) {
2226
testFiles = testFiles.concat(files);
2327
}
2428

29+
if (dirs && dirs.length > 0) {
30+
for (const dir of dirs) {
31+
const discoveredFiles = await listTestFiles({
32+
language,
33+
framework,
34+
baseDir: dir,
35+
});
36+
testFiles = testFiles.concat(discoveredFiles);
37+
}
38+
}
39+
40+
// Validate that we have at least one test file
2541
if (testFiles.length === 0) {
26-
throw new Error("No test files found");
42+
throw new Error(
43+
"No test files found. Please provide either specific file paths (files) or directory paths (dirs) containing test files.",
44+
);
45+
}
46+
47+
if (testFiles.length === 1) {
48+
const result = await updateFileAndStep(testFiles[0],0,1,percyWebSetupInstructions);
49+
return {
50+
content: result,
51+
};
2752
}
2853

29-
// Generate a UUID and store the test files in memory
54+
// For multiple files, use the UUID workflow
3055
const uuid = crypto.randomUUID();
3156
testFilePathsMap.set(uuid, testFiles);
3257

src/tools/percy-sdk.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,7 @@ import {
1818
PERCY_SNAPSHOT_COMMANDS_DESCRIPTION,
1919
SIMULATE_PERCY_CHANGE_DESCRIPTION,
2020
} from "./sdk-utils/common/constants.js";
21-
22-
import {
23-
ListTestFilesParamsShape,
24-
UpdateTestFileWithInstructionsParams,
25-
} from "./percy-snapshot-utils/constants.js";
21+
import { UpdateTestFileWithInstructionsParams } from "./percy-snapshot-utils/constants.js";
2622

2723
import {
2824
RunPercyScanParamsShape,
@@ -126,11 +122,11 @@ export function registerPercyTools(
126122
tools.listTestFiles = server.tool(
127123
"listTestFiles",
128124
LIST_TEST_FILES_DESCRIPTION,
129-
ListTestFilesParamsShape,
130-
async (args) => {
125+
{},
126+
async () => {
131127
try {
132128
trackMCP("listTestFiles", server.server.getClientVersion()!, config);
133-
return addListTestFiles(args);
129+
return addListTestFiles();
134130
} catch (error) {
135131
return handleMCPError("listTestFiles", server, config, error);
136132
}

src/tools/percy-snapshot-utils/constants.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import { z } from "zod";
2-
import {
3-
SDKSupportedLanguages,
4-
SDKSupportedTestingFrameworks,
5-
} from "../sdk-utils/common/types.js";
62
import { SDKSupportedLanguage } from "../sdk-utils/common/types.js";
73
import { DetectionConfig } from "./types.js";
84

@@ -13,18 +9,6 @@ export const UpdateTestFileWithInstructionsParams = {
139
index: z.number().describe("Index of the test file to update"),
1410
};
1511

16-
export const ListTestFilesParamsShape = {
17-
dirs: z
18-
.array(z.string())
19-
.describe("Array of directory paths to search for test files"),
20-
language: z
21-
.enum(SDKSupportedLanguages as [string, ...string[]])
22-
.describe("Programming language"),
23-
framework: z
24-
.enum(SDKSupportedTestingFrameworks as [string, ...string[]])
25-
.describe("Testing framework (optional)"),
26-
};
27-
2812
export const TEST_FILE_DETECTION: Record<
2913
SDKSupportedLanguage,
3014
DetectionConfig

src/tools/sdk-utils/common/schema.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,16 @@ export const SetUpPercyParamsShape = {
3636
),
3737
folderPaths: z
3838
.array(z.string())
39+
.optional()
3940
.describe(
4041
"An array of absolute folder paths containing UI test files. If not provided, analyze codebase for UI test folders by scanning for test patterns which contain UI test cases as per framework. Return empty array if none found.",
4142
),
43+
filePaths: z
44+
.array(z.string())
45+
.optional()
46+
.describe(
47+
"An array of absolute file paths to specific UI test files. Use this when you want to target specific test files rather than entire folders. If not provided, will use folderPaths instead.",
48+
),
4249
};
4350

4451
export const RunTestsOnBrowserStackParamsShape = {

src/tools/sdk-utils/common/utils.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
PercyAutomateNotImplementedType,
1212
} from "./types.js";
1313
import { IMPORTANT_SETUP_WARNING } from "./index.js";
14+
import { PackageJsonVersion } from "../../../index.js";
1415

1516
export function checkPercyIntegrationSupport(input: {
1617
integrationType: string;
@@ -108,14 +109,14 @@ export function getPercyAutomateNotImplementedMessage(
108109

109110
export function getBootstrapFailedMessage(
110111
error: unknown,
111-
context: { config: unknown; percyMode?: string; sdkVersion?: string },
112+
context: { config: unknown; percyMode?: string },
112113
): string {
113114
const error_message =
114115
error instanceof Error ? error.message : "unknown error";
115116
return `Failed to bootstrap project with BrowserStack SDK.
116117
Error: ${error_message}
117118
Percy Mode: ${context.percyMode ?? "automate"}
118-
SDK Version: ${context.sdkVersion ?? "N/A"}
119+
MCP Version: ${PackageJsonVersion}
119120
Please open an issue on GitHub if the problem persists.`;
120121
}
121122

@@ -136,3 +137,17 @@ export function percyUnsupportedResult(
136137
shouldSkipFormatting: true,
137138
};
138139
}
140+
141+
export function validatePercyPathandFolders(input: any): void {
142+
const hasFolderPaths = input.folderPaths && input.folderPaths.length > 0;
143+
const hasFilePaths = input.filePaths && input.filePaths.length > 0;
144+
145+
if (!hasFolderPaths && !hasFilePaths) {
146+
throw new Error(
147+
"Please provide either:\n" +
148+
"• folderPaths: Array of directory paths containing test files\n" +
149+
"• filePaths: Array of specific test file paths\n\n" +
150+
"Example: { filePaths: ['/path/to/test.spec.js'] }",
151+
);
152+
}
153+
}

src/tools/sdk-utils/handler.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import { runPercyWeb } from "./percy-web/handler.js";
88
import { runPercyAutomateOnly } from "./percy-automate/handler.js";
99
import { runBstackSDKOnly } from "./bstack/sdkHandler.js";
1010
import { runPercyWithBrowserstackSDK } from "./percy-bstack/handler.js";
11-
import { checkPercyIntegrationSupport } from "./common/utils.js";
11+
import {
12+
checkPercyIntegrationSupport,
13+
validatePercyPathandFolders,
14+
} from "./common/utils.js";
1215
import {
1316
SetUpPercySchema,
1417
RunTestsOnBrowserStackSchema,
1518
} from "./common/schema.js";
19+
import { storedPercyResults } from "../../lib/inmemory-store.js";
1620
import {
1721
getBootstrapFailedMessage,
1822
percyUnsupportedResult,
@@ -39,16 +43,32 @@ export async function setUpPercyHandler(
3943
): Promise<CallToolResult> {
4044
try {
4145
const input = SetUpPercySchema.parse(rawInput);
46+
validatePercyPathandFolders(input);
47+
48+
storedPercyResults.set({
49+
detectedLanguage: input.detectedLanguage,
50+
detectedBrowserAutomationFramework:
51+
input.detectedBrowserAutomationFramework,
52+
detectedTestingFramework: input.detectedTestingFramework,
53+
integrationType: input.integrationType,
54+
folderPaths: input.folderPaths || [],
55+
filePaths: input.filePaths || [],
56+
});
57+
4258
const authorization = getBrowserStackAuth(config);
4359

60+
const folderPaths = input.folderPaths || [];
61+
const filePaths = input.filePaths || [];
62+
4463
const percyInput = {
4564
projectName: input.projectName,
4665
detectedLanguage: input.detectedLanguage,
4766
detectedBrowserAutomationFramework:
4867
input.detectedBrowserAutomationFramework,
4968
detectedTestingFramework: input.detectedTestingFramework,
5069
integrationType: input.integrationType,
51-
folderPaths: input.folderPaths || [],
70+
folderPaths,
71+
filePaths,
5272
};
5373

5474
// Check for Percy Web integration support

0 commit comments

Comments
 (0)