@browserstack/mcp-server 1.2.3 → 1.2.4

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.
Files changed (157) hide show
  1. package/README.md +88 -2
  2. package/dist/lib/device-cache.js +20 -17
  3. package/dist/lib/inmemory-store.d.ts +1 -0
  4. package/dist/lib/inmemory-store.js +1 -0
  5. package/dist/lib/utils.d.ts +5 -0
  6. package/dist/lib/utils.js +27 -0
  7. package/dist/server-factory.js +6 -0
  8. package/dist/tools/add-percy-snapshots.d.ts +5 -0
  9. package/dist/tools/add-percy-snapshots.js +17 -0
  10. package/dist/tools/appautomate-utils/appium-sdk/config-generator.d.ts +1 -0
  11. package/dist/tools/appautomate-utils/appium-sdk/config-generator.js +50 -0
  12. package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +23 -0
  13. package/dist/tools/appautomate-utils/appium-sdk/constants.js +43 -0
  14. package/dist/tools/appautomate-utils/appium-sdk/formatter.d.ts +8 -0
  15. package/dist/tools/appautomate-utils/appium-sdk/formatter.js +59 -0
  16. package/dist/tools/appautomate-utils/appium-sdk/handler.d.ts +3 -0
  17. package/dist/tools/appautomate-utils/appium-sdk/handler.js +52 -0
  18. package/dist/tools/appautomate-utils/appium-sdk/index.d.ts +7 -0
  19. package/dist/tools/appautomate-utils/appium-sdk/index.js +8 -0
  20. package/dist/tools/appautomate-utils/appium-sdk/instructions.d.ts +3 -0
  21. package/dist/tools/appautomate-utils/appium-sdk/instructions.js +47 -0
  22. package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.d.ts +2 -0
  23. package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.js +78 -0
  24. package/dist/tools/appautomate-utils/appium-sdk/languages/java.d.ts +8 -0
  25. package/dist/tools/appautomate-utils/appium-sdk/languages/java.js +87 -0
  26. package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.d.ts +3 -0
  27. package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.js +194 -0
  28. package/dist/tools/appautomate-utils/appium-sdk/languages/python.d.ts +3 -0
  29. package/dist/tools/appautomate-utils/appium-sdk/languages/python.js +76 -0
  30. package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.d.ts +2 -0
  31. package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.js +85 -0
  32. package/dist/tools/appautomate-utils/appium-sdk/types.d.ts +57 -0
  33. package/dist/tools/appautomate-utils/appium-sdk/types.js +54 -0
  34. package/dist/tools/appautomate-utils/appium-sdk/utils.d.ts +17 -0
  35. package/dist/tools/appautomate-utils/appium-sdk/utils.js +64 -0
  36. package/dist/tools/appautomate-utils/{appautomate.d.ts → native-execution/appautomate.d.ts} +1 -1
  37. package/dist/tools/appautomate-utils/{appautomate.js → native-execution/appautomate.js} +2 -2
  38. package/dist/tools/appautomate-utils/native-execution/constants.d.ts +10 -0
  39. package/dist/tools/appautomate-utils/native-execution/constants.js +36 -0
  40. package/dist/tools/appautomate-utils/native-execution/types.d.ts +19 -0
  41. package/dist/tools/appautomate-utils/{types.js → native-execution/types.js} +5 -1
  42. package/dist/tools/appautomate.js +25 -40
  43. package/dist/tools/bstack-sdk.d.ts +2 -15
  44. package/dist/tools/bstack-sdk.js +10 -119
  45. package/dist/tools/build-insights.d.ts +7 -0
  46. package/dist/tools/build-insights.js +67 -0
  47. package/dist/tools/list-test-files.d.ts +2 -0
  48. package/dist/tools/list-test-files.js +36 -0
  49. package/dist/tools/percy-sdk.d.ts +4 -0
  50. package/dist/tools/percy-sdk.js +71 -0
  51. package/dist/tools/percy-snapshot-utils/constants.d.ts +16 -0
  52. package/dist/tools/percy-snapshot-utils/constants.js +500 -0
  53. package/dist/tools/percy-snapshot-utils/detect-test-files.d.ts +10 -0
  54. package/dist/tools/percy-snapshot-utils/detect-test-files.js +175 -0
  55. package/dist/tools/percy-snapshot-utils/types.d.ts +15 -0
  56. package/dist/tools/percy-snapshot-utils/utils.d.ts +4 -0
  57. package/dist/tools/percy-snapshot-utils/utils.js +30 -0
  58. package/dist/tools/rca-agent-utils/constants.d.ts +13 -0
  59. package/dist/tools/rca-agent-utils/constants.js +24 -0
  60. package/dist/tools/rca-agent-utils/format-rca.d.ts +1 -0
  61. package/dist/tools/rca-agent-utils/format-rca.js +37 -0
  62. package/dist/tools/rca-agent-utils/get-build-id.d.ts +1 -0
  63. package/dist/tools/rca-agent-utils/get-build-id.js +18 -0
  64. package/dist/tools/rca-agent-utils/get-failed-test-id.d.ts +2 -0
  65. package/dist/tools/rca-agent-utils/get-failed-test-id.js +69 -0
  66. package/dist/tools/rca-agent-utils/rca-data.d.ts +9 -0
  67. package/dist/tools/rca-agent-utils/rca-data.js +196 -0
  68. package/dist/tools/rca-agent-utils/types.d.ts +48 -0
  69. package/dist/tools/rca-agent-utils/types.js +20 -0
  70. package/dist/tools/rca-agent.d.ts +14 -0
  71. package/dist/tools/rca-agent.js +119 -0
  72. package/dist/tools/review-agent-utils/build-counts.d.ts +7 -0
  73. package/dist/tools/review-agent-utils/build-counts.js +44 -0
  74. package/dist/tools/review-agent-utils/percy-approve-reject.d.ts +6 -0
  75. package/dist/tools/review-agent-utils/percy-approve-reject.js +39 -0
  76. package/dist/tools/review-agent-utils/percy-diffs.d.ts +9 -0
  77. package/dist/tools/review-agent-utils/percy-diffs.js +35 -0
  78. package/dist/tools/review-agent-utils/percy-snapshots.d.ts +11 -0
  79. package/dist/tools/review-agent-utils/percy-snapshots.js +58 -0
  80. package/dist/tools/review-agent.d.ts +5 -0
  81. package/dist/tools/review-agent.js +56 -0
  82. package/dist/tools/run-percy-scan.d.ts +8 -0
  83. package/dist/tools/run-percy-scan.js +37 -0
  84. package/dist/tools/sdk-utils/{commands.d.ts → bstack/commands.d.ts} +1 -1
  85. package/dist/tools/sdk-utils/bstack/commands.js +88 -0
  86. package/dist/tools/sdk-utils/bstack/configUtils.d.ts +4 -0
  87. package/dist/tools/sdk-utils/bstack/configUtils.js +66 -0
  88. package/dist/tools/sdk-utils/bstack/constants.d.ts +58 -0
  89. package/dist/tools/sdk-utils/{constants.js → bstack/constants.js} +117 -78
  90. package/dist/tools/sdk-utils/{constants.d.ts → bstack/frameworks.d.ts} +1 -1
  91. package/dist/tools/sdk-utils/bstack/frameworks.js +57 -0
  92. package/dist/tools/sdk-utils/bstack/index.d.ts +4 -0
  93. package/dist/tools/sdk-utils/bstack/index.js +5 -0
  94. package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +4 -0
  95. package/dist/tools/sdk-utils/bstack/sdkHandler.js +74 -0
  96. package/dist/tools/sdk-utils/common/constants.d.ts +10 -0
  97. package/dist/tools/sdk-utils/common/constants.js +86 -0
  98. package/dist/tools/sdk-utils/common/formatUtils.d.ts +5 -0
  99. package/dist/tools/sdk-utils/common/formatUtils.js +27 -0
  100. package/dist/tools/sdk-utils/common/index.d.ts +3 -0
  101. package/dist/tools/sdk-utils/common/index.js +4 -0
  102. package/dist/tools/sdk-utils/common/instructionUtils.d.ts +8 -0
  103. package/dist/tools/sdk-utils/common/instructionUtils.js +20 -0
  104. package/dist/tools/sdk-utils/common/schema.d.ts +73 -0
  105. package/dist/tools/sdk-utils/common/schema.js +51 -0
  106. package/dist/tools/sdk-utils/common/types.d.ts +66 -0
  107. package/dist/tools/sdk-utils/{types.js → common/types.js} +15 -2
  108. package/dist/tools/sdk-utils/common/utils.d.ts +25 -0
  109. package/dist/tools/sdk-utils/common/utils.js +90 -0
  110. package/dist/tools/sdk-utils/handler.d.ts +4 -0
  111. package/dist/tools/sdk-utils/handler.js +119 -0
  112. package/dist/tools/sdk-utils/percy-automate/constants.d.ts +11 -0
  113. package/dist/tools/sdk-utils/percy-automate/constants.js +338 -0
  114. package/dist/tools/sdk-utils/percy-automate/frameworks.d.ts +8 -0
  115. package/dist/tools/sdk-utils/percy-automate/frameworks.js +50 -0
  116. package/dist/tools/sdk-utils/percy-automate/handler.d.ts +3 -0
  117. package/dist/tools/sdk-utils/percy-automate/handler.js +30 -0
  118. package/dist/tools/sdk-utils/percy-automate/index.d.ts +1 -0
  119. package/dist/tools/sdk-utils/percy-automate/index.js +2 -0
  120. package/dist/tools/sdk-utils/percy-automate/types.d.ts +13 -0
  121. package/dist/tools/sdk-utils/percy-automate/types.js +1 -0
  122. package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -0
  123. package/dist/tools/sdk-utils/{percy → percy-bstack}/constants.js +13 -39
  124. package/dist/tools/sdk-utils/percy-bstack/frameworks.d.ts +2 -0
  125. package/dist/tools/sdk-utils/percy-bstack/frameworks.js +27 -0
  126. package/dist/tools/sdk-utils/percy-bstack/handler.d.ts +4 -0
  127. package/dist/tools/sdk-utils/percy-bstack/handler.js +99 -0
  128. package/dist/tools/sdk-utils/percy-bstack/index.d.ts +4 -0
  129. package/dist/tools/sdk-utils/percy-bstack/index.js +4 -0
  130. package/dist/tools/sdk-utils/percy-bstack/instructions.d.ts +7 -0
  131. package/dist/tools/sdk-utils/{percy → percy-bstack}/instructions.js +5 -9
  132. package/dist/tools/sdk-utils/percy-bstack/types.d.ts +13 -0
  133. package/dist/tools/sdk-utils/percy-bstack/types.js +5 -0
  134. package/dist/tools/sdk-utils/percy-web/constants.d.ts +41 -0
  135. package/dist/tools/sdk-utils/percy-web/constants.js +883 -0
  136. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.d.ts +4 -0
  137. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.js +32 -0
  138. package/dist/tools/sdk-utils/percy-web/frameworks.d.ts +7 -0
  139. package/dist/tools/sdk-utils/percy-web/frameworks.js +103 -0
  140. package/dist/tools/sdk-utils/percy-web/handler.d.ts +4 -0
  141. package/dist/tools/sdk-utils/percy-web/handler.js +27 -0
  142. package/dist/tools/sdk-utils/percy-web/index.d.ts +4 -0
  143. package/dist/tools/sdk-utils/percy-web/index.js +4 -0
  144. package/dist/tools/sdk-utils/percy-web/types.d.ts +12 -0
  145. package/dist/tools/sdk-utils/percy-web/types.js +1 -0
  146. package/dist/tools/testmanagement-utils/create-testrun.d.ts +4 -4
  147. package/dist/tools/testmanagement-utils/update-testrun.d.ts +4 -4
  148. package/package.json +2 -1
  149. package/dist/tools/appautomate-utils/types.d.ts +0 -5
  150. package/dist/tools/sdk-utils/commands.js +0 -65
  151. package/dist/tools/sdk-utils/instructions.d.ts +0 -6
  152. package/dist/tools/sdk-utils/instructions.js +0 -99
  153. package/dist/tools/sdk-utils/percy/constants.d.ts +0 -3
  154. package/dist/tools/sdk-utils/percy/instructions.d.ts +0 -10
  155. package/dist/tools/sdk-utils/percy/types.d.ts +0 -5
  156. package/dist/tools/sdk-utils/types.d.ts +0 -40
  157. /package/dist/tools/{sdk-utils/percy → percy-snapshot-utils}/types.js +0 -0
