@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.
- package/README.md +88 -2
- package/dist/lib/device-cache.js +20 -17
- package/dist/lib/inmemory-store.d.ts +1 -0
- package/dist/lib/inmemory-store.js +1 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +27 -0
- package/dist/server-factory.js +6 -0
- package/dist/tools/add-percy-snapshots.d.ts +5 -0
- package/dist/tools/add-percy-snapshots.js +17 -0
- package/dist/tools/appautomate-utils/appium-sdk/config-generator.d.ts +1 -0
- package/dist/tools/appautomate-utils/appium-sdk/config-generator.js +50 -0
- package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +23 -0
- package/dist/tools/appautomate-utils/appium-sdk/constants.js +43 -0
- package/dist/tools/appautomate-utils/appium-sdk/formatter.d.ts +8 -0
- package/dist/tools/appautomate-utils/appium-sdk/formatter.js +59 -0
- package/dist/tools/appautomate-utils/appium-sdk/handler.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/handler.js +52 -0
- package/dist/tools/appautomate-utils/appium-sdk/index.d.ts +7 -0
- package/dist/tools/appautomate-utils/appium-sdk/index.js +8 -0
- package/dist/tools/appautomate-utils/appium-sdk/instructions.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/instructions.js +47 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.d.ts +2 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.js +78 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/java.d.ts +8 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/java.js +87 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.js +194 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/python.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/python.js +76 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.d.ts +2 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.js +85 -0
- package/dist/tools/appautomate-utils/appium-sdk/types.d.ts +57 -0
- package/dist/tools/appautomate-utils/appium-sdk/types.js +54 -0
- package/dist/tools/appautomate-utils/appium-sdk/utils.d.ts +17 -0
- package/dist/tools/appautomate-utils/appium-sdk/utils.js +64 -0
- package/dist/tools/appautomate-utils/{appautomate.d.ts → native-execution/appautomate.d.ts} +1 -1
- package/dist/tools/appautomate-utils/{appautomate.js → native-execution/appautomate.js} +2 -2
- package/dist/tools/appautomate-utils/native-execution/constants.d.ts +10 -0
- package/dist/tools/appautomate-utils/native-execution/constants.js +36 -0
- package/dist/tools/appautomate-utils/native-execution/types.d.ts +19 -0
- package/dist/tools/appautomate-utils/{types.js → native-execution/types.js} +5 -1
- package/dist/tools/appautomate.js +25 -40
- package/dist/tools/bstack-sdk.d.ts +2 -15
- package/dist/tools/bstack-sdk.js +10 -119
- package/dist/tools/build-insights.d.ts +7 -0
- package/dist/tools/build-insights.js +67 -0
- package/dist/tools/list-test-files.d.ts +2 -0
- package/dist/tools/list-test-files.js +36 -0
- package/dist/tools/percy-sdk.d.ts +4 -0
- package/dist/tools/percy-sdk.js +71 -0
- package/dist/tools/percy-snapshot-utils/constants.d.ts +16 -0
- package/dist/tools/percy-snapshot-utils/constants.js +500 -0
- package/dist/tools/percy-snapshot-utils/detect-test-files.d.ts +10 -0
- package/dist/tools/percy-snapshot-utils/detect-test-files.js +175 -0
- package/dist/tools/percy-snapshot-utils/types.d.ts +15 -0
- package/dist/tools/percy-snapshot-utils/utils.d.ts +4 -0
- package/dist/tools/percy-snapshot-utils/utils.js +30 -0
- package/dist/tools/rca-agent-utils/constants.d.ts +13 -0
- package/dist/tools/rca-agent-utils/constants.js +24 -0
- package/dist/tools/rca-agent-utils/format-rca.d.ts +1 -0
- package/dist/tools/rca-agent-utils/format-rca.js +37 -0
- package/dist/tools/rca-agent-utils/get-build-id.d.ts +1 -0
- package/dist/tools/rca-agent-utils/get-build-id.js +18 -0
- package/dist/tools/rca-agent-utils/get-failed-test-id.d.ts +2 -0
- package/dist/tools/rca-agent-utils/get-failed-test-id.js +69 -0
- package/dist/tools/rca-agent-utils/rca-data.d.ts +9 -0
- package/dist/tools/rca-agent-utils/rca-data.js +196 -0
- package/dist/tools/rca-agent-utils/types.d.ts +48 -0
- package/dist/tools/rca-agent-utils/types.js +20 -0
- package/dist/tools/rca-agent.d.ts +14 -0
- package/dist/tools/rca-agent.js +119 -0
- package/dist/tools/review-agent-utils/build-counts.d.ts +7 -0
- package/dist/tools/review-agent-utils/build-counts.js +44 -0
- package/dist/tools/review-agent-utils/percy-approve-reject.d.ts +6 -0
- package/dist/tools/review-agent-utils/percy-approve-reject.js +39 -0
- package/dist/tools/review-agent-utils/percy-diffs.d.ts +9 -0
- package/dist/tools/review-agent-utils/percy-diffs.js +35 -0
- package/dist/tools/review-agent-utils/percy-snapshots.d.ts +11 -0
- package/dist/tools/review-agent-utils/percy-snapshots.js +58 -0
- package/dist/tools/review-agent.d.ts +5 -0
- package/dist/tools/review-agent.js +56 -0
- package/dist/tools/run-percy-scan.d.ts +8 -0
- package/dist/tools/run-percy-scan.js +37 -0
- package/dist/tools/sdk-utils/{commands.d.ts → bstack/commands.d.ts} +1 -1
- package/dist/tools/sdk-utils/bstack/commands.js +88 -0
- package/dist/tools/sdk-utils/bstack/configUtils.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/configUtils.js +66 -0
- package/dist/tools/sdk-utils/bstack/constants.d.ts +58 -0
- package/dist/tools/sdk-utils/{constants.js → bstack/constants.js} +117 -78
- package/dist/tools/sdk-utils/{constants.d.ts → bstack/frameworks.d.ts} +1 -1
- package/dist/tools/sdk-utils/bstack/frameworks.js +57 -0
- package/dist/tools/sdk-utils/bstack/index.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/index.js +5 -0
- package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/sdkHandler.js +74 -0
- package/dist/tools/sdk-utils/common/constants.d.ts +10 -0
- package/dist/tools/sdk-utils/common/constants.js +86 -0
- package/dist/tools/sdk-utils/common/formatUtils.d.ts +5 -0
- package/dist/tools/sdk-utils/common/formatUtils.js +27 -0
- package/dist/tools/sdk-utils/common/index.d.ts +3 -0
- package/dist/tools/sdk-utils/common/index.js +4 -0
- package/dist/tools/sdk-utils/common/instructionUtils.d.ts +8 -0
- package/dist/tools/sdk-utils/common/instructionUtils.js +20 -0
- package/dist/tools/sdk-utils/common/schema.d.ts +73 -0
- package/dist/tools/sdk-utils/common/schema.js +51 -0
- package/dist/tools/sdk-utils/common/types.d.ts +66 -0
- package/dist/tools/sdk-utils/{types.js → common/types.js} +15 -2
- package/dist/tools/sdk-utils/common/utils.d.ts +25 -0
- package/dist/tools/sdk-utils/common/utils.js +90 -0
- package/dist/tools/sdk-utils/handler.d.ts +4 -0
- package/dist/tools/sdk-utils/handler.js +119 -0
- package/dist/tools/sdk-utils/percy-automate/constants.d.ts +11 -0
- package/dist/tools/sdk-utils/percy-automate/constants.js +338 -0
- package/dist/tools/sdk-utils/percy-automate/frameworks.d.ts +8 -0
- package/dist/tools/sdk-utils/percy-automate/frameworks.js +50 -0
- package/dist/tools/sdk-utils/percy-automate/handler.d.ts +3 -0
- package/dist/tools/sdk-utils/percy-automate/handler.js +30 -0
- package/dist/tools/sdk-utils/percy-automate/index.d.ts +1 -0
- package/dist/tools/sdk-utils/percy-automate/index.js +2 -0
- package/dist/tools/sdk-utils/percy-automate/types.d.ts +13 -0
- package/dist/tools/sdk-utils/percy-automate/types.js +1 -0
- package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -0
- package/dist/tools/sdk-utils/{percy → percy-bstack}/constants.js +13 -39
- package/dist/tools/sdk-utils/percy-bstack/frameworks.d.ts +2 -0
- package/dist/tools/sdk-utils/percy-bstack/frameworks.js +27 -0
- package/dist/tools/sdk-utils/percy-bstack/handler.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-bstack/handler.js +99 -0
- package/dist/tools/sdk-utils/percy-bstack/index.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-bstack/index.js +4 -0
- package/dist/tools/sdk-utils/percy-bstack/instructions.d.ts +7 -0
- package/dist/tools/sdk-utils/{percy → percy-bstack}/instructions.js +5 -9
- package/dist/tools/sdk-utils/percy-bstack/types.d.ts +13 -0
- package/dist/tools/sdk-utils/percy-bstack/types.js +5 -0
- package/dist/tools/sdk-utils/percy-web/constants.d.ts +41 -0
- package/dist/tools/sdk-utils/percy-web/constants.js +883 -0
- package/dist/tools/sdk-utils/percy-web/fetchPercyToken.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/fetchPercyToken.js +32 -0
- package/dist/tools/sdk-utils/percy-web/frameworks.d.ts +7 -0
- package/dist/tools/sdk-utils/percy-web/frameworks.js +103 -0
- package/dist/tools/sdk-utils/percy-web/handler.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/handler.js +27 -0
- package/dist/tools/sdk-utils/percy-web/index.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/index.js +4 -0
- package/dist/tools/sdk-utils/percy-web/types.d.ts +12 -0
- package/dist/tools/sdk-utils/percy-web/types.js +1 -0
- package/dist/tools/testmanagement-utils/create-testrun.d.ts +4 -4
- package/dist/tools/testmanagement-utils/update-testrun.d.ts +4 -4
- package/package.json +2 -1
- package/dist/tools/appautomate-utils/types.d.ts +0 -5
- package/dist/tools/sdk-utils/commands.js +0 -65
- package/dist/tools/sdk-utils/instructions.d.ts +0 -6
- package/dist/tools/sdk-utils/instructions.js +0 -99
- package/dist/tools/sdk-utils/percy/constants.d.ts +0 -3
- package/dist/tools/sdk-utils/percy/instructions.d.ts +0 -10
- package/dist/tools/sdk-utils/percy/types.d.ts +0 -5
- package/dist/tools/sdk-utils/types.d.ts +0 -40
- /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,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 "
|
|
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,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;
|