@browserstack/mcp-server 1.2.7 → 1.2.8-beta.1
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/dist/lib/tm-base-url.d.ts +2 -0
- package/dist/lib/tm-base-url.js +37 -0
- package/dist/lib/utils.d.ts +1 -1
- package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +27 -2
- package/dist/tools/appautomate-utils/appium-sdk/constants.js +11 -21
- package/dist/tools/appautomate-utils/appium-sdk/handler.js +5 -1
- package/dist/tools/appautomate-utils/native-execution/constants.d.ts +26 -2
- package/dist/tools/appautomate-utils/native-execution/constants.js +10 -21
- package/dist/tools/appautomate.js +2 -1
- package/dist/tools/sdk-utils/bstack/sdkHandler.js +22 -3
- package/dist/tools/sdk-utils/common/constants.d.ts +2 -2
- package/dist/tools/sdk-utils/common/constants.js +3 -3
- package/dist/tools/sdk-utils/common/schema.d.ts +52 -12
- package/dist/tools/sdk-utils/common/schema.js +23 -41
- package/dist/tools/testmanagement-utils/TCG-utils/api.js +21 -9
- package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +5 -5
- package/dist/tools/testmanagement-utils/TCG-utils/config.js +5 -5
- package/dist/tools/testmanagement-utils/add-test-result.js +3 -1
- package/dist/tools/testmanagement-utils/create-lca-steps.js +4 -2
- package/dist/tools/testmanagement-utils/create-project-folder.js +6 -3
- package/dist/tools/testmanagement-utils/create-testcase.js +4 -2
- package/dist/tools/testmanagement-utils/create-testrun.js +3 -1
- package/dist/tools/testmanagement-utils/list-testcases.js +3 -1
- package/dist/tools/testmanagement-utils/list-testruns.js +3 -1
- package/dist/tools/testmanagement-utils/poll-lca-status.js +3 -1
- package/dist/tools/testmanagement-utils/testcase-from-file.js +3 -1
- package/dist/tools/testmanagement-utils/update-testrun.js +3 -1
- package/dist/tools/testmanagement-utils/upload-file.js +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { apiClient } from "./apiClient.js";
|
|
2
|
+
import logger from "../logger.js";
|
|
3
|
+
import { getBrowserStackAuth } from "./get-auth.js";
|
|
4
|
+
const TM_BASE_URLS = [
|
|
5
|
+
"https://test-management.browserstack.com",
|
|
6
|
+
"https://test-management-eu.browserstack.com",
|
|
7
|
+
"https://test-management-in.browserstack.com",
|
|
8
|
+
];
|
|
9
|
+
let cachedBaseUrl = null;
|
|
10
|
+
export async function getTMBaseURL(config) {
|
|
11
|
+
if (cachedBaseUrl) {
|
|
12
|
+
logger.debug(`Using cached TM base URL: ${cachedBaseUrl}`);
|
|
13
|
+
return cachedBaseUrl;
|
|
14
|
+
}
|
|
15
|
+
logger.info("No cached TM base URL found, testing available URLs with authentication");
|
|
16
|
+
const authString = getBrowserStackAuth(config);
|
|
17
|
+
const [username, password] = authString.split(":");
|
|
18
|
+
const authHeader = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
|
19
|
+
for (const baseUrl of TM_BASE_URLS) {
|
|
20
|
+
try {
|
|
21
|
+
const res = await apiClient.get({
|
|
22
|
+
url: `${baseUrl}/api/v2/projects/`,
|
|
23
|
+
headers: { Authorization: authHeader },
|
|
24
|
+
raise_error: false,
|
|
25
|
+
});
|
|
26
|
+
if (res.ok) {
|
|
27
|
+
cachedBaseUrl = baseUrl;
|
|
28
|
+
logger.info(`Selected TM base URL: ${baseUrl}`);
|
|
29
|
+
return baseUrl;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
logger.debug(`Failed TM base URL: ${baseUrl} (${err})`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
throw new Error("Unable to connect to BrowserStack Test Management. Please check your credentials and network connection.Please open an issue on GitHub if the problem persists");
|
|
37
|
+
}
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export declare function handleMCPError(toolName: string, server: McpServer, conf
|
|
|
45
45
|
[x: string]: unknown;
|
|
46
46
|
src: string;
|
|
47
47
|
mimeType?: string | undefined;
|
|
48
|
-
sizes?: string | undefined;
|
|
48
|
+
sizes?: string[] | undefined;
|
|
49
49
|
}[] | undefined;
|
|
50
50
|
} | {
|
|
51
51
|
[x: string]: unknown;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { AppSDKSupportedFrameworkEnum, AppSDKSupportedTestingFrameworkEnum, AppSDKSupportedLanguageEnum
|
|
2
|
+
import { AppSDKSupportedFrameworkEnum, AppSDKSupportedTestingFrameworkEnum, AppSDKSupportedLanguageEnum } from "./index.js";
|
|
3
3
|
export declare const APP_DEVICE_CONFIGS: {
|
|
4
4
|
android: {
|
|
5
5
|
deviceName: string;
|
|
@@ -12,12 +12,37 @@ export declare const APP_DEVICE_CONFIGS: {
|
|
|
12
12
|
};
|
|
13
13
|
export declare const STEP_DELIMITER = "---STEP---";
|
|
14
14
|
export declare const DEFAULT_APP_PATH = "bs://sample.app";
|
|
15
|
+
export declare const MobileDeviceSchema: z.ZodObject<{
|
|
16
|
+
platform: z.ZodEnum<["android", "ios"]>;
|
|
17
|
+
deviceName: z.ZodString;
|
|
18
|
+
osVersion: z.ZodString;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
platform: "android" | "ios";
|
|
21
|
+
deviceName: string;
|
|
22
|
+
osVersion: string;
|
|
23
|
+
}, {
|
|
24
|
+
platform: "android" | "ios";
|
|
25
|
+
deviceName: string;
|
|
26
|
+
osVersion: string;
|
|
27
|
+
}>;
|
|
15
28
|
export declare const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. ONLY for Appium based framework . This tool configures SDK for various languages with appium. For pre-built Espresso or XCUITest test suites, use 'runAppTestsOnBrowserStack' instead.";
|
|
16
29
|
export declare const SETUP_APP_AUTOMATE_SCHEMA: {
|
|
17
30
|
detectedFramework: z.ZodNativeEnum<typeof AppSDKSupportedFrameworkEnum>;
|
|
18
31
|
detectedTestingFramework: z.ZodNativeEnum<typeof AppSDKSupportedTestingFrameworkEnum>;
|
|
19
32
|
detectedLanguage: z.ZodNativeEnum<typeof AppSDKSupportedLanguageEnum>;
|
|
20
|
-
devices: z.ZodDefault<z.ZodArray<z.
|
|
33
|
+
devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
34
|
+
platform: z.ZodEnum<["android", "ios"]>;
|
|
35
|
+
deviceName: z.ZodString;
|
|
36
|
+
osVersion: z.ZodString;
|
|
37
|
+
}, "strip", z.ZodTypeAny, {
|
|
38
|
+
platform: "android" | "ios";
|
|
39
|
+
deviceName: string;
|
|
40
|
+
osVersion: string;
|
|
41
|
+
}, {
|
|
42
|
+
platform: "android" | "ios";
|
|
43
|
+
deviceName: string;
|
|
44
|
+
osVersion: string;
|
|
45
|
+
}>, "many">>;
|
|
21
46
|
appPath: z.ZodString;
|
|
22
47
|
project: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
23
48
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { AppSDKSupportedFrameworkEnum, AppSDKSupportedTestingFrameworkEnum, AppSDKSupportedLanguageEnum,
|
|
2
|
+
import { AppSDKSupportedFrameworkEnum, AppSDKSupportedTestingFrameworkEnum, AppSDKSupportedLanguageEnum, } from "./index.js";
|
|
3
3
|
// App Automate specific device configurations
|
|
4
4
|
export const APP_DEVICE_CONFIGS = {
|
|
5
5
|
android: [
|
|
@@ -17,6 +17,15 @@ export const APP_DEVICE_CONFIGS = {
|
|
|
17
17
|
export const STEP_DELIMITER = "---STEP---";
|
|
18
18
|
// Default app path for examples
|
|
19
19
|
export const DEFAULT_APP_PATH = "bs://sample.app";
|
|
20
|
+
export const MobileDeviceSchema = z.object({
|
|
21
|
+
platform: z
|
|
22
|
+
.enum(["android", "ios"])
|
|
23
|
+
.describe("Platform name: 'android' or 'ios'"),
|
|
24
|
+
deviceName: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8', 'iPhone 15', 'iPhone 14 Pro'"),
|
|
27
|
+
osVersion: z.string().describe("OS version, e.g. '14', '16', '17', 'latest'"),
|
|
28
|
+
});
|
|
20
29
|
// Tool description and schema for setupBrowserStackAppAutomateTests
|
|
21
30
|
export const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. ONLY for Appium based framework . This tool configures SDK for various languages with appium. For pre-built Espresso or XCUITest test suites, use 'runAppTestsOnBrowserStack' instead.";
|
|
22
31
|
export const SETUP_APP_AUTOMATE_SCHEMA = {
|
|
@@ -30,26 +39,7 @@ export const SETUP_APP_AUTOMATE_SCHEMA = {
|
|
|
30
39
|
.nativeEnum(AppSDKSupportedLanguageEnum)
|
|
31
40
|
.describe("The programming language used in the project. Supports Java and C#. Example: 'java', 'csharp'"),
|
|
32
41
|
devices: z
|
|
33
|
-
.array(
|
|
34
|
-
// Android: [android, deviceName, osVersion]
|
|
35
|
-
z.tuple([
|
|
36
|
-
z
|
|
37
|
-
.literal(AppSDKSupportedPlatformEnum.android)
|
|
38
|
-
.describe("Platform identifier: 'android'"),
|
|
39
|
-
z
|
|
40
|
-
.string()
|
|
41
|
-
.describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'"),
|
|
42
|
-
z.string().describe("Android version, e.g. '14', '16', 'latest'"),
|
|
43
|
-
]),
|
|
44
|
-
// iOS: [ios, deviceName, osVersion]
|
|
45
|
-
z.tuple([
|
|
46
|
-
z
|
|
47
|
-
.literal(AppSDKSupportedPlatformEnum.ios)
|
|
48
|
-
.describe("Platform identifier: 'ios'"),
|
|
49
|
-
z.string().describe("Device name, e.g. 'iPhone 15', 'iPhone 14 Pro'"),
|
|
50
|
-
z.string().describe("iOS version, e.g. '17', '16', 'latest'"),
|
|
51
|
-
]),
|
|
52
|
-
]))
|
|
42
|
+
.array(MobileDeviceSchema)
|
|
53
43
|
.max(3)
|
|
54
44
|
.default([])
|
|
55
45
|
.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']]"),
|
|
@@ -12,7 +12,11 @@ export async function setupAppAutomateHandler(rawInput, config) {
|
|
|
12
12
|
// Use variables for all major input properties
|
|
13
13
|
const testingFramework = input.detectedTestingFramework;
|
|
14
14
|
const language = input.detectedLanguage;
|
|
15
|
-
const inputDevices = input.devices
|
|
15
|
+
const inputDevices = input.devices?.map((device) => [
|
|
16
|
+
device.platform,
|
|
17
|
+
device.deviceName,
|
|
18
|
+
device.osVersion,
|
|
19
|
+
]) ?? [];
|
|
16
20
|
const appPath = input.appPath;
|
|
17
21
|
const framework = input.detectedFramework;
|
|
18
22
|
//Validating if supported framework or not
|
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AppTestPlatform } from "./types.js";
|
|
3
|
-
import { AppSDKSupportedPlatformEnum } from "../appium-sdk/types.js";
|
|
4
3
|
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
|
+
export declare const MobileDeviceSchema: z.ZodObject<{
|
|
5
|
+
platform: z.ZodEnum<["android", "ios"]>;
|
|
6
|
+
deviceName: z.ZodString;
|
|
7
|
+
osVersion: z.ZodString;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
platform: "android" | "ios";
|
|
10
|
+
deviceName: string;
|
|
11
|
+
osVersion: string;
|
|
12
|
+
}, {
|
|
13
|
+
platform: "android" | "ios";
|
|
14
|
+
deviceName: string;
|
|
15
|
+
osVersion: string;
|
|
16
|
+
}>;
|
|
5
17
|
export declare const RUN_APP_AUTOMATE_SCHEMA: {
|
|
6
18
|
appPath: z.ZodString;
|
|
7
19
|
testSuitePath: z.ZodString;
|
|
8
|
-
devices: z.ZodDefault<z.ZodArray<z.
|
|
20
|
+
devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
21
|
+
platform: z.ZodEnum<["android", "ios"]>;
|
|
22
|
+
deviceName: z.ZodString;
|
|
23
|
+
osVersion: z.ZodString;
|
|
24
|
+
}, "strip", z.ZodTypeAny, {
|
|
25
|
+
platform: "android" | "ios";
|
|
26
|
+
deviceName: string;
|
|
27
|
+
osVersion: string;
|
|
28
|
+
}, {
|
|
29
|
+
platform: "android" | "ios";
|
|
30
|
+
deviceName: string;
|
|
31
|
+
osVersion: string;
|
|
32
|
+
}>, "many">>;
|
|
9
33
|
project: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
10
34
|
detectedAutomationFramework: z.ZodNativeEnum<typeof AppTestPlatform>;
|
|
11
35
|
};
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AppTestPlatform } from "./types.js";
|
|
3
|
-
import { AppSDKSupportedPlatformEnum } from "../appium-sdk/types.js";
|
|
4
3
|
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
|
+
export const MobileDeviceSchema = z.object({
|
|
5
|
+
platform: z
|
|
6
|
+
.enum(["android", "ios"])
|
|
7
|
+
.describe("Platform name: 'android' or 'ios'"),
|
|
8
|
+
deviceName: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8', 'iPhone 15', 'iPhone 14 Pro'"),
|
|
11
|
+
osVersion: z.string().describe("OS version, e.g. '14', '16', '17', 'latest'"),
|
|
12
|
+
});
|
|
5
13
|
export const RUN_APP_AUTOMATE_SCHEMA = {
|
|
6
14
|
appPath: z
|
|
7
15
|
.string()
|
|
@@ -24,26 +32,7 @@ export const RUN_APP_AUTOMATE_SCHEMA = {
|
|
|
24
32
|
" zip -r Tests.zip *.xctestrun *-Runner.app\n\n" +
|
|
25
33
|
"If in other directory, provide existing test file path"),
|
|
26
34
|
devices: z
|
|
27
|
-
.array(
|
|
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
|
-
]))
|
|
35
|
+
.array(MobileDeviceSchema)
|
|
47
36
|
.max(3)
|
|
48
37
|
.default([])
|
|
49
38
|
.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']]"),
|
|
@@ -229,7 +229,8 @@ export default function addAppAutomationTools(server, config) {
|
|
|
229
229
|
tools.runAppTestsOnBrowserStack = server.tool("runAppTestsOnBrowserStack", RUN_APP_AUTOMATE_DESCRIPTION, RUN_APP_AUTOMATE_SCHEMA, async (args) => {
|
|
230
230
|
try {
|
|
231
231
|
trackMCP("runAppTestsOnBrowserStack", server.server.getClientVersion(), undefined, config);
|
|
232
|
-
|
|
232
|
+
const devicesAsArrays = args.devices.map((device) => [device.platform, device.deviceName, device.osVersion]);
|
|
233
|
+
return await runAppTestsOnBrowserStack({ ...args, devices: devicesAsArrays }, config);
|
|
233
234
|
}
|
|
234
235
|
catch (error) {
|
|
235
236
|
trackMCP("runAppTestsOnBrowserStack", server.server.getClientVersion(), error, config);
|
|
@@ -7,9 +7,28 @@ export async function runBstackSDKOnly(input, config, isPercyAutomate = false) {
|
|
|
7
7
|
const steps = [];
|
|
8
8
|
const authString = getBrowserStackAuth(config);
|
|
9
9
|
const [username, accessKey] = authString.split(":");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const tupleTargets = input.devices?.map((device) => {
|
|
11
|
+
const platform = device.platform.toLowerCase();
|
|
12
|
+
if (platform === "windows" || platform === "macos") {
|
|
13
|
+
// Desktop: ["platform", "osVersion", "browser", "browserVersion"]
|
|
14
|
+
return [
|
|
15
|
+
platform,
|
|
16
|
+
device.osVersion || "latest",
|
|
17
|
+
device.browser || "",
|
|
18
|
+
device.browserVersion || "latest",
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// Mobile: ["platform", "deviceName", "osVersion", "browser"]
|
|
23
|
+
return [
|
|
24
|
+
platform,
|
|
25
|
+
device.deviceName || "",
|
|
26
|
+
device.osVersion || "latest",
|
|
27
|
+
device.browser || "",
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
}) || [];
|
|
31
|
+
const validatedEnvironments = await validateDevices(tupleTargets, input.detectedBrowserAutomationFramework);
|
|
13
32
|
// Handle frameworks with unique setup instructions that don't use browserstack.yml
|
|
14
33
|
if (input.detectedBrowserAutomationFramework === "cypress" ||
|
|
15
34
|
input.detectedTestingFramework === "webdriverio") {
|
|
@@ -6,6 +6,6 @@ export declare const RUN_ON_BROWSERSTACK_DESCRIPTION = "Set up and run automated
|
|
|
6
6
|
export declare const SIMULATE_PERCY_CHANGE_DESCRIPTION = "Integrate Percy visual testing into new projects and demonstrate visual change detection through a step-by-step simulation. This tool handles initial Percy integration, setup, and creates a working demonstration for new users. Primary tool for prompts like: \"Integrate percy for this project\", \"Integrate percy in this project {project_name}\"";
|
|
7
7
|
export declare const PERCY_REPLACE_REGEX: RegExp;
|
|
8
8
|
export declare const PERCY_SNAPSHOT_INSTRUCTION = "\nInvoke listTestFiles() with the provided directories from user to gather all test files in memory ---STEP---\nProcess files in STRICT sequential order using tool addPercySnapshotCommands() with below instructions:\n- Start with index 0\n- Then index 1 \n- Then index 2\n- Continue sequentially until the last index\n- DO NOT skip any index numbers\n- DO NOT jump to random indices like 10, 20, 30\n- DO NOT worry about time consuming and any constraints.\n- DO NOT STOP until you add commands in all the files or you reach end of the files.\n";
|
|
9
|
-
export declare const PERCY_SIMULATE_INSTRUCTION = "\nMANDATORY: Aggressive, Accurate Percy Snapshots Integration\n\nPrerequisite: Before making any code changes in the user's repository, create and switch to a new branch named
|
|
10
|
-
export declare const PERCY_SIMULATION_DRIVER_INSTRUCTION = "\nMANDATORY: Step-by-Step Percy Visual Change Simulation\nYou MUST follow these steps with NO EXCEPTIONS:\n\nSTEP 1: At this case we have added Percy snapshot commands to the test files at the appropriate locations (e.g., after navigation, UI change, or significant state). \n- Use ONLY existing snapshot names.\n- Do NOT add any popup injection or visual changes yet.\n- Do NOT run any Percy builds at this stage.\n\nSTEP 2: Run ONE comprehensive baseline Percy build that executes ALL tests containing Percy snapshots in a SINGLE build. This creates one baseline build with all snapshots for comparison. Use a command like: npx percy exec --
|
|
9
|
+
export declare const PERCY_SIMULATE_INSTRUCTION = "\nMANDATORY: Aggressive, Accurate Percy Snapshots Integration\n\nPrerequisite: Before making any code changes in the user's repository, create and switch to a new branch named percy-integration. Stage all changes but do not commit them. If the repository has not been initialized yet, run git init first.\n\nYou MUST follow these steps with NO EXCEPTIONS:\n1. Identify 4-5 key test files in the project that are critical for visual testing.\n2. For EACH identified test file, analyze the code to find EVERY location where a Percy snapshot should be called (e.g., after each navigation, UI change, or significant state in tests).\n3. Insert snapshot calls at ALL such points, not just the first or most obvious one.\n\nIteratively update every selected test file with percy snapshot calls at all identified points, ensuring comprehensive coverage of visual changes.";
|
|
10
|
+
export declare const PERCY_SIMULATION_DRIVER_INSTRUCTION = "\nMANDATORY: Step-by-Step Percy Visual Change Simulation\nYou MUST follow these steps with NO EXCEPTIONS:\n\nSTEP 1: At this case we have added Percy snapshot commands to the test files at the appropriate locations (e.g., after navigation, UI change, or significant state). \n- Use ONLY existing snapshot names.\n- Do NOT add any popup injection or visual changes yet.\n- Do NOT run any Percy builds at this stage.\n\nSTEP 2: Run ONE comprehensive baseline Percy build that executes ALL tests containing Percy snapshots in a SINGLE build. This creates one baseline build with all snapshots for comparison. Use a command like: npx percy exec --labels=mcp -- python -m pytest tests/ -k 'test_name1 or test_name2 or test_name3' -v to run multiple specific tests in one build.\"\n\nSTEP 3: Modify your test to inject a visible UI change (such as a popup) IMMEDIATELY BEFORE an EXISTING snapshot command (e.g., before percy_snapshot(self.driver, \"screenshot name\")).\n- Do NOT add a new snapshot name for the popup.\n- The popup must appear in an existing snapshot, not a new one.\n- Add this popup code in some test files before the percy_snapshot command you've added, to display the visual changes.\n\n```Javascript\npopup_script = `\nvar popup = document.createElement('div');\npopup.id = 'percy-test-popup';\npopup.style.cssText = popup.style.cssText = `\n /* TODO: Add styles to make the popup large, centered, and visually noticeable.\n Suggested properties: position: fixed; top/left; transform; background; color; font-size; padding; z-index; animation, etc. */\n`;\npopup.innerHTML = 'PERCY TEST<br>VISUAL CHANGE<br>DETECTED!';\ndocument.body.appendChild(popup);\n`;\n\n# Insert this just before the EXISTING snapshot command:\ndriver.execute_script(popup_script)\npercy_snapshot(self.driver, \"Before Adding to Cart\") # (Do NOT change the snapshot name, keep existing one)\n```\n\nSTEP 4: Run a second Percy build with same label and same test command as the baseline.\n- The snapshot names must remain the same as in the baseline.\n- The visual change should now appear in the same snapshot as before.\n- Use the same build command you ran for the baseline.\n\nSTEP 5: Compare the two Percy builds to see the detected visual difference.\n\nSTEP 6: Now ask user if they want to expand percy for other testcases? If yes, call the \"expandPercyVisualTesting\" tool to expand the coverage for the project.\n\nCONSTRAINTS:\n- Do NOT run any builds until explicitly instructed in the steps.\n- Do NOT add new snapshot names\u2014only use existing ones.\n- Do NOT add popup injection until the baseline is established.\n- Visual changes must appear in EXISTING snapshots, not new ones.\n\nVALIDATION CHECKPOINTS (before proceeding to the next step):\n- Are you adding only snapshot commands (not running builds)?\n- Are you reusing existing snapshot names (not creating new ones)?\n- Have you established the baseline first (before adding visual changes)\n\nCRITICAL: \nDo NOT run tests separately or create multiple builds during baseline establishment. The goal is to have exactly TWO builds total: (1) baseline build with all original snapshots, (2) modified build with the same tests but visual changes injected.\n";
|
|
11
11
|
export declare const PERCY_VERIFICATION_REGEX: RegExp;
|
|
@@ -20,7 +20,7 @@ Process files in STRICT sequential order using tool addPercySnapshotCommands() w
|
|
|
20
20
|
export const PERCY_SIMULATE_INSTRUCTION = `
|
|
21
21
|
MANDATORY: Aggressive, Accurate Percy Snapshots Integration
|
|
22
22
|
|
|
23
|
-
Prerequisite: Before making any code changes in the user's repository, create and switch to a new branch named
|
|
23
|
+
Prerequisite: Before making any code changes in the user's repository, create and switch to a new branch named percy-integration. Stage all changes but do not commit them. If the repository has not been initialized yet, run git init first.
|
|
24
24
|
|
|
25
25
|
You MUST follow these steps with NO EXCEPTIONS:
|
|
26
26
|
1. Identify 4-5 key test files in the project that are critical for visual testing.
|
|
@@ -37,7 +37,7 @@ STEP 1: At this case we have added Percy snapshot commands to the test files at
|
|
|
37
37
|
- Do NOT add any popup injection or visual changes yet.
|
|
38
38
|
- Do NOT run any Percy builds at this stage.
|
|
39
39
|
|
|
40
|
-
STEP 2: Run ONE comprehensive baseline Percy build that executes ALL tests containing Percy snapshots in a SINGLE build. This creates one baseline build with all snapshots for comparison. Use a command like: npx percy exec --
|
|
40
|
+
STEP 2: Run ONE comprehensive baseline Percy build that executes ALL tests containing Percy snapshots in a SINGLE build. This creates one baseline build with all snapshots for comparison. Use a command like: npx percy exec --labels=mcp -- python -m pytest tests/ -k 'test_name1 or test_name2 or test_name3' -v to run multiple specific tests in one build."
|
|
41
41
|
|
|
42
42
|
STEP 3: Modify your test to inject a visible UI change (such as a popup) IMMEDIATELY BEFORE an EXISTING snapshot command (e.g., before percy_snapshot(self.driver, "screenshot name")).
|
|
43
43
|
- Do NOT add a new snapshot name for the popup.
|
|
@@ -68,7 +68,7 @@ STEP 4: Run a second Percy build with same label and same test command as the ba
|
|
|
68
68
|
|
|
69
69
|
STEP 5: Compare the two Percy builds to see the detected visual difference.
|
|
70
70
|
|
|
71
|
-
STEP 6: Now ask user if they want to expand percy for other testcases? If yes, call the "expandPercyVisualTesting" tool to
|
|
71
|
+
STEP 6: Now ask user if they want to expand percy for other testcases? If yes, call the "expandPercyVisualTesting" tool to expand the coverage for the project.
|
|
72
72
|
|
|
73
73
|
CONSTRAINTS:
|
|
74
74
|
- Do NOT run any builds until explicitly instructed in the steps.
|
|
@@ -27,11 +27,25 @@ export declare const RunTestsOnBrowserStackParamsShape: {
|
|
|
27
27
|
detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
|
|
28
28
|
detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
|
|
29
29
|
detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
|
|
30
|
-
devices: z.ZodDefault<z.ZodArray<z.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
31
|
+
platform: z.ZodEnum<["windows", "macos", "android", "ios"]>;
|
|
32
|
+
deviceName: z.ZodOptional<z.ZodString>;
|
|
33
|
+
osVersion: z.ZodString;
|
|
34
|
+
browser: z.ZodOptional<z.ZodString>;
|
|
35
|
+
browserVersion: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
platform: "android" | "windows" | "macos" | "ios";
|
|
38
|
+
osVersion: string;
|
|
39
|
+
deviceName?: string | undefined;
|
|
40
|
+
browser?: string | undefined;
|
|
41
|
+
browserVersion?: string | undefined;
|
|
42
|
+
}, {
|
|
43
|
+
platform: "android" | "windows" | "macos" | "ios";
|
|
44
|
+
osVersion: string;
|
|
45
|
+
deviceName?: string | undefined;
|
|
46
|
+
browser?: string | undefined;
|
|
47
|
+
browserVersion?: string | undefined;
|
|
48
|
+
}>, "many">>;
|
|
35
49
|
};
|
|
36
50
|
export declare const SetUpPercySchema: z.ZodObject<{
|
|
37
51
|
projectName: z.ZodString;
|
|
@@ -63,23 +77,49 @@ export declare const RunTestsOnBrowserStackSchema: z.ZodObject<{
|
|
|
63
77
|
detectedLanguage: z.ZodNativeEnum<typeof SDKSupportedLanguageEnum>;
|
|
64
78
|
detectedBrowserAutomationFramework: z.ZodNativeEnum<typeof SDKSupportedBrowserAutomationFrameworkEnum>;
|
|
65
79
|
detectedTestingFramework: z.ZodNativeEnum<typeof SDKSupportedTestingFrameworkEnum>;
|
|
66
|
-
devices: z.ZodDefault<z.ZodArray<z.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
81
|
+
platform: z.ZodEnum<["windows", "macos", "android", "ios"]>;
|
|
82
|
+
deviceName: z.ZodOptional<z.ZodString>;
|
|
83
|
+
osVersion: z.ZodString;
|
|
84
|
+
browser: z.ZodOptional<z.ZodString>;
|
|
85
|
+
browserVersion: z.ZodOptional<z.ZodString>;
|
|
86
|
+
}, "strip", z.ZodTypeAny, {
|
|
87
|
+
platform: "android" | "windows" | "macos" | "ios";
|
|
88
|
+
osVersion: string;
|
|
89
|
+
deviceName?: string | undefined;
|
|
90
|
+
browser?: string | undefined;
|
|
91
|
+
browserVersion?: string | undefined;
|
|
92
|
+
}, {
|
|
93
|
+
platform: "android" | "windows" | "macos" | "ios";
|
|
94
|
+
osVersion: string;
|
|
95
|
+
deviceName?: string | undefined;
|
|
96
|
+
browser?: string | undefined;
|
|
97
|
+
browserVersion?: string | undefined;
|
|
98
|
+
}>, "many">>;
|
|
71
99
|
}, "strip", z.ZodTypeAny, {
|
|
72
100
|
projectName: string;
|
|
73
101
|
detectedLanguage: SDKSupportedLanguageEnum;
|
|
74
102
|
detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
|
|
75
103
|
detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
|
|
76
|
-
devices:
|
|
104
|
+
devices: {
|
|
105
|
+
platform: "android" | "windows" | "macos" | "ios";
|
|
106
|
+
osVersion: string;
|
|
107
|
+
deviceName?: string | undefined;
|
|
108
|
+
browser?: string | undefined;
|
|
109
|
+
browserVersion?: string | undefined;
|
|
110
|
+
}[];
|
|
77
111
|
}, {
|
|
78
112
|
projectName: string;
|
|
79
113
|
detectedLanguage: SDKSupportedLanguageEnum;
|
|
80
114
|
detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFrameworkEnum;
|
|
81
115
|
detectedTestingFramework: SDKSupportedTestingFrameworkEnum;
|
|
82
|
-
devices?:
|
|
116
|
+
devices?: {
|
|
117
|
+
platform: "android" | "windows" | "macos" | "ios";
|
|
118
|
+
osVersion: string;
|
|
119
|
+
deviceName?: string | undefined;
|
|
120
|
+
browser?: string | undefined;
|
|
121
|
+
browserVersion?: string | undefined;
|
|
122
|
+
}[] | undefined;
|
|
83
123
|
}>;
|
|
84
124
|
export type SetUpPercyInput = z.infer<typeof SetUpPercySchema>;
|
|
85
125
|
export type RunTestsOnBrowserStackInput = z.infer<typeof RunTestsOnBrowserStackSchema>;
|
|
@@ -31,6 +31,27 @@ export const SetUpPercyParamsShape = {
|
|
|
31
31
|
.optional()
|
|
32
32
|
.describe("An array of absolute file paths to specific UI test files. Use this when you want to target specific test files rather than entire folders. If not provided, will use folderPaths instead."),
|
|
33
33
|
};
|
|
34
|
+
// Device schema for BrowserStack Automate (supports desktop and mobile)
|
|
35
|
+
const DeviceSchema = z.object({
|
|
36
|
+
platform: z
|
|
37
|
+
.enum(["windows", "macos", "android", "ios"])
|
|
38
|
+
.describe("Platform name, e.g. 'windows', 'macos', 'android', 'ios'"),
|
|
39
|
+
deviceName: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Device name for mobile platforms, e.g. 'iPhone 15', 'Samsung Galaxy S24'"),
|
|
43
|
+
osVersion: z
|
|
44
|
+
.string()
|
|
45
|
+
.describe("OS version, e.g. '11', 'Sequoia', '14', '17', 'latest'"),
|
|
46
|
+
browser: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Browser name, e.g. 'chrome', 'safari', 'edge', 'firefox'"),
|
|
50
|
+
browserVersion: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Browser version for desktop platforms only (windows, macos), e.g. '132', 'latest', 'oldest'. Not used for mobile devices (android, ios)."),
|
|
54
|
+
});
|
|
34
55
|
export const RunTestsOnBrowserStackParamsShape = {
|
|
35
56
|
projectName: z
|
|
36
57
|
.string()
|
|
@@ -39,49 +60,10 @@ export const RunTestsOnBrowserStackParamsShape = {
|
|
|
39
60
|
detectedBrowserAutomationFramework: z.nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum),
|
|
40
61
|
detectedTestingFramework: z.nativeEnum(SDKSupportedTestingFrameworkEnum),
|
|
41
62
|
devices: z
|
|
42
|
-
.array(
|
|
43
|
-
// Windows: [windows, osVersion, browser, browserVersion]
|
|
44
|
-
z.tuple([
|
|
45
|
-
z
|
|
46
|
-
.nativeEnum(WindowsPlatformEnum)
|
|
47
|
-
.describe("Platform identifier: 'windows'"),
|
|
48
|
-
z.string().describe("Windows version, e.g. '10', '11'"),
|
|
49
|
-
z.string().describe("Browser name, e.g. 'chrome', 'firefox', 'edge'"),
|
|
50
|
-
z
|
|
51
|
-
.string()
|
|
52
|
-
.describe("Browser version, e.g. '132', 'latest', 'oldest'"),
|
|
53
|
-
]),
|
|
54
|
-
// Android: [android, name, model, osVersion, browser]
|
|
55
|
-
z.tuple([
|
|
56
|
-
z
|
|
57
|
-
.literal(PlatformEnum.ANDROID)
|
|
58
|
-
.describe("Platform identifier: 'android'"),
|
|
59
|
-
z
|
|
60
|
-
.string()
|
|
61
|
-
.describe("Device name, e.g. 'Samsung Galaxy S24', 'Google Pixel 8'"),
|
|
62
|
-
z.string().describe("Android version, e.g. '14', '16', 'latest'"),
|
|
63
|
-
z.string().describe("Browser name, e.g. 'chrome', 'samsung browser'"),
|
|
64
|
-
]),
|
|
65
|
-
// iOS: [ios, name, model, osVersion, browser]
|
|
66
|
-
z.tuple([
|
|
67
|
-
z.literal(PlatformEnum.IOS).describe("Platform identifier: 'ios'"),
|
|
68
|
-
z.string().describe("Device name, e.g. 'iPhone 12 Pro'"),
|
|
69
|
-
z.string().describe("iOS version, e.g. '17', 'latest'"),
|
|
70
|
-
z.string().describe("Browser name, typically 'safari'"),
|
|
71
|
-
]),
|
|
72
|
-
// macOS: [mac|macos, name, model, browser, browserVersion]
|
|
73
|
-
z.tuple([
|
|
74
|
-
z
|
|
75
|
-
.nativeEnum(MacOSPlatformEnum)
|
|
76
|
-
.describe("Platform identifier: 'mac' or 'macos'"),
|
|
77
|
-
z.string().describe("macOS version name, e.g. 'Sequoia', 'Ventura'"),
|
|
78
|
-
z.string().describe("Browser name, e.g. 'safari', 'chrome'"),
|
|
79
|
-
z.string().describe("Browser version, e.g. 'latest'"),
|
|
80
|
-
]),
|
|
81
|
-
]))
|
|
63
|
+
.array(DeviceSchema)
|
|
82
64
|
.max(3)
|
|
83
65
|
.default([])
|
|
84
|
-
.describe("
|
|
66
|
+
.describe("Device objects array. Use the object format directly - no transformation needed. Add only when user explicitly requests devices. Examples: [{ platform: 'windows', osVersion: '11', browser: 'chrome', browserVersion: 'latest' }] or [{ platform: 'android', deviceName: 'Samsung Galaxy S24', osVersion: '14', browser: 'chrome' }]."),
|
|
85
67
|
};
|
|
86
68
|
export const SetUpPercySchema = z.object(SetUpPercyParamsShape);
|
|
87
69
|
export const RunTestsOnBrowserStackSchema = z.object(RunTestsOnBrowserStackParamsShape);
|
|
@@ -2,12 +2,14 @@ import { apiClient } from "../../../lib/apiClient.js";
|
|
|
2
2
|
import { TCG_TRIGGER_URL, TCG_POLL_URL, FETCH_DETAILS_URL, FORM_FIELDS_URL, BULK_CREATE_URL, } from "./config.js";
|
|
3
3
|
import { createTestCasePayload } from "./helpers.js";
|
|
4
4
|
import { getBrowserStackAuth } from "../../../lib/get-auth.js";
|
|
5
|
+
import { getTMBaseURL } from "../../../lib/tm-base-url.js";
|
|
5
6
|
/**
|
|
6
7
|
* Fetch default and custom form fields for a project.
|
|
7
8
|
*/
|
|
8
9
|
export async function fetchFormFields(projectId, config) {
|
|
10
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
9
11
|
const res = await apiClient.get({
|
|
10
|
-
url: FORM_FIELDS_URL(projectId),
|
|
12
|
+
url: FORM_FIELDS_URL(tmBaseUrl, projectId),
|
|
11
13
|
headers: {
|
|
12
14
|
"API-TOKEN": getBrowserStackAuth(config),
|
|
13
15
|
},
|
|
@@ -18,8 +20,9 @@ export async function fetchFormFields(projectId, config) {
|
|
|
18
20
|
* Trigger AI-based test case generation for a document.
|
|
19
21
|
*/
|
|
20
22
|
export async function triggerTestCaseGeneration(document, documentId, folderId, projectId, source, config) {
|
|
23
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
21
24
|
const res = await apiClient.post({
|
|
22
|
-
url: TCG_TRIGGER_URL,
|
|
25
|
+
url: TCG_TRIGGER_URL(tmBaseUrl),
|
|
23
26
|
headers: {
|
|
24
27
|
"API-TOKEN": getBrowserStackAuth(config),
|
|
25
28
|
"Content-Type": "application/json",
|
|
@@ -31,7 +34,7 @@ export async function triggerTestCaseGeneration(document, documentId, folderId,
|
|
|
31
34
|
folderId,
|
|
32
35
|
projectId,
|
|
33
36
|
source,
|
|
34
|
-
webhookUrl:
|
|
37
|
+
webhookUrl: `${tmBaseUrl}/api/v1/projects/${projectId}/folder/${folderId}/webhooks/tcg`,
|
|
35
38
|
},
|
|
36
39
|
});
|
|
37
40
|
if (res.status !== 200) {
|
|
@@ -46,8 +49,9 @@ export async function fetchTestCaseDetails(documentId, folderId, projectId, test
|
|
|
46
49
|
if (testCaseIds.length === 0) {
|
|
47
50
|
throw new Error("No testCaseIds provided to fetchTestCaseDetails");
|
|
48
51
|
}
|
|
52
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
49
53
|
const res = await apiClient.post({
|
|
50
|
-
url: FETCH_DETAILS_URL,
|
|
54
|
+
url: FETCH_DETAILS_URL(tmBaseUrl),
|
|
51
55
|
headers: {
|
|
52
56
|
"API-TOKEN": getBrowserStackAuth(config),
|
|
53
57
|
"request-source": source,
|
|
@@ -71,11 +75,13 @@ export async function fetchTestCaseDetails(documentId, folderId, projectId, test
|
|
|
71
75
|
export async function pollTestCaseDetails(traceRequestId, config) {
|
|
72
76
|
const detailMap = {};
|
|
73
77
|
let done = false;
|
|
78
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
79
|
+
const TCG_POLL_URL_VALUE = TCG_POLL_URL(tmBaseUrl);
|
|
74
80
|
while (!done) {
|
|
75
81
|
// add a bit of jitter to avoid synchronized polling storms
|
|
76
82
|
await new Promise((r) => setTimeout(r, 10000 + Math.random() * 5000));
|
|
77
83
|
const poll = await apiClient.post({
|
|
78
|
-
url: `${
|
|
84
|
+
url: `${TCG_POLL_URL_VALUE}?x-bstack-traceRequestId=${encodeURIComponent(traceRequestId)}`,
|
|
79
85
|
headers: {
|
|
80
86
|
"API-TOKEN": getBrowserStackAuth(config),
|
|
81
87
|
},
|
|
@@ -108,12 +114,14 @@ export async function pollScenariosTestDetails(args, traceId, context, documentI
|
|
|
108
114
|
const scenariosMap = {};
|
|
109
115
|
const detailPromises = [];
|
|
110
116
|
let iteratorCount = 0;
|
|
117
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
118
|
+
const TCG_POLL_URL_VALUE = TCG_POLL_URL(tmBaseUrl);
|
|
111
119
|
// Promisify interval-style polling using a wrapper
|
|
112
120
|
await new Promise((resolve, reject) => {
|
|
113
121
|
const intervalId = setInterval(async () => {
|
|
114
122
|
try {
|
|
115
123
|
const poll = await apiClient.post({
|
|
116
|
-
url: `${
|
|
124
|
+
url: `${TCG_POLL_URL_VALUE}?x-bstack-traceRequestId=${encodeURIComponent(traceId)}`,
|
|
117
125
|
headers: {
|
|
118
126
|
"API-TOKEN": getBrowserStackAuth(config),
|
|
119
127
|
},
|
|
@@ -203,6 +211,8 @@ export async function bulkCreateTestCases(scenariosMap, projectId, folderId, fie
|
|
|
203
211
|
const total = Object.keys(scenariosMap).length;
|
|
204
212
|
let doneCount = 0;
|
|
205
213
|
let testCaseCount = 0;
|
|
214
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
215
|
+
const BULK_CREATE_URL_VALUE = BULK_CREATE_URL(tmBaseUrl, projectId, folderId);
|
|
206
216
|
for (const { id, testcases } of Object.values(scenariosMap)) {
|
|
207
217
|
const testCaseLength = testcases.length;
|
|
208
218
|
testCaseCount += testCaseLength;
|
|
@@ -213,7 +223,7 @@ export async function bulkCreateTestCases(scenariosMap, projectId, folderId, fie
|
|
|
213
223
|
};
|
|
214
224
|
try {
|
|
215
225
|
const resp = await apiClient.post({
|
|
216
|
-
url:
|
|
226
|
+
url: BULK_CREATE_URL_VALUE,
|
|
217
227
|
headers: {
|
|
218
228
|
"API-TOKEN": getBrowserStackAuth(config),
|
|
219
229
|
"Content-Type": "application/json",
|
|
@@ -251,7 +261,8 @@ export async function bulkCreateTestCases(scenariosMap, projectId, folderId, fie
|
|
|
251
261
|
return resultString;
|
|
252
262
|
}
|
|
253
263
|
export async function projectIdentifierToId(projectId, config) {
|
|
254
|
-
const
|
|
264
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
265
|
+
const url = `${tmBaseUrl}/api/v1/projects/?q=${projectId}`;
|
|
255
266
|
const response = await apiClient.get({
|
|
256
267
|
url,
|
|
257
268
|
headers: {
|
|
@@ -270,7 +281,8 @@ export async function projectIdentifierToId(projectId, config) {
|
|
|
270
281
|
throw new Error(`Project with identifier ${projectId} not found.`);
|
|
271
282
|
}
|
|
272
283
|
export async function testCaseIdentifierToDetails(projectId, testCaseIdentifier, config) {
|
|
273
|
-
const
|
|
284
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
285
|
+
const url = `${tmBaseUrl}/api/v1/projects/${projectId}/test-cases/search?q[query]=${testCaseIdentifier}`;
|
|
274
286
|
const response = await apiClient.get({
|
|
275
287
|
url,
|
|
276
288
|
headers: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const TCG_TRIGGER_URL
|
|
2
|
-
export declare const TCG_POLL_URL
|
|
3
|
-
export declare const FETCH_DETAILS_URL
|
|
4
|
-
export declare const FORM_FIELDS_URL: (projectId: string) => string;
|
|
5
|
-
export declare const BULK_CREATE_URL: (projectId: string, folderId: string) => string;
|
|
1
|
+
export declare const TCG_TRIGGER_URL: (baseUrl: string) => string;
|
|
2
|
+
export declare const TCG_POLL_URL: (baseUrl: string) => string;
|
|
3
|
+
export declare const FETCH_DETAILS_URL: (baseUrl: string) => string;
|
|
4
|
+
export declare const FORM_FIELDS_URL: (baseUrl: string, projectId: string) => string;
|
|
5
|
+
export declare const BULK_CREATE_URL: (baseUrl: string, projectId: string, folderId: string) => string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export const TCG_TRIGGER_URL =
|
|
2
|
-
export const TCG_POLL_URL =
|
|
3
|
-
export const FETCH_DETAILS_URL =
|
|
4
|
-
export const FORM_FIELDS_URL = (projectId) =>
|
|
5
|
-
export const BULK_CREATE_URL = (projectId, folderId) =>
|
|
1
|
+
export const TCG_TRIGGER_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/suggest-test-cases`;
|
|
2
|
+
export const TCG_POLL_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/test-cases-polling`;
|
|
3
|
+
export const FETCH_DETAILS_URL = (baseUrl) => `${baseUrl}/api/v1/integration/tcg/test-generation/fetch-test-case-details`;
|
|
4
|
+
export const FORM_FIELDS_URL = (baseUrl, projectId) => `${baseUrl}/api/v1/projects/${projectId}/form-fields-v2`;
|
|
5
|
+
export const BULK_CREATE_URL = (baseUrl, projectId, folderId) => `${baseUrl}/api/v1/projects/${projectId}/folder/${folderId}/bulk-test-cases`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { apiClient } from "../../lib/apiClient.js";
|
|
2
2
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
4
5
|
/**
|
|
5
6
|
* Schema for adding a test result to a test run.
|
|
6
7
|
*/
|
|
@@ -28,7 +29,8 @@ export const AddTestResultSchema = z.object({
|
|
|
28
29
|
export async function addTestResult(rawArgs, config) {
|
|
29
30
|
try {
|
|
30
31
|
const args = AddTestResultSchema.parse(rawArgs);
|
|
31
|
-
const
|
|
32
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
33
|
+
const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/results`;
|
|
32
34
|
const body = {
|
|
33
35
|
test_result: args.test_result,
|
|
34
36
|
test_case_id: args.test_case_id,
|
|
@@ -3,6 +3,7 @@ import { apiClient } from "../../lib/apiClient.js";
|
|
|
3
3
|
import { projectIdentifierToId, testCaseIdentifierToDetails, } from "./TCG-utils/api.js";
|
|
4
4
|
import { pollLCAStatus } from "./poll-lca-status.js";
|
|
5
5
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
6
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
7
|
/**
|
|
7
8
|
* Schema for creating LCA steps for a test case
|
|
8
9
|
*/
|
|
@@ -55,7 +56,8 @@ export async function createLCASteps(args, context, config) {
|
|
|
55
56
|
const projectId = await projectIdentifierToId(args.project_identifier, config);
|
|
56
57
|
// Get the test case ID and folder ID from identifier
|
|
57
58
|
const { testCaseId, folderId } = await testCaseIdentifierToDetails(projectId, args.test_case_identifier, config);
|
|
58
|
-
const
|
|
59
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
60
|
+
const url = `${tmBaseUrl}/api/v1/projects/${projectId}/test-cases/${testCaseId}/lcnc`;
|
|
59
61
|
const payload = {
|
|
60
62
|
base_url: args.base_url,
|
|
61
63
|
credentials: args.credentials,
|
|
@@ -63,7 +65,7 @@ export async function createLCASteps(args, context, config) {
|
|
|
63
65
|
test_name: args.test_name,
|
|
64
66
|
test_case_details: args.test_case_details,
|
|
65
67
|
version: "v2",
|
|
66
|
-
webhook_path:
|
|
68
|
+
webhook_path: `${tmBaseUrl}/api/v1/projects/${projectId}/test-cases/${testCaseId}/webhooks/lcnc`,
|
|
67
69
|
};
|
|
68
70
|
await apiClient.post({
|
|
69
71
|
url,
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
4
|
import { projectIdentifierToId } from "../testmanagement-utils/TCG-utils/api.js";
|
|
5
5
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
6
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
7
|
// Schema for combined project/folder creation
|
|
7
8
|
export const CreateProjFoldSchema = z.object({
|
|
8
9
|
project_name: z
|
|
@@ -43,8 +44,9 @@ export async function createProjectOrFolder(args, config) {
|
|
|
43
44
|
try {
|
|
44
45
|
const authString = getBrowserStackAuth(config);
|
|
45
46
|
const [username, password] = authString.split(":");
|
|
47
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
46
48
|
const res = await apiClient.post({
|
|
47
|
-
url:
|
|
49
|
+
url: `${tmBaseUrl}/api/v2/projects`,
|
|
48
50
|
headers: {
|
|
49
51
|
"Content-Type": "application/json",
|
|
50
52
|
Authorization: "Basic " +
|
|
@@ -69,8 +71,9 @@ export async function createProjectOrFolder(args, config) {
|
|
|
69
71
|
if (!projId)
|
|
70
72
|
throw new Error("Cannot create folder without project_identifier.");
|
|
71
73
|
try {
|
|
74
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
72
75
|
const res = await apiClient.post({
|
|
73
|
-
url:
|
|
76
|
+
url: `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(projId)}/folders`,
|
|
74
77
|
headers: {
|
|
75
78
|
"Content-Type": "application/json",
|
|
76
79
|
Authorization: "Basic " +
|
|
@@ -98,7 +101,7 @@ export async function createProjectOrFolder(args, config) {
|
|
|
98
101
|
- ID: ${folder.id}
|
|
99
102
|
- Name: ${folder.name}
|
|
100
103
|
- Project Identifier: ${projId}
|
|
101
|
-
Access it here:
|
|
104
|
+
Access it here: ${tmBaseUrl}/projects/${projectId}/folder/${folder.id}/`,
|
|
102
105
|
},
|
|
103
106
|
],
|
|
104
107
|
};
|
|
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
4
|
import { projectIdentifierToId } from "./TCG-utils/api.js";
|
|
5
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
5
6
|
export const CreateTestCaseSchema = z.object({
|
|
6
7
|
project_identifier: z
|
|
7
8
|
.string()
|
|
@@ -78,8 +79,9 @@ export async function createTestCase(params, config) {
|
|
|
78
79
|
const authString = getBrowserStackAuth(config);
|
|
79
80
|
const [username, password] = authString.split(":");
|
|
80
81
|
try {
|
|
82
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
81
83
|
const response = await apiClient.post({
|
|
82
|
-
url:
|
|
84
|
+
url: `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(params.project_identifier)}/folders/${encodeURIComponent(params.folder_id)}/test-cases`,
|
|
83
85
|
headers: {
|
|
84
86
|
"Content-Type": "application/json",
|
|
85
87
|
Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
|
|
@@ -109,7 +111,7 @@ export async function createTestCase(params, config) {
|
|
|
109
111
|
- Identifier: ${tc.identifier}
|
|
110
112
|
- Title: ${tc.title}
|
|
111
113
|
|
|
112
|
-
You can view it here:
|
|
114
|
+
You can view it here: ${tmBaseUrl}/projects/${projectId}/folder/search?q=${tc.identifier}`,
|
|
113
115
|
},
|
|
114
116
|
{
|
|
115
117
|
type: "text",
|
|
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
4
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
5
6
|
/**
|
|
6
7
|
* Schema for creating a test run.
|
|
7
8
|
*/
|
|
@@ -54,7 +55,8 @@ export async function createTestRun(rawArgs, config) {
|
|
|
54
55
|
},
|
|
55
56
|
};
|
|
56
57
|
const args = CreateTestRunSchema.parse(inputArgs);
|
|
57
|
-
const
|
|
58
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
59
|
+
const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs`;
|
|
58
60
|
const authString = getBrowserStackAuth(config);
|
|
59
61
|
const [username, password] = authString.split(":");
|
|
60
62
|
const response = await apiClient.post({
|
|
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
4
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
5
6
|
/**
|
|
6
7
|
* Schema for listing test cases with optional filters.
|
|
7
8
|
*/
|
|
@@ -38,7 +39,8 @@ export async function listTestCases(args, config) {
|
|
|
38
39
|
params.append("priority", args.priority);
|
|
39
40
|
if (args.p !== undefined)
|
|
40
41
|
params.append("p", args.p.toString());
|
|
41
|
-
const
|
|
42
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
43
|
+
const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-cases?${params.toString()}`;
|
|
42
44
|
const authString = getBrowserStackAuth(config);
|
|
43
45
|
const [username, password] = authString.split(":");
|
|
44
46
|
const resp = await apiClient.get({
|
|
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
4
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
5
6
|
/**
|
|
6
7
|
* Schema for listing test runs with optional filters.
|
|
7
8
|
*/
|
|
@@ -23,7 +24,8 @@ export async function listTestRuns(args, config) {
|
|
|
23
24
|
if (args.run_state) {
|
|
24
25
|
params.set("run_state", args.run_state);
|
|
25
26
|
}
|
|
26
|
-
const
|
|
27
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
28
|
+
const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs?` + params.toString();
|
|
27
29
|
const authString = getBrowserStackAuth(config);
|
|
28
30
|
const [username, password] = authString.split(":");
|
|
29
31
|
const resp = await apiClient.get({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apiClient } from "../../lib/apiClient.js";
|
|
2
2
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
3
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
3
4
|
/**
|
|
4
5
|
* Poll test case details to check LCA build status
|
|
5
6
|
*/
|
|
@@ -7,7 +8,8 @@ export async function pollLCAStatus(projectId, folderId, testCaseId, context, ma
|
|
|
7
8
|
initialWaitMs = 2 * 60 * 1000, // 2 minutes initial wait
|
|
8
9
|
pollIntervalMs = 10 * 1000, // 10 seconds interval
|
|
9
10
|
config) {
|
|
10
|
-
const
|
|
11
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
12
|
+
const url = `${tmBaseUrl}/api/v1/projects/${projectId}/folder/${folderId}/test-cases/${testCaseId}`;
|
|
11
13
|
const startTime = Date.now();
|
|
12
14
|
// Send initial notification that polling is starting
|
|
13
15
|
const notificationInterval = Math.min(initialWaitMs, pollIntervalMs);
|
|
@@ -3,6 +3,7 @@ import { buildDefaultFieldMaps, findBooleanFieldId, } from "./TCG-utils/helpers.
|
|
|
3
3
|
import { signedUrlMap } from "../../lib/inmemory-store.js";
|
|
4
4
|
import logger from "../../logger.js";
|
|
5
5
|
import { projectIdentifierToId } from "./TCG-utils/api.js";
|
|
6
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
7
|
export async function createTestCasesFromFile(args, context, config) {
|
|
7
8
|
logger.info(`createTestCasesFromFile called with projectId: ${args.projectReferenceId}, folderId: ${args.folderId}`);
|
|
8
9
|
if (args.projectReferenceId.startsWith("PR-")) {
|
|
@@ -31,7 +32,8 @@ export async function createTestCasesFromFile(args, context, config) {
|
|
|
31
32
|
const scenariosMap = await pollScenariosTestDetails(args, traceId, context, documentId, source, config);
|
|
32
33
|
const resultString = await bulkCreateTestCases(scenariosMap, args.projectReferenceId, args.folderId, fieldMaps, booleanFieldId, traceId, context, documentId, config);
|
|
33
34
|
signedUrlMap.delete(args.documentId);
|
|
34
|
-
const
|
|
35
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
36
|
+
const dashboardURL = `${tmBaseUrl}/projects/${args.projectReferenceId}/folder/${args.folderId}/test-cases`;
|
|
35
37
|
return {
|
|
36
38
|
content: [
|
|
37
39
|
{
|
|
@@ -2,6 +2,7 @@ import { apiClient } from "../../lib/apiClient.js";
|
|
|
2
2
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { formatAxiosError } from "../../lib/error.js";
|
|
5
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
5
6
|
/**
|
|
6
7
|
* Schema for updating a test run with partial fields.
|
|
7
8
|
*/
|
|
@@ -31,7 +32,8 @@ export const UpdateTestRunSchema = z.object({
|
|
|
31
32
|
export async function updateTestRun(args, config) {
|
|
32
33
|
try {
|
|
33
34
|
const body = { test_run: args.test_run };
|
|
34
|
-
const
|
|
35
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
36
|
+
const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(args.project_identifier)}/test-runs/${encodeURIComponent(args.test_run_id)}/update`;
|
|
35
37
|
const authString = getBrowserStackAuth(config);
|
|
36
38
|
const [username, password] = authString.split(":");
|
|
37
39
|
const resp = await apiClient.patch({
|
|
@@ -7,6 +7,7 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
7
7
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
8
8
|
import { signedUrlMap } from "../../lib/inmemory-store.js";
|
|
9
9
|
import { projectIdentifierToId } from "./TCG-utils/api.js";
|
|
10
|
+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
10
11
|
/**
|
|
11
12
|
* Schema for the upload file tool
|
|
12
13
|
*/
|
|
@@ -41,7 +42,8 @@ export async function uploadFile(args, config) {
|
|
|
41
42
|
const projectIdResponse = await projectIdentifierToId(project_identifier, config);
|
|
42
43
|
const formData = new FormData();
|
|
43
44
|
formData.append("attachments[]", fs.createReadStream(file_path));
|
|
44
|
-
const
|
|
45
|
+
const tmBaseUrl = await getTMBaseURL(config);
|
|
46
|
+
const uploadUrl = `${tmBaseUrl}/api/v1/projects/${projectIdResponse}/generic/attachments/ai_uploads`;
|
|
45
47
|
const response = await apiClient.post({
|
|
46
48
|
url: uploadUrl,
|
|
47
49
|
headers: {
|