@@ -0,0 +1,44 @@
1
+ // Utility for fetching the count of Percy builds and orgId.
2
+ export async function getPercyBuildCount(percyToken) {
3
+ const apiUrl = `https://percy.io/api/v1/builds`;
4
+ const response = await fetch(apiUrl, {
5
+ headers: {
6
+ Authorization: `Token token=${percyToken}`,
7
+ "Content-Type": "application/json",
8
+ },
9
+ });
10
+ if (!response.ok) {
11
+ throw new Error(`Failed to fetch Percy builds: ${response.statusText}`);
12
+ }
13
+ const data = await response.json();
14
+ const builds = data.data ?? [];
15
+ const included = data.included ?? [];
16
+ let isFirstBuild = false;
17
+ let lastBuildId;
18
+ let orgId;
19
+ let browserIds = [];
20
+ if (builds.length === 0) {
21
+ return {
22
+ noBuilds: true,
23
+ isFirstBuild: false,
24
+ lastBuildId: undefined,
25
+ orgId,
26
+ browserIds: [],
27
+ };
28
+ }
29
+ else {
30
+ isFirstBuild = builds.length === 1;
31
+ lastBuildId = builds[0].id;
32
+ }
33
+ // Extract browserIds from the latest build if available
34
+ browserIds =
35
+ builds[0]?.relationships?.browsers?.data
36
+ ?.map((b) => b.id)
37
+ ?.filter((id) => typeof id === "string") ?? [];
38
+ // Extract orgId from the `included` projects block
39
+ const project = included.find((item) => item.type === "projects");
40
+ if (project?.relationships?.organization?.data?.id) {
41
+ orgId = project.relationships.organization.data.id;
42
+ }
43
+ return { noBuilds: false, isFirstBuild, lastBuildId, orgId, browserIds };
44
+ }
@@ -0,0 +1,6 @@
1
+ import { BrowserStackConfig } from "../../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function approveOrDeclinePercyBuild(args: {
4
+ buildId: string;
5
+ action: "approve" | "unapprove" | "reject";
6
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,39 @@
1
+ import { getBrowserStackAuth } from "../../lib/get-auth.js";
2
+ export async function approveOrDeclinePercyBuild(args, config) {
3
+ const { buildId, action } = args;
4
+ // Get Basic Auth credentials
5
+ const authString = getBrowserStackAuth(config);
6
+ const auth = Buffer.from(authString).toString("base64");
7
+ // Prepare request body
8
+ const body = {
9
+ data: {
10
+ type: "reviews",
11
+ attributes: { action },
12
+ relationships: {
13
+ build: { data: { type: "builds", id: buildId } },
14
+ },
15
+ },
16
+ };
17
+ // Send request to Percy API
18
+ const response = await fetch("https://percy.io/api/v1/reviews", {
19
+ method: "POST",
20
+ headers: {
21
+ "Content-Type": "application/json",
22
+ Authorization: `Basic ${auth}`,
23
+ },
24
+ body: JSON.stringify(body),
25
+ });
26
+ if (!response.ok) {
27
+ const errorText = await response.text();
28
+ throw new Error(`Percy build ${action} failed: ${response.status} ${errorText}`);
29
+ }
30
+ const result = await response.json();
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: `Percy build ${buildId} was ${result.data.attributes["review-state"]} by ${result.data.attributes["action-performed-by"].user_name}`,
36
+ },
37
+ ],
38
+ };
39
+ }
@@ -0,0 +1,9 @@
1
+ export interface PercySnapshotDiff {
2
+ id: string;
3
+ name: string | null;
4
+ title: string;
5
+ description: string | null;
6
+ coordinates: any;
7
+ }
8
+ export declare function getPercySnapshotDiff(snapshotId: string, percyToken: string): Promise<PercySnapshotDiff[]>;
9
+ export declare function getPercySnapshotDiffs(snapshotIds: string[], percyToken: string): Promise<PercySnapshotDiff[]>;
@@ -0,0 +1,35 @@
1
+ export async function getPercySnapshotDiff(snapshotId, percyToken) {
2
+ const apiUrl = `https://percy.io/api/v1/snapshots/${snapshotId}`;
3
+ const response = await fetch(apiUrl, {
4
+ headers: {
5
+ Authorization: `Token token=${percyToken}`,
6
+ "Content-Type": "application/json",
7
+ },
8
+ });
9
+ if (!response.ok) {
10
+ throw new Error(`Failed to fetch Percy snapshot ${snapshotId}: ${response.statusText}`);
11
+ }
12
+ const data = await response.json();
13
+ const pageUrl = data.data.attributes?.name || null;
14
+ const changes = [];
15
+ const comparisons = data.included?.filter((item) => item.type === "comparisons") ?? [];
16
+ for (const comparison of comparisons) {
17
+ const appliedRegions = comparison.attributes?.["applied-regions"] ?? [];
18
+ for (const region of appliedRegions) {
19
+ if (region.ignored)
20
+ continue;
21
+ changes.push({
22
+ id: String(region.id),
23
+ name: pageUrl,
24
+ title: region.change_title,
25
+ description: region.change_description ?? null,
26
+ coordinates: region.coordinates ?? null,
27
+ });
28
+ }
29
+ }
30
+ return changes;
31
+ }
32
+ export async function getPercySnapshotDiffs(snapshotIds, percyToken) {
33
+ const allDiffs = await Promise.all(snapshotIds.map((id) => getPercySnapshotDiff(id, percyToken)));
34
+ return allDiffs.flat();
35
+ }
@@ -0,0 +1,11 @@
1
+ import { BrowserStackConfig } from "../../lib/types.js";
2
+ export declare function getChangedPercySnapshotIds(buildId: string, config: BrowserStackConfig, orgId: string | undefined, browserIds: string[]): Promise<string[]>;
3
+ export declare function constructPercyBuildItemsUrl({ buildId, orgId, category, subcategories, browserIds, widths, groupSnapshotsBy, }: {
4
+ buildId: string;
5
+ orgId: string;
6
+ category?: string[];
7
+ subcategories?: string[];
8
+ browserIds?: string[];
9
+ widths?: string[];
10
+ groupSnapshotsBy?: string;
11
+ }): string;
@@ -0,0 +1,58 @@
1
+ import { getBrowserStackAuth } from "../../lib/get-auth.js";
2
+ import { sanitizeUrlParam } from "../../lib/utils.js";
3
+ // Utility for fetching only the IDs of changed Percy snapshots for a given build.
4
+ export async function getChangedPercySnapshotIds(buildId, config, orgId, browserIds) {
5
+ if (!buildId || !orgId) {
6
+ throw new Error("Failed to fetch AI Summary: Missing build ID or organization ID");
7
+ }
8
+ const urlStr = constructPercyBuildItemsUrl({
9
+ buildId,
10
+ orgId,
11
+ category: ["changed"],
12
+ subcategories: ["unreviewed", "approved", "changes_requested"],
13
+ groupSnapshotsBy: "similar_diff",
14
+ browserIds,
15
+ widths: ["375", "1280", "1920"],
16
+ });
17
+ const authString = getBrowserStackAuth(config);
18
+ const auth = Buffer.from(authString).toString("base64");
19
+ const response = await fetch(urlStr, {
20
+ headers: {
21
+ Authorization: `Basic ${auth}`,
22
+ "Content-Type": "application/json",
23
+ },
24
+ });
25
+ if (!response.ok) {
26
+ throw new Error(`Failed to fetch changed Percy snapshots: ${response.status} ${response.statusText}`);
27
+ }
28
+ const responseData = await response.json();
29
+ const buildItems = responseData.data ?? [];
30
+ if (buildItems.length === 0) {
31
+ return [];
32
+ }
33
+ const snapshotIds = buildItems
34
+ .flatMap((item) => item.attributes?.["snapshot-ids"] ?? [])
35
+ .map((id) => String(id));
36
+ return snapshotIds;
37
+ }
38
+ export function constructPercyBuildItemsUrl({ buildId, orgId, category = [], subcategories = [], browserIds = [], widths = [], groupSnapshotsBy, }) {
39
+ const url = new URL("https://percy.io/api/v1/build-items");
40
+ url.searchParams.set("filter[build-id]", sanitizeUrlParam(buildId));
41
+ url.searchParams.set("filter[organization-id]", sanitizeUrlParam(orgId));
42
+ if (category && category.length > 0) {
43
+ category.forEach((cat) => url.searchParams.append("filter[category][]", sanitizeUrlParam(cat)));
44
+ }
45
+ if (subcategories && subcategories.length > 0) {
46
+ subcategories.forEach((sub) => url.searchParams.append("filter[subcategories][]", sanitizeUrlParam(sub)));
47
+ }
48
+ if (browserIds && browserIds.length > 0) {
49
+ browserIds.forEach((id) => url.searchParams.append("filter[browser_ids][]", sanitizeUrlParam(id)));
50
+ }
51
+ if (widths && widths.length > 0) {
52
+ widths.forEach((w) => url.searchParams.append("filter[widths][]", sanitizeUrlParam(w)));
53
+ }
54
+ if (groupSnapshotsBy) {
55
+ url.searchParams.set("filter[group_snapshots_by]", sanitizeUrlParam(groupSnapshotsBy));
56
+ }
57
+ return url.toString();
58
+ }
@@ -0,0 +1,5 @@
1
+ import { BrowserStackConfig } from "../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function fetchPercyChanges(args: {
4
+ project_name: string;
5
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,56 @@
1
+ import { getBrowserStackAuth } from "../lib/get-auth.js";
2
+ import { getPercyBuildCount } from "./review-agent-utils/build-counts.js";
3
+ import { getChangedPercySnapshotIds } from "./review-agent-utils/percy-snapshots.js";
4
+ import { PercyIntegrationTypeEnum } from "./sdk-utils/common/types.js";
5
+ import { fetchPercyToken } from "./sdk-utils/percy-web/fetchPercyToken.js";
6
+ import { getPercySnapshotDiffs, } from "./review-agent-utils/percy-diffs.js";
7
+ export async function fetchPercyChanges(args, config) {
8
+ const { project_name } = args;
9
+ const authorization = getBrowserStackAuth(config);
10
+ // Get Percy token for the project
11
+ const percyToken = await fetchPercyToken(project_name, authorization, {
12
+ type: PercyIntegrationTypeEnum.WEB,
13
+ });
14
+ // Get build info (noBuilds, isFirstBuild, lastBuildId)
15
+ const { noBuilds, isFirstBuild, lastBuildId, orgId, browserIds } = await getPercyBuildCount(percyToken);
16
+ if (noBuilds) {
17
+ return {
18
+ content: [
19
+ {
20
+ type: "text",
21
+ text: "No Percy builds found. Please run your first Percy scan to start visual testing.",
22
+ },
23
+ ],
24
+ };
25
+ }
26
+ if (isFirstBuild || !lastBuildId) {
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: "This is the first Percy build. No baseline exists to compare changes.",
32
+ },
33
+ ],
34
+ };
35
+ }
36
+ // Get snapshot IDs for the latest build
37
+ const snapshotIds = await getChangedPercySnapshotIds(lastBuildId, config, orgId, browserIds);
38
+ // Fetch all diffs concurrently and flatten results
39
+ const allDiffs = await getPercySnapshotDiffs(snapshotIds, percyToken);
40
+ if (allDiffs.length === 0) {
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text",
45
+ text: "AI Summary is not yet available for this build/framework. There may still be visual changes—please review the build on the dashboard.",
46
+ },
47
+ ],
48
+ };
49
+ }
50
+ return {
51
+ content: allDiffs.map((diff) => ({
52
+ type: "text",
53
+ text: `${diff.name} → ${diff.title}: ${diff.description ?? ""}`,
54
+ })),
55
+ };
56
+ }
@@ -0,0 +1,8 @@
1
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import { PercyIntegrationTypeEnum } from "./sdk-utils/common/types.js";
3
+ import { BrowserStackConfig } from "../lib/types.js";
4
+ export declare function runPercyScan(args: {
5
+ projectName: string;
6
+ integrationType: PercyIntegrationTypeEnum;
7
+ instruction?: string;
8
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,37 @@
1
+ import { getBrowserStackAuth } from "../lib/get-auth.js";
2
+ import { fetchPercyToken } from "./sdk-utils/percy-web/fetchPercyToken.js";
3
+ export async function runPercyScan(args, config) {
4
+ const { projectName, integrationType, instruction } = args;
5
+ const authorization = getBrowserStackAuth(config);
6
+ const percyToken = await fetchPercyToken(projectName, authorization, {
7
+ type: integrationType,
8
+ });
9
+ const steps = [generatePercyTokenInstructions(percyToken)];
10
+ if (instruction) {
11
+ steps.push(`Use the provided test command with Percy:\n${instruction}`, `If this command fails or is incorrect, fall back to the default approach below.`);
12
+ }
13
+ steps.push(`Attempt to infer the project's test command from context (high confidence commands first):
14
+ - Java → mvn test
15
+ - Python → pytest
16
+ - Node.js → npm test or yarn test
17
+ - Cypress → cypress run
18
+ or from package.json scripts`, `Wrap the inferred command with Percy:\nnpx percy exec -- <test command>`, `If the test command cannot be inferred confidently, ask the user directly for the correct test command.`);
19
+ const instructionContext = steps
20
+ .map((step, index) => `${index + 1}. ${step}`)
21
+ .join("\n\n");
22
+ return {
23
+ content: [
24
+ {
25
+ type: "text",
26
+ text: instructionContext,
27
+ },
28
+ ],
29
+ };
30
+ }
31
+ function generatePercyTokenInstructions(percyToken) {
32
+ return `Set the environment variable for your project:
33
+
34
+ export PERCY_TOKEN="${percyToken}"
35
+
36
+ (For Windows: use 'setx PERCY_TOKEN "${percyToken}"' or 'set PERCY_TOKEN=${percyToken}' as appropriate.)`;
37
+ }
@@ -1,3 +1,3 @@
1
- import { SDKSupportedLanguage } from "./types.js";
1
+ import { SDKSupportedLanguage } from "../common/types.js";
2
2
  export declare function getSDKPrefixCommand(language: SDKSupportedLanguage, framework: string, username: string, accessKey: string): string;
3
3
  export declare function getJavaFrameworkForMaven(framework: string): string;
@@ -0,0 +1,88 @@
1
+ // Constants
2
+ const MAVEN_ARCHETYPE_GROUP_ID = "com.browserstack";
3
+ const MAVEN_ARCHETYPE_ARTIFACT_ID = "browserstack-sdk-archetype-integrate";
4
+ const MAVEN_ARCHETYPE_VERSION = "1.0";
5
+ // Mapping of test frameworks to their corresponding Maven archetype framework names
6
+ const JAVA_FRAMEWORK_MAP = {
7
+ testng: "testng",
8
+ junit5: "junit5",
9
+ junit4: "junit4",
10
+ cucumber: "cucumber-testng",
11
+ };
12
+ // Template for Node.js SDK setup instructions
13
+ const NODEJS_SDK_INSTRUCTIONS = (username, accessKey) => `---STEP---
14
+ Install BrowserStack Node SDK using command:
15
+ \`\`\`bash
16
+ npm i -D browserstack-node-sdk@latest
17
+ \`\`\`
18
+ ---STEP---
19
+ Run the following command to setup browserstack sdk:
20
+ \`\`\`bash
21
+ npx setup --username ${username} --key ${accessKey}
22
+ \`\`\``;
23
+ // Template for Gradle setup instructions (platform-independent)
24
+ const GRADLE_SETUP_INSTRUCTIONS = `
25
+ **For Gradle setup:**
26
+ 1. Add browserstack-java-sdk to dependencies:
27
+ compileOnly 'com.browserstack:browserstack-java-sdk:latest.release'
28
+
29
+ 2. Add browserstackSDK path variable:
30
+ def browserstackSDKArtifact = configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.find { it.name == 'browserstack-java-sdk' }
31
+
32
+ 3. Add javaagent to gradle tasks:
33
+ jvmArgs "-javaagent:\${browserstackSDKArtifact.file}"
34
+ `;
35
+ // Generates Maven archetype command for Windows platform
36
+ function getMavenCommandForWindows(framework, mavenFramework) {
37
+ return (`mvn archetype:generate -B ` +
38
+ `-DarchetypeGroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
39
+ `-DarchetypeArtifactId="${MAVEN_ARCHETYPE_ARTIFACT_ID}" ` +
40
+ `-DarchetypeVersion="${MAVEN_ARCHETYPE_VERSION}" ` +
41
+ `-DgroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
42
+ `-DartifactId="${MAVEN_ARCHETYPE_ARTIFACT_ID}" ` +
43
+ `-Dversion="${MAVEN_ARCHETYPE_VERSION}" ` +
44
+ `-DBROWSERSTACK_USERNAME="${process.env.BROWSERSTACK_USERNAME}" ` +
45
+ `-DBROWSERSTACK_ACCESS_KEY="${process.env.BROWSERSTACK_ACCESS_KEY}" ` +
46
+ `-DBROWSERSTACK_FRAMEWORK="${mavenFramework}"`);
47
+ }
48
+ // Generates Maven archetype command for Unix-like platforms (macOS/Linux)
49
+ function getMavenCommandForUnix(username, accessKey, mavenFramework) {
50
+ return `mvn archetype:generate -B -DarchetypeGroupId=${MAVEN_ARCHETYPE_GROUP_ID} \\
51
+ -DarchetypeArtifactId=${MAVEN_ARCHETYPE_ARTIFACT_ID} -DarchetypeVersion=${MAVEN_ARCHETYPE_VERSION} \\
52
+ -DgroupId=${MAVEN_ARCHETYPE_GROUP_ID} -DartifactId=${MAVEN_ARCHETYPE_ARTIFACT_ID} -Dversion=${MAVEN_ARCHETYPE_VERSION} \\
53
+ -DBROWSERSTACK_USERNAME="${username}" \\
54
+ -DBROWSERSTACK_ACCESS_KEY="${accessKey}" \\
55
+ -DBROWSERSTACK_FRAMEWORK="${mavenFramework}"`;
56
+ }
57
+ // Generates Java SDK setup instructions with Maven/Gradle options
58
+ function getJavaSDKInstructions(framework, username, accessKey) {
59
+ const mavenFramework = getJavaFrameworkForMaven(framework);
60
+ const isWindows = process.platform === "win32";
61
+ const platformLabel = isWindows ? "Windows" : "macOS/Linux";
62
+ const mavenCommand = isWindows
63
+ ? getMavenCommandForWindows(framework, mavenFramework)
64
+ : getMavenCommandForUnix(username, accessKey, mavenFramework);
65
+ return `---STEP---
66
+ Install BrowserStack Java SDK
67
+
68
+ **Maven command for ${framework} (${platformLabel}):**
69
+ Run the command, it is required to generate the browserstack-sdk-archetype-integrate project:
70
+ ${mavenCommand}
71
+
72
+ Alternative setup for Gradle users:
73
+ ${GRADLE_SETUP_INSTRUCTIONS}`;
74
+ }
75
+ // Main function to get SDK setup commands based on language and framework
76
+ export function getSDKPrefixCommand(language, framework, username, accessKey) {
77
+ switch (language) {
78
+ case "nodejs":
79
+ return NODEJS_SDK_INSTRUCTIONS(username, accessKey);
80
+ case "java":
81
+ return getJavaSDKInstructions(framework, username, accessKey);
82
+ default:
83
+ return "";
84
+ }
85
+ }
86
+ export function getJavaFrameworkForMaven(framework) {
87
+ return JAVA_FRAMEWORK_MAP[framework] || framework;
88
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Utilities for generating BrowserStack configuration files.
3
+ */
4
+ export declare function generateBrowserStackYMLInstructions(desiredPlatforms: string[], enablePercy: boolean | undefined, projectName: string): string;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Utilities for generating BrowserStack configuration files.
3
+ */
4
+ export function generateBrowserStackYMLInstructions(desiredPlatforms, enablePercy = false, projectName) {
5
+ let ymlContent = `
6
+ # ======================
7
+ # BrowserStack Reporting
8
+ # ======================
9
+ # A single name for your project to organize all your tests. This is required for Percy.
10
+ projectName: ${projectName}
11
+ # TODO: Replace these sample values with your actual project details
12
+ buildName: Sample-Build
13
+
14
+ # =======================================
15
+ # Platforms (Browsers / Devices to test)
16
+ # =======================================
17
+ # Platforms object contains all the browser / device combinations you want to test on.
18
+ # Generate this on the basis of the following platforms requested by the user:
19
+ # Requested platforms: ${desiredPlatforms}
20
+ platforms:
21
+ - os: Windows
22
+ osVersion: 11
23
+ browserName: chrome
24
+ browserVersion: latest
25
+
26
+ # =======================
27
+ # Parallels per Platform
28
+ # =======================
29
+ # The number of parallel threads to be used for each platform set.
30
+ # BrowserStack's SDK runner will select the best strategy based on the configured value
31
+ #
32
+ # Example 1 - If you have configured 3 platforms and set \`parallelsPerPlatform\` as 2, a total of 6 (2 * 3) parallel threads will be used on BrowserStack
33
+ #
34
+ # Example 2 - If you have configured 1 platform and set \`parallelsPerPlatform\` as 5, a total of 5 (1 * 5) parallel threads will be used on BrowserStack
35
+ parallelsPerPlatform: 1
36
+
37
+ # =================
38
+ # Local Testing
39
+ # =================
40
+ # Set to true to test local
41
+ browserstackLocal: true
42
+
43
+ # ===================
44
+ # Debugging features
45
+ # ===================
46
+ debug: true # Visual logs, text logs, etc.
47
+ testObservability: true # For Test Observability`;
48
+ if (enablePercy) {
49
+ ymlContent += `
50
+
51
+ # =====================
52
+ # Percy Visual Testing
53
+ # =====================
54
+ # Set percy to true to enable visual testing.
55
+ # Set percyCaptureMode to 'manual' to control when screenshots are taken.
56
+ percy: true
57
+ percyCaptureMode: manual`;
58
+ }
59
+ return `
60
+ ---STEP---
61
+ Create a browserstack.yml file in the project root. The file should be in the following format:
62
+
63
+ \`\`\`yaml${ymlContent}
64
+ \`\`\`
65
+ \n`;
66
+ }
@@ -0,0 +1,58 @@
1
+ import { ConfigMapping } from "../common/types.js";
2
+ /**
3
+ * ---------- PYTHON INSTRUCTIONS ----------
4
+ */
5
+ export declare const pythonInstructions: (username: string, accessKey: string) => {
6
+ setup: string;
7
+ run: string;
8
+ };
9
+ export declare const generatePythonFrameworkInstructions: (framework: string) => (username: string, accessKey: string) => {
10
+ setup: string;
11
+ run: string;
12
+ };
13
+ export declare const robotInstructions: (username: string, accessKey: string) => {
14
+ setup: string;
15
+ run: string;
16
+ };
17
+ export declare const behaveInstructions: (username: string, accessKey: string) => {
18
+ setup: string;
19
+ run: string;
20
+ };
21
+ export declare const pytestInstructions: (username: string, accessKey: string) => {
22
+ setup: string;
23
+ run: string;
24
+ };
25
+ export declare const javaInstructions: (username: string, accessKey: string) => {
26
+ setup: string;
27
+ run: string;
28
+ };
29
+ /**
30
+ * ---------- CSharp INSTRUCTIONS ----------
31
+ */
32
+ export declare const csharpCommonInstructions: (username: string, accessKey: string) => {
33
+ setup: string;
34
+ run: string;
35
+ };
36
+ export declare const csharpPlaywrightCommonInstructions: (username: string, accessKey: string) => {
37
+ setup: string;
38
+ run: string;
39
+ };
40
+ /**
41
+ * ---------- NODEJS INSTRUCTIONS ----------
42
+ */
43
+ export declare const nodejsInstructions: (username: string, accessKey: string) => {
44
+ setup: string;
45
+ run: string;
46
+ };
47
+ /**
48
+ * ---------- EXPORT CONFIG ----------
49
+ */
50
+ export declare const webdriverioInstructions: (username: string, accessKey: string) => {
51
+ setup: string;
52
+ run: string;
53
+ };
54
+ export declare const cypressInstructions: (username: string, accessKey: string) => {
55
+ setup: string;
56
+ run: string;
57
+ };
58
+ export declare const SUPPORTED_CONFIGURATIONS: ConfigMapping;