diff --git a/src/tools/bstack-sdk.ts b/src/tools/bstack-sdk.ts index 17800039..d5c5b59f 100644 --- a/src/tools/bstack-sdk.ts +++ b/src/tools/bstack-sdk.ts @@ -5,12 +5,20 @@ import { SDKSupportedBrowserAutomationFramework, SDKSupportedLanguage, SDKSupportedTestingFramework, + SDKSupportedLanguageEnum, + SDKSupportedBrowserAutomationFrameworkEnum, + SDKSupportedTestingFrameworkEnum, } from "./sdk-utils/types.js"; import { generateBrowserStackYMLInstructions, getInstructionsForProjectConfiguration, } from "./sdk-utils/instructions.js"; import { trackMCP } from "../lib/instrumentation.js"; +import { + formatPercyInstructions, + getPercyInstructions, +} from "./sdk-utils/percy/instructions.js"; +import { getSDKPrefixCommand } from "./sdk-utils/commands.js"; /** * BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack. @@ -21,13 +29,56 @@ export async function bootstrapProjectWithSDK({ detectedTestingFramework, detectedLanguage, desiredPlatforms, + enablePercy, }: { detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFramework; detectedTestingFramework: SDKSupportedTestingFramework; detectedLanguage: SDKSupportedLanguage; desiredPlatforms: string[]; + enablePercy: boolean; }): Promise { - const instructions = generateBrowserStackYMLInstructions(desiredPlatforms); + // Handle frameworks with unique setup instructions that don't use browserstack.yml + if ( + detectedBrowserAutomationFramework === "cypress" || + detectedTestingFramework === "webdriverio" + ) { + let instructions = getInstructionsForProjectConfiguration( + detectedBrowserAutomationFramework, + detectedTestingFramework, + detectedLanguage, + ); + if (enablePercy) { + const percyInstructions = getPercyInstructions( + detectedLanguage, + detectedBrowserAutomationFramework, + detectedTestingFramework, + ); + if (percyInstructions) { + instructions += formatPercyInstructions(percyInstructions); + } else { + throw new Error( + `Percy is currently not supported through MCP for ${detectedLanguage} with ${detectedTestingFramework}. If you want to run the test cases without Percy, disable Percy and run it again.`, + ); + } + } + return { + content: [{ type: "text", text: instructions, isError: false }], + }; + } + + let fullInstructions = ""; + // Add language-dependent prefix command + fullInstructions += getSDKPrefixCommand( + detectedLanguage, + detectedTestingFramework, + ); + + const ymlInstructions = generateBrowserStackYMLInstructions( + desiredPlatforms, + enablePercy, + ); + fullInstructions += `${ymlInstructions}`; + const instructionsForProjectConfiguration = getInstructionsForProjectConfiguration( detectedBrowserAutomationFramework, @@ -35,11 +86,28 @@ export async function bootstrapProjectWithSDK({ detectedLanguage, ); + if (enablePercy) { + const percyInstructions = getPercyInstructions( + detectedLanguage, + detectedBrowserAutomationFramework, + detectedTestingFramework, + ); + if (percyInstructions) { + fullInstructions += formatPercyInstructions(percyInstructions); + } else { + throw new Error( + `Percy is currently not supported through MCP for ${detectedLanguage} with ${detectedTestingFramework}. If you want to run the test cases without Percy, disable Percy and run it again.`, + ); + } + } + + fullInstructions += `\n\nAfter setting up the files above, follow these final steps:\n${instructionsForProjectConfiguration}`; + return { content: [ { type: "text", - text: `${instructions}\n\n After creating the browserstack.yml file above, do the following: ${instructionsForProjectConfiguration}`, + text: fullInstructions, isError: false, }, ], @@ -49,28 +117,35 @@ export async function bootstrapProjectWithSDK({ export default function addSDKTools(server: McpServer) { server.tool( "runTestsOnBrowserStack", - "Use this tool to get instructions for running tests on BrowserStack.", + "Use this tool to get instructions for running tests on BrowserStack and browserstack percy", { detectedBrowserAutomationFramework: z - .string() + .nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum) .describe( "The automation framework configured in the project. Example: 'playwright', 'selenium'", ), detectedTestingFramework: z - .string() + .nativeEnum(SDKSupportedTestingFrameworkEnum) .describe( - "The testing framework used in the project. Example: 'jest', 'pytest'", + "The testing framework used in the project. Be precise with framework selection Example: 'webdriverio', 'jest', 'pytest', 'junit4', 'junit5', 'mocha'", ), detectedLanguage: z - .string() + .nativeEnum(SDKSupportedLanguageEnum) .describe( - "The programming language used in the project. Example: 'nodejs', 'python'", + "The programming language used in the project. Example: 'nodejs', 'python', 'java', 'csharp'", ), desiredPlatforms: z .array(z.enum(["windows", "macos", "android", "ios"])) .describe( "The platforms the user wants to test on. Always ask this to the user, do not try to infer this.", ), + enablePercy: z + .boolean() + .optional() + .default(false) + .describe( + "Set to true if the user wants to enable Percy for visual testing. Defaults to false.", + ), }, async (args) => { try { @@ -83,6 +158,7 @@ export default function addSDKTools(server: McpServer) { args.detectedTestingFramework as SDKSupportedTestingFramework, detectedLanguage: args.detectedLanguage as SDKSupportedLanguage, desiredPlatforms: args.desiredPlatforms, + enablePercy: args.enablePercy, }); } catch (error) { trackMCP( diff --git a/src/tools/sdk-utils/commands.ts b/src/tools/sdk-utils/commands.ts new file mode 100644 index 00000000..0c768c3a --- /dev/null +++ b/src/tools/sdk-utils/commands.ts @@ -0,0 +1,64 @@ +// Utility to get the language-dependent prefix command for BrowserStack SDK setup +import { SDKSupportedLanguage } from "./types.js"; + +// Framework mapping for Java Maven archetype generation +const JAVA_FRAMEWORK_MAP: Record = { + testng: "testng", + junit5: "junit5", + junit4: "junit4", + cucumber: "cucumber-testng", +}; + +// Common Gradle setup instructions (platform-independent) +const GRADLE_SETUP_INSTRUCTIONS = ` +**For Gradle setup:** +1. Add browserstack-java-sdk to dependencies: + compileOnly 'com.browserstack:browserstack-java-sdk:latest.release' + +2. Add browserstackSDK path variable: + def browserstackSDKArtifact = configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.find { it.name == 'browserstack-java-sdk' } + +3. Add javaagent to gradle tasks: + jvmArgs "-javaagent:\${browserstackSDKArtifact.file}" +`; + +export function getSDKPrefixCommand( + language: SDKSupportedLanguage, + framework: string, +): string { + switch (language) { + case "nodejs": + return `Install BrowserStack Node SDK\nusing command | npm i -D browserstack-node-sdk@latest\n| and then run following command to setup browserstack sdk:\n npx setup --username ${process.env.BROWSERSTACK_USERNAME} --key ${process.env.BROWSERSTACK_ACCESS_KEY}\n\n. This will create browserstack.yml file in the project root. Edit the file to add your desired platforms and browsers. If the file is not created :\n`; + + case "java": { + const mavenFramework = getJavaFrameworkForMaven(framework); + const isWindows = process.platform === "win32"; + + const mavenCommand = isWindows + ? `mvn archetype:generate -B -DarchetypeGroupId="com.browserstack" -DarchetypeArtifactId="browserstack-sdk-archetype-integrate" -DarchetypeVersion="1.0" -DgroupId="com.browserstack" -DartifactId="browserstack-sdk-archetype-integrate" -Dversion="1.0" -DBROWSERSTACK_USERNAME="${process.env.BROWSERSTACK_USERNAME}" -DBROWSERSTACK_ACCESS_KEY="${process.env.BROWSERSTACK_ACCESS_KEY}" -DBROWSERSTACK_FRAMEWORK="${mavenFramework}"` + : `mvn archetype:generate -B -DarchetypeGroupId=com.browserstack \\ +-DarchetypeArtifactId=browserstack-sdk-archetype-integrate -DarchetypeVersion=1.0 \\ +-DgroupId=com.browserstack -DartifactId=browserstack-sdk-archetype-integrate -Dversion=1.0 \\ +-DBROWSERSTACK_USERNAME="${process.env.BROWSERSTACK_USERNAME}" \\ +-DBROWSERSTACK_ACCESS_KEY="${process.env.BROWSERSTACK_ACCESS_KEY}" \\ +-DBROWSERSTACK_FRAMEWORK="${mavenFramework}"`; + + const platformLabel = isWindows ? "Windows" : "macOS/Linux"; + + return `Install BrowserStack Java SDK + +**Maven command for ${framework} (${platformLabel}):** +Run the command, it is required to generate the browserstack-sdk-archetype-integrate project: +${mavenCommand} +${GRADLE_SETUP_INSTRUCTIONS}`; + } + + // Add more languages as needed + default: + return ""; + } +} + +export function getJavaFrameworkForMaven(framework: string): string { + return JAVA_FRAMEWORK_MAP[framework] || framework; +} diff --git a/src/tools/sdk-utils/constants.ts b/src/tools/sdk-utils/constants.ts index 8f92251c..04b0d5c6 100644 --- a/src/tools/sdk-utils/constants.ts +++ b/src/tools/sdk-utils/constants.ts @@ -1,20 +1,9 @@ import { ConfigMapping } from "./types.js"; import config from "../../config.js"; -const nodejsInstructions = ` -- Ensure that \`browserstack-node-sdk\` is present in package.json, use the latest version. -- Add new scripts to package.json for running tests on BrowserStack (use \`npx\` to trigger the sdk): - \`\`\`json - "scripts": { - "test:browserstack": "npx browserstack-node-sdk " - } - \`\`\` -- Add to dependencies: - \`\`\`json - "browserstack-node-sdk": "latest" - \`\`\` -- Inform user to export BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY as environment variables. -`; +/** + * ---------- PYTHON INSTRUCTIONS ---------- + */ const pythonInstructions = ` Run the following command to install the browserstack-sdk: @@ -33,6 +22,31 @@ browserstack-sdk python \`\`\` `; +const generatePythonFrameworkInstructions = (framework: string) => ` +Run the following command to install the browserstack-sdk: +\`\`\`bash +python3 -m pip install browserstack-sdk +\`\`\` + +Run the following command to setup the browserstack-sdk: +\`\`\`bash +browserstack-sdk setup --framework "${framework}" --username "${config.browserstackUsername}" --key "${config.browserstackAccessKey}" +\`\`\` + +In order to run tests on BrowserStack, run the following command: +\`\`\`bash +browserstack-sdk ${framework} +\`\`\` +`; + +const robotInstructions = generatePythonFrameworkInstructions("robot"); +const behaveInstructions = generatePythonFrameworkInstructions("behave"); +const pytestInstructions = generatePythonFrameworkInstructions("pytest"); + +/** + * ---------- JAVA INSTRUCTIONS ---------- + */ + const argsInstruction = '-javaagent:"${com.browserstack:browserstack-java-sdk:jar}"'; @@ -73,30 +87,329 @@ gradle clean test \`\`\` `; -export const SUPPORTED_CONFIGURATIONS: ConfigMapping = { - nodejs: { - playwright: { - jest: { instructions: nodejsInstructions }, - codeceptjs: { instructions: nodejsInstructions }, - playwright: { instructions: nodejsInstructions }, +/** + * ---------- CSharp INSTRUCTIONS ---------- + */ + +const csharpCommonInstructions = ` +1. Install BrowserStack TestAdapter NuGet package + Add the package to your project: + \`\`\`bash + dotnet add package BrowserStack.TestAdapter + \`\`\` + +2. Build the project + \`\`\`bash + dotnet build + \`\`\` + +3. Set up BrowserStack SDK + Replace the placeholders with your actual BrowserStack credentials: + \`\`\`bash + dotnet browserstack-sdk setup --userName ${config.browserstackUsername} --accessKey ${config.browserstackAccessKey} + \`\`\` + +4. Detect if you are running on Apple Silicon (macOS only) + Run this check to determine if Apple Silicon-specific setup is required: + \`\`\`bash + ARCH="$(uname -m)" + if [ "$ARCH" = "arm64" ]; then + echo "Detected arm64 architecture - running Apple-Silicon flow" + fi + \`\`\` + +5. macOS (Apple Silicon) setup (required only if arm64 detected) + Install the x64 version of .NET for BrowserStack compatibility. + + - Check your current .NET version: + \`\`\`bash + dotnet --version + \`\`\` + + - Ensure the path exists strictly; if not, create it first and then run the setup. + \`\`\`bash + sudo dotnet browserstack-sdk setup-dotnet --dotnet-path "" --dotnet-version "" + \`\`\` + Common paths: /usr/local/share/dotnet, ~/dotnet-x64, or /opt/dotnet-x64 + +6. Run the tests + - For macOS (Apple Silicon), use the full path: + \`\`\`bash + /dotnet browserstack-sdk + \`\`\` + - For Windows, Intel Macs, or if dotnet alias is configured: + \`\`\`bash + dotnet test + \`\`\` +`; + +const csharpPlaywrightCommonInstructions = ` +1. Install BrowserStack TestAdapter NuGet package + Run the following command: + \`\`\`bash + dotnet add package BrowserStack.TestAdapter + \`\`\` + +2. Build the project + \`\`\`bash + dotnet build + \`\`\` + +3. Set up BrowserStack SDK + Replace the placeholders with your actual credentials: + \`\`\`bash + dotnet browserstack-sdk setup --userName ${config.browserstackUsername} --accessKey ${config.browserstackAccessKey} + \`\`\` + +4. Supported browsers + Use exactly one of the following (case-sensitive): + \`chrome\`, \`edge\`, \`playwright-chromium\`, \`playwright-webkit\`, \`playwright-firefox\` + +5. Detect if you are running on Apple Silicon (macOS only) + Run this check to determine if Apple Silicon-specific setup is required: + \`\`\`bash + ARCH="$(uname -m)" + if [ "$ARCH" = "arm64" ]; then + echo "Detected arm64 architecture - running Apple-Silicon flow" + fi + \`\`\` + +6. macOS (Apple Silicon) setup (required only if arm64 detected) + Install the x64 version of .NET for compatibility with BrowserStack. + + - Check your .NET version: + \`\`\`bash + dotnet --version + \`\`\` + + - Ensure the path exists strictly; if not, create it first and then run the setup. + \`\`\`bash + sudo dotnet browserstack-sdk setup-dotnet --dotnet-path "" --dotnet-version "" + \`\`\` + Common paths: /usr/local/share/dotnet, ~/dotnet-x64, or /opt/dotnet-x64 + +7. Fix for Playwright architecture (macOS only) + If the folder exists: + \`/bin/Debug/net8.0/.playwright/node/darwin-arm64\` + Rename \`darwin-arm64\` to \`darwin-x64\` + +8. Run the tests + - For macOS (Apple Silicon), use the full path: + \`\`\`bash + /dotnet browserstack-sdk + \`\`\` + - For Windows, Intel Macs, or if dotnet alias is configured: + \`\`\`bash + dotnet test + \`\`\` +`; + +/** + * ---------- NODEJS INSTRUCTIONS ---------- + */ + +const nodejsInstructions = ` +- Ensure that \`browserstack-node-sdk\` is present in package.json, use the latest version. +- Add new scripts to package.json for running tests on BrowserStack (use \`npx\` to trigger the sdk): + \`\`\`json + "scripts": { + "test:browserstack": "npx browserstack-node-sdk " + } + \`\`\` +- Add to dependencies: + \`\`\`json + "browserstack-node-sdk": "latest" + \`\`\` +- Inform user to export BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY as environment variables. +`; + +/** + * ---------- EXPORT CONFIG ---------- + */ + +const webdriverioInstructions = ` +To integrate your WebdriverIO test suite with BrowserStack, follow these steps. This process uses the @wdio/browserstack-service and does not require a browserstack.yml file. + +**1. Set BrowserStack Credentials** + +Export your BrowserStack username and access key as environment variables. + +For macOS/Linux: +\`\`\`bash +export BROWSERSTACK_USERNAME= ${config.browserstackUsername} +export BROWSERSTACK_ACCESS_KEY= ${config.browserstackAccessKey} +\`\`\` + +For Windows PowerShell: +\`\`\`powershell +$env:BROWSERSTACK_USERNAME=${config.browserstackUsername} +$env:BROWSERSTACK_ACCESS_KEY=${config.browserstackAccessKey} +\`\`\` + +**2. Install the BrowserStack WDIO Service** + +Add the service to your project's dev dependencies. +\`\`\`bash +npm install @wdio/browserstack-service --save-dev +\`\`\` + +**3. Update your WebdriverIO Config File (e.g., wdio.conf.js)** + +Modify your configuration file to use the BrowserStack service and define the platforms you want to test on. + +Here is an example configuration: + +\`\`\`javascript +exports.config = { + // Set your BrowserStack credentials + user: process.env.BROWSERSTACK_USERNAME, + key: process.env.BROWSERSTACK_ACCESS_KEY, + + // Set BrowserStack hostname + hostname: 'hub.browserstack.com', + + // Add browserstack service + services: [ + [ + 'browserstack', + { + // Set to true to test local websites + browserstackLocal: false, + // Other service options... + }, + ], + ], + + // Define platforms to test on + capabilities: [ + { + browserName: 'Chrome', + 'bstack:options': { + browserVersion: 'latest', + os: 'Windows', + osVersion: '11' + } }, - selenium: { - jest: { instructions: nodejsInstructions }, - webdriverio: { instructions: nodejsInstructions }, - mocha: { instructions: nodejsInstructions }, - cucumber: { instructions: nodejsInstructions }, - nightwatch: { instructions: nodejsInstructions }, - codeceptjs: { instructions: nodejsInstructions }, + { + browserName: 'Safari', + 'bstack:options': { + browserVersion: 'latest', + os: 'OS X', + osVersion: 'Sonoma' + } }, + ], + + // Set common capabilities for all test environments + commonCapabilities: { + 'bstack:options': { + buildName: "my-webdriverio-build", + buildIdentifier: "#\${BUILD_NUMBER}", // Example for CI + projectName: "My WebdriverIO Project", + testObservability: true, + debug: true, // Enables visual logs + networkLogs: true, // Enables network logs + consoleLogs: "info" // Sets console log level + } }, + + // The number of parallel tests running at the same time + maxInstances: 5, + + // ... other wdio configurations +}; + +// This loop merges commonCapabilities into each capability +exports.config.capabilities.forEach(function (caps) { + for (let i in exports.config.commonCapabilities) + caps[i] = { ...caps[i], ...exports.config.commonCapabilities[i]}; +}); +\`\`\` + +**4. Run your tests** + +You can now run your tests on BrowserStack using your standard WebdriverIO command. +`; + +const cypressInstructions = ` +To integrate your Cypress test suite with BrowserStack, follow these steps. This process uses the BrowserStack Cypress CLI and a \`browserstack.json\` file for configuration. + +**1. Install the BrowserStack Cypress CLI** + +Install the CLI as a dev dependency in your project. +\`\`\`bash +npm install browserstack-cypress-cli --save-dev +\`\`\` + +**2. Create the Configuration File** + +Generate the \`browserstack.json\` configuration file in your project's root directory by running the following command: +\`\`\`bash +npx browserstack-cypress init +\`\`\` + +**3. Configure \`browserstack.json\`** + +Open the generated \`browserstack.json\` file and update it with your BrowserStack credentials and desired capabilities. Below is an example configuration. + +* **auth**: Your BrowserStack username and access key. +* **browsers**: The list of browser and OS combinations you want to test on. +* **run_settings**: Project-level settings, including the path to your Cypress config file, build name, and parallels. + +\`\`\`json +{ + "auth": { + "username": "${config.browserstackUsername}", + "access_key": "${config.browserstackAccessKey}" + }, + "browsers": [ + { + "browser": "chrome", + "os": "Windows 10", + "versions": ["latest", "latest - 1"] + }, + { + "browser": "firefox", + "os": "OS X Mojave", + "versions": ["latest", "latest - 1"] + }, + { + "browser": "edge", + "os": "OS X Catalina", + "versions": ["latest"] + } + ], + "run_settings": { + "cypress_config_file": "./cypress.config.js", + "cypress_version": "12", + "project_name": "My Cypress Project", + "build_name": "Build #1", + "parallels": 5, + "testObservability": true + } +} +\`\`\` +**Note:** For Cypress v9 or lower, use \`"cypress_config_file": "./cypress.json"\`. The \`testObservability: true\` flag enables the [Test Reporting & Analytics dashboard](https://www.browserstack.com/docs/test-management/test-reporting-and-analytics) for deeper insights into your test runs. + +**4. Run Your Tests on BrowserStack** + +Execute your tests on BrowserStack using the following command: +\`\`\`bash +npx browserstack-cypress run --sync +\`\`\` + +After the tests complete, you can view the results on your [BrowserStack Automate Dashboard](https://automate.browserstack.com/dashboard/). +`; + +export const SUPPORTED_CONFIGURATIONS: ConfigMapping = { python: { playwright: { pytest: { instructions: pythonInstructions }, }, selenium: { - pytest: { instructions: pythonInstructions }, - robot: { instructions: pythonInstructions }, - behave: { instructions: pythonInstructions }, + pytest: { instructions: pytestInstructions }, + robot: { instructions: robotInstructions }, + behave: { instructions: behaveInstructions }, }, }, java: { @@ -104,7 +417,37 @@ export const SUPPORTED_CONFIGURATIONS: ConfigMapping = { selenium: { testng: { instructions: javaInstructions }, cucumber: { instructions: javaInstructions }, - junit: { instructions: javaInstructions }, + junit4: { instructions: javaInstructions }, + junit5: { instructions: javaInstructions }, + }, + }, + csharp: { + playwright: { + nunit: { instructions: csharpPlaywrightCommonInstructions }, + mstest: { instructions: csharpPlaywrightCommonInstructions }, + }, + selenium: { + xunit: { instructions: csharpCommonInstructions }, + nunit: { instructions: csharpCommonInstructions }, + mstest: { instructions: csharpCommonInstructions }, + }, + }, + nodejs: { + playwright: { + jest: { instructions: nodejsInstructions }, + codeceptjs: { instructions: nodejsInstructions }, + playwright: { instructions: nodejsInstructions }, + }, + selenium: { + jest: { instructions: nodejsInstructions }, + webdriverio: { instructions: webdriverioInstructions }, + mocha: { instructions: nodejsInstructions }, + cucumber: { instructions: nodejsInstructions }, + nightwatch: { instructions: nodejsInstructions }, + codeceptjs: { instructions: nodejsInstructions }, + }, + cypress: { + cypress: { instructions: cypressInstructions }, }, }, }; diff --git a/src/tools/sdk-utils/instructions.ts b/src/tools/sdk-utils/instructions.ts index 75e3fd54..ff9c018b 100644 --- a/src/tools/sdk-utils/instructions.ts +++ b/src/tools/sdk-utils/instructions.ts @@ -40,16 +40,16 @@ export const getInstructionsForProjectConfiguration = ( export function generateBrowserStackYMLInstructions( desiredPlatforms: string[], + enablePercy: boolean = false, ) { - return ` - Create a browserstack.yml file in the project root. The file should be in the following format: - - \`\`\`yaml + let ymlContent = ` # ====================== # BrowserStack Reporting # ====================== -projectName: BrowserStack MCP Runs -build: mcp-run +# A single name for your project to organize all your tests. This is required for Percy. +projectName: BrowserStack Sample +# A name for the group of tests you are running +buildName: mcp-run # ======================================= # Platforms (Browsers / Devices to test) @@ -74,13 +74,33 @@ platforms: # 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 parallelsPerPlatform: 1 +# ================= +# Local Testing +# ================= +# Set to true to test local browserstackLocal: true # =================== # Debugging features # =================== -debug: true -testObservability: true +debug: true # Visual logs, text logs, etc. +testObservability: true # For Test Observability`; + + if (enablePercy) { + ymlContent += ` + +# ===================== +# Percy Visual Testing +# ===================== +# Set percy to true to enable visual testing. +# Set percyCaptureMode to 'manual' to control when screenshots are taken. +percy: true +percyCaptureMode: manual`; + } + return ` + Create a browserstack.yml file in the project root. The file should be in the following format: + + \`\`\`yaml${ymlContent} \`\`\` \n`; } diff --git a/src/tools/sdk-utils/percy/constants.ts b/src/tools/sdk-utils/percy/constants.ts new file mode 100644 index 00000000..2a39b72f --- /dev/null +++ b/src/tools/sdk-utils/percy/constants.ts @@ -0,0 +1,172 @@ +import { PercyConfigMapping } from "./types.js"; + +const javaSeleniumInstructions = ` +To manually capture screenshots, implement the following steps in your test script: + +1. **Import the BrowserStack Percy SDK** in your test script. +2. Add the \`PercySDK.screenshot(driver, name)\` method at required points in your test script to capture the screenshots. + +Here's an example: + +\`\`\`java +// ...imports +import com.browserstack.PercySDK; + +public class YourTestClass extends YourBaseTest { + @Test + public void test() throws Exception { + // your test logic + // ... + + // Capture a Percy screenshot + PercySDK.screenshot(driver, "My Screenshot Name"); + + // ... + // more test logic + } +} +\`\`\` +`; + +export const nodejsSeleniumInstructions = ` +To manually capture screenshots, implement the following steps in your test script: + - Import the BrowserStack Percy SDK in your test script. + - Add the \`percy.snapshot(driver, name)\` method at required points in your test script to capture the screenshots you want. + + \`\`\`javascript + const { percy } = require('browserstack-node-sdk'); + describe("sample Test", () => { + // ... other imports and setup + + test("my test", async () => { + // .... + await percy.screenshot(driver, "My Snapshot") + // .... + }); + }) + \`\`\` +`; + +const webdriverioPercyInstructions = ` +**1. Enable Percy in \`wdio.conf.js\`** + +In your WebdriverIO configuration file, modify the 'browserstack' service options to enable Percy. + +- Set \`percy: true\`. +- Set a \`projectName\`. This is required and will be used for both your Automate and Percy projects. +- Set \`percyCaptureMode\`. The default \`auto\` mode is recommended, which captures screenshots on events like clicks. Other modes are \`testcase\`, \`click\`, \`screenshot\`, and \`manual\`. + +Here's how to modify the service configuration: +\`\`\`javascript +// in wdio.conf.js + +exports.config = { + // ... other configs + services: [ + [ + 'browserstack', + { + // ... other service options + percy: true, + percyCaptureMode: 'auto' // or 'manual', 'testcase', etc. + }, + ], + ], + + commonCapabilities: { + 'bstack:options': { + projectName: "My Percy Project", // This is required for Percy + // ... other common capabilities + } + }, + // ... rest of your config +}; +\`\`\` + +**2. Manually Capturing Screenshots (Optional)** + +If you set \`percyCaptureMode: 'manual'\` or want to take extra screenshots in \`auto\` mode, you need to add screenshot commands to your tests. + +First, install \`browserstack-node-sdk\`: +\`\`\`bash +npm install browserstack-node-sdk +\`\`\` + +Then, in your test script, import \`percy\` and use it to take a snapshot: +\`\`\`javascript +// your_test_file.js +const { percy } = require('browserstack-node-sdk'); + +describe("My WebdriverIO Test", () => { + it("should take a percy snapshot", async () => { + // ... your test logic (e.g., browser.url('https://example.com/')) + + // Capture a Percy screenshot + await percy.screenshot(driver, "My Snapshot Name"); + + // ... more test logic + }); +}); +\`\`\` +`; + +const csharpSeleniumInstructions = ` +To manually capture screenshots alongside the auto mode, implement the following steps in your test script: + +1. **Import the BrowserStack Percy SDK** in your test script. +2. Add the \`PercySDK.Screenshot(driver, name)\` method at required points in your test script to get the screenshots you want. + +Here's an example: + +\`\`\`csharp +using BrowserStackSDK.Percy; +using NUnit.Framework; + +namespace Tests; + +public class MyTest +{ + [Test] + public void SampleTest() + { + // your test logic + // ... + + // Capture a Percy screenshot + PercySDK.Screenshot(driver, "Screenshot name"); + + // ... + // more test logic + } +} +\`\`\` +`; + +export const PERCY_INSTRUCTIONS: PercyConfigMapping = { + java: { + selenium: { + testng: { script_updates: javaSeleniumInstructions }, + cucumber: { script_updates: javaSeleniumInstructions }, + junit4: { script_updates: javaSeleniumInstructions }, + junit5: { script_updates: javaSeleniumInstructions }, + }, + }, + csharp: { + selenium: { + nunit: { script_updates: csharpSeleniumInstructions }, + }, + }, + nodejs: { + selenium: { + mocha: { + script_updates: nodejsSeleniumInstructions, + }, + jest: { + script_updates: nodejsSeleniumInstructions, + }, + webdriverio: { + script_updates: webdriverioPercyInstructions, + }, + }, + }, +}; diff --git a/src/tools/sdk-utils/percy/instructions.ts b/src/tools/sdk-utils/percy/instructions.ts new file mode 100644 index 00000000..f642efa5 --- /dev/null +++ b/src/tools/sdk-utils/percy/instructions.ts @@ -0,0 +1,45 @@ +import { + SDKSupportedBrowserAutomationFramework, + SDKSupportedLanguage, + SDKSupportedTestingFramework, +} from "../types.js"; +import { PERCY_INSTRUCTIONS } from "./constants.js"; +import { PercyInstructions } from "./types.js"; + +/** + * Retrieves Percy-specific instructions for a given language and framework. + */ +export function getPercyInstructions( + language: SDKSupportedLanguage, + automationFramework: SDKSupportedBrowserAutomationFramework, + testingFramework: SDKSupportedTestingFramework, +): PercyInstructions | null { + const langConfig = PERCY_INSTRUCTIONS[language]; + if (!langConfig) { + return null; + } + + const frameworkConfig = langConfig[automationFramework]; + if (!frameworkConfig) { + return null; + } + + const percyInstructions = frameworkConfig[testingFramework]; + if (!percyInstructions) { + return null; + } + + return percyInstructions; +} + +/** + * Formats the retrieved Percy instructions into a user-friendly string. + */ +export function formatPercyInstructions( + instructions: PercyInstructions, +): string { + return `\n\n## Percy Visual Testing Setup +To enable visual testing with Percy, you need to make the following changes to your project configuration and test scripts. +${instructions.script_updates} +`; +} diff --git a/src/tools/sdk-utils/percy/types.ts b/src/tools/sdk-utils/percy/types.ts new file mode 100644 index 00000000..1ddd464f --- /dev/null +++ b/src/tools/sdk-utils/percy/types.ts @@ -0,0 +1,21 @@ +import { + SDKSupportedBrowserAutomationFramework, + SDKSupportedLanguage, + SDKSupportedTestingFramework, +} from "../types.js"; + +export interface PercyInstructions { + script_updates: string; +} + +export type PercyConfigMapping = Partial< + Record< + SDKSupportedLanguage, + Partial< + Record< + SDKSupportedBrowserAutomationFramework, + Partial> + > + > + > +>; diff --git a/src/tools/sdk-utils/types.ts b/src/tools/sdk-utils/types.ts index 4e7eeb77..3e3ed445 100644 --- a/src/tools/sdk-utils/types.ts +++ b/src/tools/sdk-utils/types.ts @@ -1,23 +1,49 @@ -export type SDKSupportedLanguage = "nodejs" | "python" | "java"; -export type SDKSupportedBrowserAutomationFramework = "playwright" | "selenium"; +export enum SDKSupportedLanguageEnum { + nodejs = "nodejs", + python = "python", + java = "java", + csharp = "csharp", +} +export type SDKSupportedLanguage = keyof typeof SDKSupportedLanguageEnum; + +export enum SDKSupportedBrowserAutomationFrameworkEnum { + playwright = "playwright", + selenium = "selenium", + cypress = "cypress", +} +export type SDKSupportedBrowserAutomationFramework = + keyof typeof SDKSupportedBrowserAutomationFrameworkEnum; + +export enum SDKSupportedTestingFrameworkEnum { + jest = "jest", + codeceptjs = "codeceptjs", + playwright = "playwright", + pytest = "pytest", + robot = "robot", + behave = "behave", + cucumber = "cucumber", + nightwatch = "nightwatch", + webdriverio = "webdriverio", + mocha = "mocha", + junit4 = "junit4", + junit5 = "junit5", + testng = "testng", + cypress = "cypress", + nunit = "nunit", + mstest = "mstest", + xunit = "xunit", +} export type SDKSupportedTestingFramework = - | "jest" - | "codeceptjs" - | "playwright" - | "pytest" - | "robot" - | "behave" - | "cucumber" - | "nightwatch" - | "webdriverio" - | "mocha" - | "junit" - | "testng"; + keyof typeof SDKSupportedTestingFrameworkEnum; export type ConfigMapping = Record< - SDKSupportedLanguage, - Record< - SDKSupportedBrowserAutomationFramework, - Partial> + SDKSupportedLanguageEnum, + Partial< + Record< + SDKSupportedBrowserAutomationFrameworkEnum, + Partial< + Record + > + > > >;