@browserstack/mcp-server 1.2.4 → 1.2.6
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/README.md +9 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/apiClient.d.ts +8 -5
- package/dist/lib/apiClient.js +77 -15
- package/dist/lib/device-cache.d.ts +3 -1
- package/dist/lib/device-cache.js +4 -0
- package/dist/lib/inmemory-store.d.ts +5 -1
- package/dist/lib/inmemory-store.js +10 -1
- package/dist/lib/instrumentation.js +6 -3
- package/dist/lib/utils.d.ts +75 -2
- package/dist/lib/utils.js +20 -0
- package/dist/lib/version-resolver.js +30 -14
- package/dist/tools/add-percy-snapshots.d.ts +0 -1
- package/dist/tools/add-percy-snapshots.js +11 -6
- package/dist/tools/appautomate-utils/appium-sdk/config-generator.d.ts +7 -1
- package/dist/tools/appautomate-utils/appium-sdk/config-generator.js +46 -26
- package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +1 -1
- package/dist/tools/appautomate-utils/appium-sdk/constants.js +24 -3
- package/dist/tools/appautomate-utils/appium-sdk/handler.js +16 -2
- package/dist/tools/appautomate-utils/appium-sdk/languages/java.d.ts +2 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/java.js +63 -29
- package/dist/tools/appautomate-utils/appium-sdk/types.d.ts +2 -1
- package/dist/tools/appautomate-utils/appium-sdk/types.js +10 -1
- package/dist/tools/appautomate-utils/native-execution/constants.d.ts +2 -1
- package/dist/tools/appautomate-utils/native-execution/constants.js +24 -2
- package/dist/tools/appautomate.js +15 -2
- package/dist/tools/automate-utils/fetch-screenshots.js +4 -1
- package/dist/tools/list-test-files.d.ts +1 -1
- package/dist/tools/list-test-files.js +43 -19
- package/dist/tools/percy-sdk.js +33 -6
- package/dist/tools/percy-snapshot-utils/constants.d.ts +0 -6
- package/dist/tools/percy-snapshot-utils/constants.js +0 -15
- package/dist/tools/rca-agent-utils/constants.d.ts +1 -1
- package/dist/tools/rca-agent-utils/constants.js +2 -2
- package/dist/tools/rca-agent-utils/rca-data.d.ts +1 -1
- package/dist/tools/rca-agent-utils/rca-data.js +2 -2
- package/dist/tools/rca-agent-utils/types.d.ts +3 -3
- package/dist/tools/rca-agent.d.ts +1 -1
- package/dist/tools/run-percy-scan.js +51 -10
- package/dist/tools/sdk-utils/bstack/configUtils.d.ts +8 -4
- package/dist/tools/sdk-utils/bstack/configUtils.js +74 -20
- package/dist/tools/sdk-utils/bstack/constants.d.ts +1 -1
- package/dist/tools/sdk-utils/bstack/constants.js +7 -9
- package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +1 -1
- package/dist/tools/sdk-utils/bstack/sdkHandler.js +19 -9
- package/dist/tools/sdk-utils/common/constants.d.ts +6 -5
- package/dist/tools/sdk-utils/common/constants.js +8 -7
- package/dist/tools/sdk-utils/common/device-validator.d.ts +25 -0
- package/dist/tools/sdk-utils/common/device-validator.js +375 -0
- package/dist/tools/sdk-utils/common/schema.d.ts +32 -8
- package/dist/tools/sdk-utils/common/schema.js +62 -3
- package/dist/tools/sdk-utils/common/utils.d.ts +1 -1
- package/dist/tools/sdk-utils/common/utils.js +14 -2
- package/dist/tools/sdk-utils/handler.d.ts +1 -0
- package/dist/tools/sdk-utils/handler.js +59 -14
- package/dist/tools/sdk-utils/percy-automate/constants.d.ts +4 -4
- package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -4
- package/dist/tools/sdk-utils/percy-bstack/handler.js +5 -1
- package/dist/tools/sdk-utils/percy-web/constants.d.ts +22 -20
- package/dist/tools/sdk-utils/percy-web/constants.js +39 -0
- package/dist/tools/sdk-utils/percy-web/handler.js +3 -1
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getBrowserStackAuth } from "../../../lib/get-auth.js";
|
|
3
|
+
import { validateAppAutomateDevices } from "../../sdk-utils/common/device-validator.js";
|
|
3
4
|
import { getAppUploadInstruction, validateSupportforAppAutomate, } from "./utils.js";
|
|
4
5
|
import { getAppSDKPrefixCommand, generateAppBrowserStackYMLInstructions, } from "./index.js";
|
|
5
6
|
import { formatAppInstructionsWithNumbers, getAppInstructionsForProjectConfiguration, SETUP_APP_AUTOMATE_SCHEMA, } from "./index.js";
|
|
@@ -11,18 +12,31 @@ export async function setupAppAutomateHandler(rawInput, config) {
|
|
|
11
12
|
// Use variables for all major input properties
|
|
12
13
|
const testingFramework = input.detectedTestingFramework;
|
|
13
14
|
const language = input.detectedLanguage;
|
|
14
|
-
const
|
|
15
|
+
const inputDevices = input.devices ?? [];
|
|
15
16
|
const appPath = input.appPath;
|
|
16
17
|
const framework = input.detectedFramework;
|
|
17
18
|
//Validating if supported framework or not
|
|
18
19
|
validateSupportforAppAutomate(framework, language, testingFramework);
|
|
20
|
+
// Use default mobile devices when array is empty
|
|
21
|
+
const devices = inputDevices.length === 0
|
|
22
|
+
? [["android", "Samsung Galaxy S24", "latest"]]
|
|
23
|
+
: inputDevices;
|
|
24
|
+
// Validate devices against real BrowserStack device data
|
|
25
|
+
const validatedEnvironments = await validateAppAutomateDevices(devices);
|
|
26
|
+
// Extract platforms for backward compatibility (if needed)
|
|
27
|
+
const platforms = validatedEnvironments.map((env) => env.platform);
|
|
19
28
|
// Step 1: Generate SDK setup command
|
|
20
29
|
const sdkCommand = getAppSDKPrefixCommand(language, testingFramework, username, accessKey, appPath);
|
|
21
30
|
if (sdkCommand) {
|
|
22
31
|
instructions.push({ content: sdkCommand, type: "setup" });
|
|
23
32
|
}
|
|
24
33
|
// Step 2: Generate browserstack.yml configuration
|
|
25
|
-
const configInstructions = generateAppBrowserStackYMLInstructions(
|
|
34
|
+
const configInstructions = generateAppBrowserStackYMLInstructions({
|
|
35
|
+
validatedEnvironments,
|
|
36
|
+
platforms,
|
|
37
|
+
testingFramework,
|
|
38
|
+
projectName: input.project,
|
|
39
|
+
}, username, accessKey, appPath);
|
|
26
40
|
if (configInstructions) {
|
|
27
41
|
instructions.push({ content: configInstructions, type: "config" });
|
|
28
42
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export declare const MAVEN_ARCHETYPE_GROUP_ID = "com.browserstack";
|
|
2
2
|
export declare const MAVEN_ARCHETYPE_ARTIFACT_ID = "junit-archetype-integrate";
|
|
3
3
|
export declare const MAVEN_ARCHETYPE_VERSION = "1.0";
|
|
4
|
+
export declare const JAVA_APP_FRAMEWORK_VERSION_MAP: Record<string, string>;
|
|
4
5
|
export declare const JAVA_APP_FRAMEWORK_MAP: Record<string, string>;
|
|
5
6
|
export declare const GRADLE_APP_SETUP_INSTRUCTIONS = "\n**For Gradle setup:**\n1. Add browserstack-java-sdk to dependencies:\n compileOnly 'com.browserstack:browserstack-java-sdk:latest.release'\n\n2. Add browserstackSDK path variable:\n def browserstackSDKArtifact = configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.find { it.name == 'browserstack-java-sdk' }\n\n3. Add javaagent to gradle tasks:\n jvmArgs \"-javaagent:${browserstackSDKArtifact.file}\"\n";
|
|
6
7
|
export declare function getJavaAppInstructions(): string;
|
|
7
8
|
export declare function getJavaAppFrameworkForMaven(framework: string): string;
|
|
9
|
+
export declare function getJavaAppFrameworkVersion(framework: string): string;
|
|
8
10
|
export declare function getJavaSDKCommand(framework: string, username: string, accessKey: string, appPath?: string): string;
|
|
@@ -4,15 +4,31 @@ import { createStep, combineInstructions, createEnvStep, PLATFORM_UTILS, } from
|
|
|
4
4
|
export const MAVEN_ARCHETYPE_GROUP_ID = "com.browserstack";
|
|
5
5
|
export const MAVEN_ARCHETYPE_ARTIFACT_ID = "junit-archetype-integrate";
|
|
6
6
|
export const MAVEN_ARCHETYPE_VERSION = "1.0";
|
|
7
|
+
// Version mapping for different frameworks
|
|
8
|
+
export const JAVA_APP_FRAMEWORK_VERSION_MAP = {
|
|
9
|
+
testng: "1.4",
|
|
10
|
+
selenide: "1.4",
|
|
11
|
+
junit5: "1.0",
|
|
12
|
+
junit4: "1.0",
|
|
13
|
+
jbehave: "1.0",
|
|
14
|
+
cucumberTestng: "1.0",
|
|
15
|
+
cucumberJunit4: "1.0",
|
|
16
|
+
cucumberJunit5: "1.0",
|
|
17
|
+
cucumber: "1.0",
|
|
18
|
+
serenity: "1.0",
|
|
19
|
+
};
|
|
7
20
|
// Framework mapping for Java Maven archetype generation for App Automate
|
|
8
21
|
export const JAVA_APP_FRAMEWORK_MAP = {
|
|
9
|
-
testng: "
|
|
22
|
+
testng: "testng-archetype-integrate",
|
|
10
23
|
junit5: "browserstack-sdk-archetype-integrate",
|
|
11
24
|
selenide: "selenide-archetype-integrate",
|
|
12
25
|
jbehave: "browserstack-sdk-archetype-integrate",
|
|
26
|
+
junit4: "browserstack-sdk-archetype-integrate",
|
|
13
27
|
cucumberTestng: "browserstack-sdk-archetype-integrate",
|
|
14
28
|
cucumberJunit4: "browserstack-sdk-archetype-integrate",
|
|
15
29
|
cucumberJunit5: "browserstack-sdk-archetype-integrate",
|
|
30
|
+
cucumber: "browserstack-sdk-archetype-integrate",
|
|
31
|
+
serenity: "browserstack-sdk-archetype-integrate",
|
|
16
32
|
};
|
|
17
33
|
// Common Gradle setup instructions for App Automate (platform-independent)
|
|
18
34
|
export const GRADLE_APP_SETUP_INSTRUCTIONS = `
|
|
@@ -35,53 +51,71 @@ mvn test
|
|
|
35
51
|
export function getJavaAppFrameworkForMaven(framework) {
|
|
36
52
|
return JAVA_APP_FRAMEWORK_MAP[framework] || framework;
|
|
37
53
|
}
|
|
38
|
-
function
|
|
39
|
-
return
|
|
54
|
+
export function getJavaAppFrameworkVersion(framework) {
|
|
55
|
+
return JAVA_APP_FRAMEWORK_VERSION_MAP[framework] || MAVEN_ARCHETYPE_VERSION;
|
|
56
|
+
}
|
|
57
|
+
function getMavenCommandForWindows(framework, mavenFramework, version, username, accessKey, appPath) {
|
|
58
|
+
let command = `mvn archetype:generate -B ` +
|
|
40
59
|
`-DarchetypeGroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
|
|
41
60
|
`-DarchetypeArtifactId="${mavenFramework}" ` +
|
|
42
|
-
`-DarchetypeVersion="${
|
|
61
|
+
`-DarchetypeVersion="${version}" ` +
|
|
43
62
|
`-DgroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
|
|
44
|
-
`-DartifactId="${
|
|
45
|
-
`-Dversion="${
|
|
63
|
+
`-DartifactId="${mavenFramework}" ` +
|
|
64
|
+
`-Dversion="${version}" ` +
|
|
46
65
|
`-DBROWSERSTACK_USERNAME="${username}" ` +
|
|
47
|
-
`-DBROWSERSTACK_ACCESS_KEY="${accessKey}"
|
|
48
|
-
|
|
66
|
+
`-DBROWSERSTACK_ACCESS_KEY="${accessKey}"`;
|
|
67
|
+
// Add framework parameter for browserstack-sdk-archetype-integrate
|
|
68
|
+
if (mavenFramework === "browserstack-sdk-archetype-integrate") {
|
|
69
|
+
command += ` -DBROWSERSTACK_FRAMEWORK="${framework}"`;
|
|
70
|
+
}
|
|
71
|
+
// Add app path if provided
|
|
72
|
+
if (appPath) {
|
|
73
|
+
command += ` -DBROWSERSTACK_APP="${appPath}"`;
|
|
74
|
+
}
|
|
75
|
+
return command;
|
|
49
76
|
}
|
|
50
|
-
function getMavenCommandForUnix(framework, mavenFramework, username, accessKey) {
|
|
51
|
-
|
|
77
|
+
function getMavenCommandForUnix(framework, mavenFramework, version, username, accessKey, appPath) {
|
|
78
|
+
let command = `mvn archetype:generate -B ` +
|
|
52
79
|
`-DarchetypeGroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
|
|
53
80
|
`-DarchetypeArtifactId="${mavenFramework}" ` +
|
|
54
|
-
`-DarchetypeVersion="${
|
|
81
|
+
`-DarchetypeVersion="${version}" ` +
|
|
55
82
|
`-DgroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
|
|
56
|
-
`-DartifactId="${
|
|
57
|
-
`-Dversion="${
|
|
83
|
+
`-DartifactId="${mavenFramework}" ` +
|
|
84
|
+
`-Dversion="${version}" ` +
|
|
58
85
|
`-DBROWSERSTACK_USERNAME="${username}" ` +
|
|
59
|
-
`-DBROWSERSTACK_ACCESS_KEY="${accessKey}"
|
|
60
|
-
|
|
86
|
+
`-DBROWSERSTACK_ACCESS_KEY="${accessKey}"`;
|
|
87
|
+
// Add framework parameter for browserstack-sdk-archetype-integrate
|
|
88
|
+
if (mavenFramework === "browserstack-sdk-archetype-integrate") {
|
|
89
|
+
command += ` -DBROWSERSTACK_FRAMEWORK="${framework}"`;
|
|
90
|
+
}
|
|
91
|
+
// Add app path if provided
|
|
92
|
+
if (appPath) {
|
|
93
|
+
command += ` -DBROWSERSTACK_APP="${appPath}"`;
|
|
94
|
+
}
|
|
95
|
+
return command;
|
|
61
96
|
}
|
|
62
97
|
export function getJavaSDKCommand(framework, username, accessKey, appPath) {
|
|
63
98
|
const { isWindows = false, getPlatformLabel } = PLATFORM_UTILS || {};
|
|
64
99
|
const mavenFramework = getJavaAppFrameworkForMaven(framework);
|
|
100
|
+
const version = getJavaAppFrameworkVersion(framework);
|
|
65
101
|
let mavenCommand;
|
|
66
102
|
if (isWindows) {
|
|
67
|
-
mavenCommand = getMavenCommandForWindows(framework, mavenFramework, username, accessKey);
|
|
68
|
-
if (appPath) {
|
|
69
|
-
mavenCommand += ` -DBROWSERSTACK_APP="${appPath}"`;
|
|
70
|
-
}
|
|
103
|
+
mavenCommand = getMavenCommandForWindows(framework, mavenFramework, version, username, accessKey, appPath);
|
|
71
104
|
}
|
|
72
105
|
else {
|
|
73
|
-
mavenCommand = getMavenCommandForUnix(framework, mavenFramework, username, accessKey);
|
|
74
|
-
if (appPath) {
|
|
75
|
-
mavenCommand += ` -DBROWSERSTACK_APP="${appPath}"`;
|
|
76
|
-
}
|
|
106
|
+
mavenCommand = getMavenCommandForUnix(framework, mavenFramework, version, username, accessKey, appPath);
|
|
77
107
|
}
|
|
78
108
|
const envStep = createEnvStep(username, accessKey, isWindows, getPlatformLabel());
|
|
79
109
|
const mavenStep = createStep("Install BrowserStack SDK using Maven Archetype for App Automate", `Maven command for ${framework} (${getPlatformLabel()}):
|
|
80
|
-
\`\`\`bash
|
|
81
|
-
${mavenCommand}
|
|
82
|
-
\`\`\`
|
|
110
|
+
\`\`\`bash
|
|
111
|
+
${mavenCommand}
|
|
112
|
+
\`\`\`
|
|
83
113
|
|
|
84
|
-
Alternative setup for Gradle users:
|
|
85
|
-
${GRADLE_APP_SETUP_INSTRUCTIONS}`);
|
|
86
|
-
|
|
114
|
+
Alternative setup for Gradle users:
|
|
115
|
+
${GRADLE_APP_SETUP_INSTRUCTIONS}`);
|
|
116
|
+
const argsLineStep = createStep("Verifying dependency and argsLine", `Verify browserstack-java-sdk with LATEST is added as dependency and add this line in pom.xml if not added:
|
|
117
|
+
\`\`\`xml
|
|
118
|
+
<argLine>-javaagent:"\${com.browserstack:browserstack-java-sdk:jar}"</argLine>
|
|
119
|
+
\`\`\``);
|
|
120
|
+
return combineInstructions(envStep, mavenStep, argsLineStep);
|
|
87
121
|
}
|
|
@@ -16,6 +16,7 @@ export declare enum AppSDKSupportedTestingFrameworkEnum {
|
|
|
16
16
|
junit4 = "junit4",
|
|
17
17
|
selenide = "selenide",
|
|
18
18
|
jbehave = "jbehave",
|
|
19
|
+
serenity = "serenity",
|
|
19
20
|
cucumberTestng = "cucumberTestng",
|
|
20
21
|
cucumberJunit4 = "cucumberJunit4",
|
|
21
22
|
cucumberJunit5 = "cucumberJunit5",
|
|
@@ -49,7 +50,7 @@ export interface AppSDKInstruction {
|
|
|
49
50
|
export declare const SUPPORTED_CONFIGURATIONS: {
|
|
50
51
|
appium: {
|
|
51
52
|
ruby: string[];
|
|
52
|
-
java:
|
|
53
|
+
java: string[];
|
|
53
54
|
csharp: never[];
|
|
54
55
|
python: string[];
|
|
55
56
|
nodejs: string[];
|
|
@@ -18,6 +18,7 @@ export var AppSDKSupportedTestingFrameworkEnum;
|
|
|
18
18
|
AppSDKSupportedTestingFrameworkEnum["junit4"] = "junit4";
|
|
19
19
|
AppSDKSupportedTestingFrameworkEnum["selenide"] = "selenide";
|
|
20
20
|
AppSDKSupportedTestingFrameworkEnum["jbehave"] = "jbehave";
|
|
21
|
+
AppSDKSupportedTestingFrameworkEnum["serenity"] = "serenity";
|
|
21
22
|
AppSDKSupportedTestingFrameworkEnum["cucumberTestng"] = "cucumberTestng";
|
|
22
23
|
AppSDKSupportedTestingFrameworkEnum["cucumberJunit4"] = "cucumberJunit4";
|
|
23
24
|
AppSDKSupportedTestingFrameworkEnum["cucumberJunit5"] = "cucumberJunit5";
|
|
@@ -46,7 +47,15 @@ export var AppSDKSupportedPlatformEnum;
|
|
|
46
47
|
export const SUPPORTED_CONFIGURATIONS = {
|
|
47
48
|
appium: {
|
|
48
49
|
ruby: ["cucumberRuby"],
|
|
49
|
-
java: [
|
|
50
|
+
java: [
|
|
51
|
+
"testng",
|
|
52
|
+
"cucumber",
|
|
53
|
+
"junit4",
|
|
54
|
+
"junit5",
|
|
55
|
+
"jbehave",
|
|
56
|
+
"selenide",
|
|
57
|
+
"serenity",
|
|
58
|
+
],
|
|
50
59
|
csharp: [],
|
|
51
60
|
python: ["pytest", "robot", "behave", "lettuce"],
|
|
52
61
|
nodejs: ["jest", "mocha", "cucumberJs", "webdriverio", "nightwatch"],
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AppTestPlatform } from "./types.js";
|
|
3
|
+
import { AppSDKSupportedPlatformEnum } from "../appium-sdk/types.js";
|
|
3
4
|
export declare const RUN_APP_AUTOMATE_DESCRIPTION = "Execute pre-built native mobile test suites (Espresso for Android, XCUITest for iOS) by direct upload to BrowserStack. ONLY for compiled .apk/.ipa test files. This is NOT for SDK integration or Appium tests. For Appium-based testing with SDK setup, use 'setupBrowserStackAppAutomateTests' instead.";
|
|
4
5
|
export declare const RUN_APP_AUTOMATE_SCHEMA: {
|
|
5
6
|
appPath: z.ZodString;
|
|
6
7
|
testSuitePath: z.ZodString;
|
|
7
|
-
devices: z.ZodArray<z.ZodString, "many"
|
|
8
|
+
devices: z.ZodDefault<z.ZodArray<z.ZodUnion<[z.ZodTuple<[z.ZodLiteral<AppSDKSupportedPlatformEnum.android>, z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodLiteral<AppSDKSupportedPlatformEnum.ios>, z.ZodString, z.ZodString], null>]>, "many">>;
|
|
8
9
|
project: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
9
10
|
detectedAutomationFramework: z.ZodNativeEnum<typeof AppTestPlatform>;
|
|
10
11
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AppTestPlatform } from "./types.js";
|
|
3
|
+
import { AppSDKSupportedPlatformEnum } from "../appium-sdk/types.js";
|
|
3
4
|
export const RUN_APP_AUTOMATE_DESCRIPTION = `Execute pre-built native mobile test suites (Espresso for Android, XCUITest for iOS) by direct upload to BrowserStack. ONLY for compiled .apk/.ipa test files. This is NOT for SDK integration or Appium tests. For Appium-based testing with SDK setup, use 'setupBrowserStackAppAutomateTests' instead.`;
|
|
4
5
|
export const RUN_APP_AUTOMATE_SCHEMA = {
|
|
5
6
|
appPath: z
|
|
@@ -23,8 +24,29 @@ export const RUN_APP_AUTOMATE_SCHEMA = {
|
|
|
23
24
|
" zip -r Tests.zip *.xctestrun *-Runner.app\n\n" +
|
|
24
25
|
"If in other directory, provide existing test file path"),
|
|
25
26
|
devices: z
|
|
26
|
-
.array(z.
|
|
27
|
-
|
|
27
|
+
.array(z.union([
|
|
28
|
+
// Android: [android, deviceName, osVersion]
|
|
29
|
+
z.tuple([
|
|
30
|
+
z
|
|
31
|
+
.literal(AppSDKSupportedPlatformEnum.android)
|
|
32
|
+
.describe("Platform identifier: 'android'"),
|
|
33
|
+
z
|
|
34
|
+
.string()
|
|
35
|
+
.describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'"),
|
|
36
|
+
z.string().describe("Android version, e.g. '14', '16', 'latest'"),
|
|
37
|
+
]),
|
|
38
|
+
// iOS: [ios, deviceName, osVersion]
|
|
39
|
+
z.tuple([
|
|
40
|
+
z
|
|
41
|
+
.literal(AppSDKSupportedPlatformEnum.ios)
|
|
42
|
+
.describe("Platform identifier: 'ios'"),
|
|
43
|
+
z.string().describe("Device name, e.g. 'iPhone 15', 'iPhone 14 Pro'"),
|
|
44
|
+
z.string().describe("iOS version, e.g. '17', '16', 'latest'"),
|
|
45
|
+
]),
|
|
46
|
+
]))
|
|
47
|
+
.max(3)
|
|
48
|
+
.default([])
|
|
49
|
+
.describe("Tuples describing target mobile devices. Add device only when user asks explicitly for it. Defaults to [] . Example: [['android', 'Samsung Galaxy S24', '14'], ['ios', 'iPhone 15', '17']]"),
|
|
28
50
|
project: z
|
|
29
51
|
.string()
|
|
30
52
|
.optional()
|
|
@@ -6,6 +6,7 @@ import { maybeCompressBase64 } from "../lib/utils.js";
|
|
|
6
6
|
import { remote } from "webdriverio";
|
|
7
7
|
import { AppTestPlatform } from "./appautomate-utils/native-execution/types.js";
|
|
8
8
|
import { setupAppAutomateHandler } from "./appautomate-utils/appium-sdk/handler.js";
|
|
9
|
+
import { validateAppAutomateDevices } from "./sdk-utils/common/device-validator.js";
|
|
9
10
|
import { SETUP_APP_AUTOMATE_DESCRIPTION, SETUP_APP_AUTOMATE_SCHEMA, } from "./appautomate-utils/appium-sdk/constants.js";
|
|
10
11
|
import { Platform, } from "./appautomate-utils/native-execution/types.js";
|
|
11
12
|
import { getDevicesAndBrowsers, BrowserStackProducts, } from "../lib/device-cache.js";
|
|
@@ -105,6 +106,8 @@ async function runAppTestsOnBrowserStack(args, config) {
|
|
|
105
106
|
if (!args.browserstackTestSuiteUrl && !args.testSuitePath) {
|
|
106
107
|
throw new Error("testSuitePath is required when browserstackTestSuiteUrl is not provided");
|
|
107
108
|
}
|
|
109
|
+
// Validate devices against real BrowserStack device data
|
|
110
|
+
await validateAppAutomateDevices(args.devices);
|
|
108
111
|
switch (args.detectedAutomationFramework) {
|
|
109
112
|
case AppTestPlatform.ESPRESSO: {
|
|
110
113
|
try {
|
|
@@ -126,7 +129,12 @@ async function runAppTestsOnBrowserStack(args, config) {
|
|
|
126
129
|
test_suite_url = await uploadEspressoTestSuite(args.testSuitePath, config);
|
|
127
130
|
logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
|
|
128
131
|
}
|
|
129
|
-
|
|
132
|
+
// Convert array format to string format for Espresso
|
|
133
|
+
const deviceStrings = args.devices.map((device) => {
|
|
134
|
+
const [, deviceName, osVersion] = device;
|
|
135
|
+
return `${deviceName}-${osVersion}`;
|
|
136
|
+
});
|
|
137
|
+
const build_id = await triggerEspressoBuild(app_url, test_suite_url, deviceStrings, args.project);
|
|
130
138
|
return {
|
|
131
139
|
content: [
|
|
132
140
|
{
|
|
@@ -161,7 +169,12 @@ async function runAppTestsOnBrowserStack(args, config) {
|
|
|
161
169
|
test_suite_url = await uploadXcuiTestSuite(args.testSuitePath, config);
|
|
162
170
|
logger.info(`Test suite uploaded. URL: ${test_suite_url}`);
|
|
163
171
|
}
|
|
164
|
-
|
|
172
|
+
// Convert array format to string format for XCUITest
|
|
173
|
+
const deviceStrings = args.devices.map((device) => {
|
|
174
|
+
const [, deviceName, osVersion] = device;
|
|
175
|
+
return `${deviceName}-${osVersion}`;
|
|
176
|
+
});
|
|
177
|
+
const build_id = await triggerXcuiBuild(app_url, test_suite_url, deviceStrings, args.project, config);
|
|
165
178
|
return {
|
|
166
179
|
content: [
|
|
167
180
|
{
|
|
@@ -39,7 +39,10 @@ async function extractScreenshotUrls(sessionId, sessionType, config) {
|
|
|
39
39
|
//Converts screenshot URLs to base64 encoded images
|
|
40
40
|
async function convertUrlsToBase64(urls) {
|
|
41
41
|
const screenshots = await Promise.all(urls.map(async (url) => {
|
|
42
|
-
const response = await apiClient.get({
|
|
42
|
+
const response = await apiClient.get({
|
|
43
|
+
url,
|
|
44
|
+
responseType: "arraybuffer",
|
|
45
|
+
});
|
|
43
46
|
// Axios returns response.data as a Buffer for binary data
|
|
44
47
|
const base64 = Buffer.from(response.data).toString("base64");
|
|
45
48
|
// Compress the base64 image if needed
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
-
export declare function addListTestFiles(
|
|
2
|
+
export declare function addListTestFiles(): Promise<CallToolResult>;
|
|
@@ -1,35 +1,59 @@
|
|
|
1
1
|
import { listTestFiles } from "./percy-snapshot-utils/detect-test-files.js";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (!
|
|
8
|
-
throw new Error("No
|
|
2
|
+
import { 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";
|
|
5
|
+
export async function addListTestFiles() {
|
|
6
|
+
const storedResults = storedPercyResults.get();
|
|
7
|
+
if (!storedResults) {
|
|
8
|
+
throw new Error("No Framework details found. Please call expandPercyVisualTesting first to fetch the framework details.");
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
const language = storedResults.detectedLanguage;
|
|
11
|
+
const framework = storedResults.detectedTestingFramework;
|
|
12
|
+
// Use stored paths from setUpPercy
|
|
13
|
+
const dirs = storedResults.folderPaths;
|
|
14
|
+
const files = storedResults.filePaths;
|
|
15
|
+
let testFiles = [];
|
|
16
|
+
if (files && files.length > 0) {
|
|
16
17
|
testFiles = testFiles.concat(files);
|
|
17
18
|
}
|
|
19
|
+
if (dirs && dirs.length > 0) {
|
|
20
|
+
for (const dir of dirs) {
|
|
21
|
+
const discoveredFiles = await listTestFiles({
|
|
22
|
+
language,
|
|
23
|
+
framework,
|
|
24
|
+
baseDir: dir,
|
|
25
|
+
});
|
|
26
|
+
testFiles = testFiles.concat(discoveredFiles);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Validate that we have at least one test file
|
|
18
30
|
if (testFiles.length === 0) {
|
|
19
|
-
throw new Error("No test files found");
|
|
31
|
+
throw new Error("No test files found. Please provide either specific file paths (files) or directory paths (dirs) containing test files.");
|
|
32
|
+
}
|
|
33
|
+
if (testFiles.length === 1) {
|
|
34
|
+
const result = await updateFileAndStep(testFiles[0], 0, 1, percyWebSetupInstructions);
|
|
35
|
+
return {
|
|
36
|
+
content: result,
|
|
37
|
+
};
|
|
20
38
|
}
|
|
21
|
-
//
|
|
22
|
-
const
|
|
23
|
-
|
|
39
|
+
// For multiple files, store directly in testFiles
|
|
40
|
+
const fileStatusMap = {};
|
|
41
|
+
testFiles.forEach((file) => {
|
|
42
|
+
fileStatusMap[file] = false; // false = not updated, true = updated
|
|
43
|
+
});
|
|
44
|
+
// Update storedPercyResults with test files
|
|
45
|
+
const updatedStored = { ...storedResults };
|
|
46
|
+
updatedStored.testFiles = fileStatusMap;
|
|
47
|
+
storedPercyResults.set(updatedStored);
|
|
24
48
|
return {
|
|
25
49
|
content: [
|
|
26
50
|
{
|
|
27
51
|
type: "text",
|
|
28
|
-
text: `The Test files are stored in memory
|
|
52
|
+
text: `The Test files are stored in memory and the total number of tests files found is ${testFiles.length}.`,
|
|
29
53
|
},
|
|
30
54
|
{
|
|
31
55
|
type: "text",
|
|
32
|
-
text: `You can now use the tool addPercySnapshotCommands to update the test file with Percy commands for visual testing
|
|
56
|
+
text: `You can now use the tool addPercySnapshotCommands to update the test file with Percy commands for visual testing.`,
|
|
33
57
|
},
|
|
34
58
|
],
|
|
35
59
|
};
|
package/dist/tools/percy-sdk.js
CHANGED
|
@@ -5,14 +5,41 @@ import { runPercyScan } from "./run-percy-scan.js";
|
|
|
5
5
|
import { SetUpPercyParamsShape } from "./sdk-utils/common/schema.js";
|
|
6
6
|
import { updateTestsWithPercyCommands } from "./add-percy-snapshots.js";
|
|
7
7
|
import { approveOrDeclinePercyBuild } from "./review-agent-utils/percy-approve-reject.js";
|
|
8
|
-
import { setUpPercyHandler } from "./sdk-utils/handler.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { setUpPercyHandler, simulatePercyChangeHandler, } from "./sdk-utils/handler.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { SETUP_PERCY_DESCRIPTION, LIST_TEST_FILES_DESCRIPTION, PERCY_SNAPSHOT_COMMANDS_DESCRIPTION, SIMULATE_PERCY_CHANGE_DESCRIPTION, } from "./sdk-utils/common/constants.js";
|
|
11
|
+
import { UpdateTestFileWithInstructionsParams } from "./percy-snapshot-utils/constants.js";
|
|
11
12
|
import { RunPercyScanParamsShape, FetchPercyChangesParamsShape, ManagePercyBuildApprovalParamsShape, } from "./sdk-utils/common/schema.js";
|
|
12
13
|
import { handleMCPError } from "../lib/utils.js";
|
|
13
14
|
export function registerPercyTools(server, config) {
|
|
14
15
|
const tools = {};
|
|
15
|
-
|
|
16
|
+
server.prompt("integrate-percy", {
|
|
17
|
+
project_name: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe("The name of the project to integrate with Percy"),
|
|
20
|
+
}, async ({ project_name }) => {
|
|
21
|
+
return {
|
|
22
|
+
messages: [
|
|
23
|
+
{
|
|
24
|
+
role: "assistant",
|
|
25
|
+
content: {
|
|
26
|
+
type: "text",
|
|
27
|
+
text: `Integrate percy in this project ${project_name} using tool percyVisualTestIntegrationAgent.`,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
tools.percyVisualTestIntegrationAgent = server.tool("percyVisualTestIntegrationAgent", SIMULATE_PERCY_CHANGE_DESCRIPTION, SetUpPercyParamsShape, async (args) => {
|
|
34
|
+
try {
|
|
35
|
+
trackMCP("VisualTestIntegrationAgent", server.server.getClientVersion(), config);
|
|
36
|
+
return simulatePercyChangeHandler(args, config);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return handleMCPError("VisualTestIntegrationAgent", server, config, error);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
tools.setupPercyVisualTesting = server.tool("expandPercyVisualTesting", SETUP_PERCY_DESCRIPTION, SetUpPercyParamsShape, async (args) => {
|
|
16
43
|
try {
|
|
17
44
|
trackMCP("setupPercyVisualTesting", server.server.getClientVersion(), config);
|
|
18
45
|
return setUpPercyHandler(args, config);
|
|
@@ -30,10 +57,10 @@ export function registerPercyTools(server, config) {
|
|
|
30
57
|
return handleMCPError("addPercySnapshotCommands", server, config, error);
|
|
31
58
|
}
|
|
32
59
|
});
|
|
33
|
-
tools.listTestFiles = server.tool("listTestFiles", LIST_TEST_FILES_DESCRIPTION,
|
|
60
|
+
tools.listTestFiles = server.tool("listTestFiles", LIST_TEST_FILES_DESCRIPTION, {}, async () => {
|
|
34
61
|
try {
|
|
35
62
|
trackMCP("listTestFiles", server.server.getClientVersion(), config);
|
|
36
|
-
return addListTestFiles(
|
|
63
|
+
return addListTestFiles();
|
|
37
64
|
}
|
|
38
65
|
catch (error) {
|
|
39
66
|
return handleMCPError("listTestFiles", server, config, error);
|
|
@@ -2,14 +2,8 @@ import { z } from "zod";
|
|
|
2
2
|
import { SDKSupportedLanguage } from "../sdk-utils/common/types.js";
|
|
3
3
|
import { DetectionConfig } from "./types.js";
|
|
4
4
|
export declare const UpdateTestFileWithInstructionsParams: {
|
|
5
|
-
uuid: z.ZodString;
|
|
6
5
|
index: z.ZodNumber;
|
|
7
6
|
};
|
|
8
|
-
export declare const ListTestFilesParamsShape: {
|
|
9
|
-
dirs: z.ZodArray<z.ZodString, "many">;
|
|
10
|
-
language: z.ZodEnum<[string, ...string[]]>;
|
|
11
|
-
framework: z.ZodEnum<[string, ...string[]]>;
|
|
12
|
-
};
|
|
13
7
|
export declare const TEST_FILE_DETECTION: Record<SDKSupportedLanguage, DetectionConfig>;
|
|
14
8
|
export declare const EXCLUDED_DIRS: Set<string>;
|
|
15
9
|
export declare const backendIndicators: RegExp[];
|
|
@@ -1,22 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { SDKSupportedLanguages, SDKSupportedTestingFrameworks, } from "../sdk-utils/common/types.js";
|
|
3
2
|
export const UpdateTestFileWithInstructionsParams = {
|
|
4
|
-
uuid: z
|
|
5
|
-
.string()
|
|
6
|
-
.describe("UUID referencing the in-memory array of test file paths"),
|
|
7
3
|
index: z.number().describe("Index of the test file to update"),
|
|
8
4
|
};
|
|
9
|
-
export const ListTestFilesParamsShape = {
|
|
10
|
-
dirs: z
|
|
11
|
-
.array(z.string())
|
|
12
|
-
.describe("Array of directory paths to search for test files"),
|
|
13
|
-
language: z
|
|
14
|
-
.enum(SDKSupportedLanguages)
|
|
15
|
-
.describe("Programming language"),
|
|
16
|
-
framework: z
|
|
17
|
-
.enum(SDKSupportedTestingFrameworks)
|
|
18
|
-
.describe("Testing framework (optional)"),
|
|
19
|
-
};
|
|
20
5
|
export const TEST_FILE_DETECTION = {
|
|
21
6
|
java: {
|
|
22
7
|
extensions: [".java"],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { TestStatus } from "./types.js";
|
|
3
3
|
export declare const FETCH_RCA_PARAMS: {
|
|
4
|
-
testId: z.ZodArray<z.
|
|
4
|
+
testId: z.ZodArray<z.ZodNumber, "many">;
|
|
5
5
|
};
|
|
6
6
|
export declare const GET_BUILD_ID_PARAMS: {
|
|
7
7
|
browserStackProjectName: z.ZodString;
|
|
@@ -2,9 +2,9 @@ import { z } from "zod";
|
|
|
2
2
|
import { TestStatus } from "./types.js";
|
|
3
3
|
export const FETCH_RCA_PARAMS = {
|
|
4
4
|
testId: z
|
|
5
|
-
.array(z.
|
|
5
|
+
.array(z.number().int())
|
|
6
6
|
.max(3)
|
|
7
|
-
.describe("Array of test IDs to fetch RCA data for (maximum 3 IDs). If not provided, use the listTestIds tool get all failed testcases. If more than 3 IDs are provided, only the first 3 will be processed."),
|
|
7
|
+
.describe("Array of integer test IDs to fetch RCA data for (maximum 3 IDs). These must be numeric test IDs, not session IDs or strings. If not provided, use the listTestIds tool to get all failed testcases. If more than 3 IDs are provided, only the first 3 will be processed."),
|
|
8
8
|
};
|
|
9
9
|
export const GET_BUILD_ID_PARAMS = {
|
|
10
10
|
browserStackProjectName: z
|
|
@@ -5,5 +5,5 @@ interface ScanProgressContext {
|
|
|
5
5
|
progressToken?: string | number;
|
|
6
6
|
};
|
|
7
7
|
}
|
|
8
|
-
export declare function getRCAData(testIds:
|
|
8
|
+
export declare function getRCAData(testIds: number[], authString: string, context?: ScanProgressContext): Promise<RCAResponse>;
|
|
9
9
|
export {};
|
|
@@ -66,7 +66,7 @@ async function updateProgress(context, testCases, message) {
|
|
|
66
66
|
: calculateProgress(resolvedCount, testCases.length));
|
|
67
67
|
}
|
|
68
68
|
async function fetchInitialRCA(testId, headers, baseUrl) {
|
|
69
|
-
const url = baseUrl.replace("{testId}", testId);
|
|
69
|
+
const url = baseUrl.replace("{testId}", testId.toString());
|
|
70
70
|
try {
|
|
71
71
|
const response = await fetch(url, { headers });
|
|
72
72
|
if (!response.ok) {
|
|
@@ -125,7 +125,7 @@ async function pollRCAResults(testCases, headers, baseUrl, context, pollInterval
|
|
|
125
125
|
}
|
|
126
126
|
await Promise.allSettled(inProgressCases.map(async (tc) => {
|
|
127
127
|
try {
|
|
128
|
-
const pollUrl = baseUrl.replace("{testId}", tc.id);
|
|
128
|
+
const pollUrl = baseUrl.replace("{testId}", tc.id.toString());
|
|
129
129
|
const response = await fetch(pollUrl, { headers });
|
|
130
130
|
if (!response.ok) {
|
|
131
131
|
const errorText = await response.text();
|
|
@@ -18,7 +18,7 @@ export interface TestRun {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
export interface FailedTestInfo {
|
|
21
|
-
test_id:
|
|
21
|
+
test_id: number;
|
|
22
22
|
test_name: string;
|
|
23
23
|
}
|
|
24
24
|
export declare enum RCAState {
|
|
@@ -34,8 +34,8 @@ export declare enum RCAState {
|
|
|
34
34
|
TIMEOUT = "TIMEOUT"
|
|
35
35
|
}
|
|
36
36
|
export interface RCATestCase {
|
|
37
|
-
id:
|
|
38
|
-
testRunId:
|
|
37
|
+
id: number;
|
|
38
|
+
testRunId: number;
|
|
39
39
|
state: RCAState;
|
|
40
40
|
rcaData?: any;
|
|
41
41
|
}
|
|
@@ -5,7 +5,7 @@ import { TestStatus } from "./rca-agent-utils/types.js";
|
|
|
5
5
|
import { BuildIdArgs } from "./rca-agent-utils/types.js";
|
|
6
6
|
export declare function getBuildIdTool(args: BuildIdArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
7
7
|
export declare function fetchRCADataTool(args: {
|
|
8
|
-
testId:
|
|
8
|
+
testId: number[];
|
|
9
9
|
}, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
10
10
|
export declare function listTestIdsTool(args: {
|
|
11
11
|
buildId: string;
|