├── .gitattributes ├── .github ├── CODEOWNERS └── workflows │ ├── Semgrep.yml │ └── reviewing_changes.yml ├── .gitignore ├── SpecFlow_BrowserStack.sln ├── SpecFlow_BrowserStack ├── Properties │ └── AssemblyInfo.cs ├── SpecFlow_BrowserStack.csproj ├── browserstack.yml └── resources │ ├── features │ ├── SampleLocalTest.feature │ └── SampleTest.feature │ └── src │ ├── BrowserStackSpecFlowTest.cs │ ├── SampleLocalTest.cs │ └── SampleTest.cs └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | .github/* @browserstack/asi-devs 2 | 3 | * @browserstack/automate-public-repos 4 | -------------------------------------------------------------------------------- /.github/workflows/Semgrep.yml: -------------------------------------------------------------------------------- 1 | # Name of this GitHub Actions workflow. 2 | name: Semgrep 3 | 4 | on: 5 | # Scan changed files in PRs (diff-aware scanning): 6 | # The branches below must be a subset of the branches above 7 | pull_request: 8 | branches: ["master", "main"] 9 | push: 10 | branches: ["master", "main"] 11 | schedule: 12 | - cron: '0 6 * * *' 13 | 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | semgrep: 20 | # User definable name of this GitHub Actions job. 21 | permissions: 22 | contents: read # for actions/checkout to fetch code 23 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 24 | name: semgrep/ci 25 | # If you are self-hosting, change the following `runs-on` value: 26 | runs-on: ubuntu-latest 27 | 28 | container: 29 | # A Docker image with Semgrep installed. Do not change this. 30 | image: returntocorp/semgrep 31 | 32 | # Skip any PR created by dependabot to avoid permission issues: 33 | if: (github.actor != 'dependabot[bot]') 34 | 35 | steps: 36 | # Fetch project source with GitHub Actions Checkout. 37 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 38 | # Run the "semgrep ci" command on the command line of the docker image. 39 | - run: semgrep ci --sarif --output=semgrep.sarif 40 | env: 41 | # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable. 42 | SEMGREP_RULES: p/default # more at semgrep.dev/explore 43 | 44 | - name: Upload SARIF file for GitHub Advanced Security Dashboard 45 | uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0 46 | with: 47 | sarif_file: semgrep.sarif 48 | if: always() -------------------------------------------------------------------------------- /.github/workflows/reviewing_changes.yml: -------------------------------------------------------------------------------- 1 | # This job is to test different profiles in sdk branch against Pull Requests raised 2 | # This workflow targets specflow 3 | 4 | name: C-sharp SDK Test workflow on workflow_dispatch 5 | 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | commit_sha: 10 | description: 'The full commit id to build' 11 | required: true 12 | 13 | jobs: 14 | comment-run: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | max-parallel: 3 19 | matrix: 20 | dotnet: ['6.0.x', '5.0.x'] 21 | os: [ windows-latest ] 22 | name: SpecFlow Repo ${{ matrix.dotnet }} - ${{ matrix.os }} Sample 23 | env: 24 | BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} 25 | BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} 26 | 27 | steps: 28 | - uses: actions/checkout@v3 29 | with: 30 | ref: ${{ github.event.inputs.commit_sha }} 31 | - uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975 32 | id: status-check-in-progress 33 | env: 34 | job_name: SpecFlow Repo ${{ matrix.dotnet }} - ${{ matrix.os }} Sample 35 | commit_sha: ${{ github.event.inputs.commit_sha }} 36 | with: 37 | github-token: ${{ github.token }} 38 | script: | 39 | const result = await github.rest.checks.create({ 40 | owner: context.repo.owner, 41 | repo: context.repo.repo, 42 | name: process.env.job_name, 43 | head_sha: process.env.commit_sha, 44 | status: 'in_progress' 45 | }).catch((err) => ({status: err.status, response: err.response})); 46 | console.log(`The status-check response : ${result.status} Response : ${JSON.stringify(result.response)}`) 47 | if (result.status !== 201) { 48 | console.log('Failed to create check run') 49 | } 50 | - name: Setup dotnet 51 | uses: actions/setup-dotnet@v3 52 | with: 53 | dotnet-version: ${{ matrix.dotnet }} 54 | 55 | - name: Install dependencies 56 | run: dotnet build 57 | 58 | - name: Run sample tests 59 | run: dotnet test --filter "Category=sample-test" 60 | 61 | - name: Run local tests 62 | run: dotnet test --filter "Category=sample-local-test" 63 | 64 | - if: always() 65 | uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975 66 | id: status-check-completed 67 | env: 68 | conclusion: ${{ job.status }} 69 | job_name: SpecFlow Repo ${{ matrix.dotnet }} - ${{ matrix.os }} Sample 70 | commit_sha: ${{ github.event.inputs.commit_sha }} 71 | with: 72 | github-token: ${{ github.token }} 73 | script: | 74 | const result = await github.rest.checks.create({ 75 | owner: context.repo.owner, 76 | repo: context.repo.repo, 77 | name: process.env.job_name, 78 | head_sha: process.env.commit_sha, 79 | status: 'completed', 80 | conclusion: process.env.conclusion 81 | }).catch((err) => ({status: err.status, response: err.response})); 82 | console.log(`The status-check response : ${result.status} Response : ${JSON.stringify(result.response)}`) 83 | if (result.status !== 201) { 84 | console.log('Failed to create check run') 85 | } 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | packages/ 2 | bin 3 | obj 4 | *.user 5 | *.suo 6 | *.html 7 | *.txt 8 | *.xml 9 | *.features.cs 10 | .DS_Store 11 | *.log 12 | .vs 13 | -------------------------------------------------------------------------------- /SpecFlow_BrowserStack.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2042 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{925E0612-77F8-459D-8100-87AFA52B581A}" 7 | ProjectSection(SolutionItems) = preProject 8 | build.ps1 = build.ps1 9 | build_local.ps1 = build_local.ps1 10 | EndProjectSection 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpecFlow_BrowserStack", "SpecFlow_BrowserStack\SpecFlow_BrowserStack.csproj", "{445D58CF-ED98-4C47-9E51-7EEF066305FF}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {445D58CF-ED98-4C47-9E51-7EEF066305FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {445D58CF-ED98-4C47-9E51-7EEF066305FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {445D58CF-ED98-4C47-9E51-7EEF066305FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {445D58CF-ED98-4C47-9E51-7EEF066305FF}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {3AA72975-DF48-47E1-9152-D2087415A1B5} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SpecFlow_BrowserStack/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | // For more details on LevelOfParallelism Attribute review NUnit documentation- https://github.com/nunit/docs/wiki/LevelOfParallelism-Attribute 4 | [assembly: LevelOfParallelism(5)] 5 | [assembly: Parallelizable(ParallelScope.Fixtures)] 6 | -------------------------------------------------------------------------------- /SpecFlow_BrowserStack/SpecFlow_BrowserStack.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | false 6 | enable 7 | enable 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SpecFlow_BrowserStack/browserstack.yml: -------------------------------------------------------------------------------- 1 | # ============================= 2 | # Set BrowserStack Credentials 3 | # ============================= 4 | # Add your BrowserStack userName and acccessKey here or set BROWSERSTACK_USERNAME and 5 | # BROWSERSTACK_ACCESS_KEY as env variables 6 | 7 | userName: YOUR_USERNAME 8 | accessKey: YOUR_ACCESS_KEY 9 | 10 | # ====================== 11 | # BrowserStack Reporting 12 | # ====================== 13 | # The following capabilities are used to set up reporting on BrowserStack: 14 | # Set 'projectName' to the name of your project. Example, Marketing Website 15 | projectName: BrowserStack Samples 16 | # Set `buildName` as the name of the job / testsuite being run 17 | buildName: browserstack build 18 | # `buildIdentifier` is a unique id to differentiate every execution that gets appended to 19 | # buildName. Choose your buildIdentifier format from the available expressions: 20 | # ${BUILD_NUMBER} (Default): Generates an incremental counter with every execution 21 | # ${DATE_TIME}: Generates a Timestamp with every execution. Eg. 05-Nov-19:30 22 | # Read more about buildIdentifiers here -> https://www.browserstack.com/docs/automate/selenium/organize-tests 23 | buildIdentifier: '#${BUILD_NUMBER}' # Supports strings along with either/both ${expression} 24 | # Set `source` in the syntax `:sample-: 25 | 26 | source: specflow:sample-sdk:v1.0 27 | 28 | # ======================================= 29 | # Platforms (Browsers / Devices to test) 30 | # ======================================= 31 | # Platforms object contains all the browser / device combinations you want to test on. 32 | # Entire list available here -> (https://www.browserstack.com/list-of-browsers-and-platforms/automate) 33 | platforms: 34 | - os: OS X 35 | osVersion: Big Sur 36 | browserName: Chrome 37 | browserVersion: latest 38 | - os: Windows 39 | osVersion: 10 40 | browserName: Edge 41 | browserVersion: latest 42 | - deviceName: Samsung Galaxy S22 Ultra 43 | browserName: chrome # Try 'samsung' for Samsung browser 44 | osVersion: 12.0 45 | 46 | # ========================================== 47 | # BrowserStack Local 48 | # (For localhost, staging/private websites) 49 | # ========================================== 50 | # Set browserStackLocal to true if your website under test is not accessible publicly over the internet 51 | # Learn more about how BrowserStack Local works here -> https://www.browserstack.com/docs/automate/selenium/local-testing-introduction 52 | browserstackLocal: true # (Default false) 53 | 54 | # browserStackLocalOptions: 55 | # Options to be passed to BrowserStack local in-case of advanced configurations 56 | # localIdentifier: # (Default: null) Needed if you need to run multiple instances of local. 57 | # forceLocal: true # (Default: false) Set to true if you need to resolve all your traffic via BrowserStack Local tunnel. 58 | # Entire list of arguments availabe here -> https://www.browserstack.com/docs/automate/selenium/manage-incoming-connections 59 | 60 | # =================== 61 | # Debugging features 62 | # =================== 63 | debug: false # # Set to true if you need screenshots for every selenium command ran 64 | networkLogs: false # Set to true to enable HAR logs capturing 65 | consoleLogs: errors # Remote browser's console debug levels to be printed (Default: errors) 66 | # Available options are `disable`, `errors`, `warnings`, `info`, `verbose` (Default: errors) 67 | -------------------------------------------------------------------------------- /SpecFlow_BrowserStack/resources/features/SampleLocalTest.feature: -------------------------------------------------------------------------------- 1 | @sample-local-test 2 | Feature: BStack Local 3 | Scenario Outline: Open BrowserStack Local 4 | Given I navigate to local website 5 | Then title should contain BrowserStack Local 6 | -------------------------------------------------------------------------------- /SpecFlow_BrowserStack/resources/features/SampleTest.feature: -------------------------------------------------------------------------------- 1 | @sample-test 2 | Feature: BStack Sample 3 | Scenario Outline: Can add product to cart 4 | Given I navigate to website 5 | Then I should see title StackDemo 6 | Then I add product to cart 7 | When I check if cart is opened 8 | Then I should see same product in cart 9 | -------------------------------------------------------------------------------- /SpecFlow_BrowserStack/resources/src/BrowserStackSpecFlowTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TechTalk.SpecFlow; 3 | using log4net; 4 | using OpenQA.Selenium; 5 | using OpenQA.Selenium.Remote; 6 | using OpenQA.Selenium.Chrome; 7 | using System.Threading; 8 | 9 | namespace SpecFlowBrowserStack 10 | { 11 | [Binding] 12 | public class BrowserStackSpecFlowTest 13 | { 14 | private FeatureContext _featureContext; 15 | private ScenarioContext _scenarioContext; 16 | 17 | public static ThreadLocal ThreadLocalDriver = new ThreadLocal(); 18 | private static readonly ILog log = LogManager.GetLogger(typeof(BrowserStackSpecFlowTest)); 19 | 20 | public BrowserStackSpecFlowTest(FeatureContext featureContext, ScenarioContext scenarioContext) 21 | { 22 | _featureContext = featureContext; 23 | _scenarioContext = scenarioContext; 24 | } 25 | 26 | 27 | [BeforeScenario] 28 | public static void Initialize(ScenarioContext scenarioContext) 29 | { 30 | ChromeOptions capabilities = new ChromeOptions(); 31 | ThreadLocalDriver.Value = new RemoteWebDriver(new Uri("https://hub.browserstack.com/wd/hub/"),capabilities); 32 | } 33 | 34 | 35 | [AfterScenario] 36 | public static void TearDown(ScenarioContext scenarioContext) 37 | { 38 | Shutdown(); 39 | } 40 | 41 | protected static void Shutdown() 42 | { 43 | if (ThreadLocalDriver.IsValueCreated) 44 | { 45 | ThreadLocalDriver.Value?.Quit(); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SpecFlow_BrowserStack/resources/src/SampleLocalTest.cs: -------------------------------------------------------------------------------- 1 | using TechTalk.SpecFlow; 2 | using OpenQA.Selenium; 3 | using System; 4 | using OpenQA.Selenium.Support.UI; 5 | using NUnit.Framework; 6 | using SeleniumExtras.WaitHelpers; 7 | using System.Threading; 8 | 9 | namespace SpecFlowBrowserStack 10 | { 11 | [Binding] 12 | public class SampleLocalTest 13 | { 14 | private readonly IWebDriver _driver; 15 | 16 | public SampleLocalTest() 17 | { 18 | _driver = BrowserStackSpecFlowTest.ThreadLocalDriver.Value; 19 | new WebDriverWait(_driver, TimeSpan.FromSeconds(30)); 20 | } 21 | 22 | [Given(@"I navigate to local website")] 23 | public void GivenINavigatedTowebsite() 24 | { 25 | _driver.Navigate().GoToUrl("http://bs-local.com:45454/"); 26 | } 27 | 28 | [Then(@"title should contain (.*)")] 29 | public void ThenIShouldSeeTitle(string localString) 30 | { 31 | Thread.Sleep(5000); 32 | StringAssert.Contains(localString, _driver.Title); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SpecFlow_BrowserStack/resources/src/SampleTest.cs: -------------------------------------------------------------------------------- 1 | using TechTalk.SpecFlow; 2 | using OpenQA.Selenium; 3 | using System; 4 | using OpenQA.Selenium.Support.UI; 5 | using NUnit.Framework; 6 | using SeleniumExtras.WaitHelpers; 7 | using System.Threading; 8 | 9 | namespace SpecFlowBrowserStack 10 | { 11 | [Binding] 12 | public class SampleTest 13 | 14 | { 15 | private readonly IWebDriver _driver; 16 | private string? productOnPageText; 17 | private string? productOnCartText; 18 | private bool? cartOpened; 19 | readonly WebDriverWait wait; 20 | 21 | public SampleTest() 22 | { 23 | _driver = BrowserStackSpecFlowTest.ThreadLocalDriver.Value; 24 | wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(30)); 25 | } 26 | 27 | [Given(@"I navigate to website")] 28 | public void GivenINavigatedTowebsite() 29 | { 30 | _driver.Navigate().GoToUrl("https://bstackdemo.com"); 31 | } 32 | 33 | [Then(@"I should see title (.*)")] 34 | public void ThenIShouldSeeTitle(string title) 35 | { 36 | Thread.Sleep(5000); 37 | Assert.That(_driver.Title, Is.EqualTo(title)); 38 | } 39 | 40 | [Then(@"I add product to cart")] 41 | public void ThenIAddProductToCart() 42 | { 43 | wait.Until(ExpectedConditions.ElementExists(By.XPath("//*[@id=\"1\"]/p"))); 44 | productOnPageText = _driver.FindElement(By.XPath("//*[@id=\"1\"]/p")).Text; 45 | wait.Until(ExpectedConditions.ElementExists(By.XPath("//*[@id=\"1\"]/div[4]"))); 46 | _driver.FindElement(By.XPath("//*[@id=\"1\"]/div[4]")).Click(); 47 | } 48 | 49 | [When(@"I check if cart is opened")] 50 | public void ThenICheckIfCartIsOpened() 51 | { 52 | cartOpened = _driver.FindElement(By.XPath("//*[@class=\"float-cart__content\"]")).Displayed; 53 | Assert.That(cartOpened, Is.EqualTo(true)); 54 | } 55 | 56 | [Then(@"I should see same product in cart")] 57 | public void ThenIShouldSeeSameProductInCart() 58 | { 59 | productOnCartText = _driver.FindElement(By.XPath("//*[@id=\"__next\"]/div/div/div[2]/div[2]/div[2]/div/div[3]/p[1]")).Text; 60 | Assert.That(productOnCartText, Is.EqualTo(productOnPageText)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # specflow-browserstack 2 | [SpecFlow](http://www.specflow.org/) Integration with BrowserStack. 3 | 4 | ![BrowserStack Logo](https://d98b8t1nnulk5.cloudfront.net/production/images/layout/logo-header.png?1469004780) 5 | 6 | 7 | 8 | ## Setup 9 | * Clone the repo 10 | * Open the solution `SpecFlow_BrowserStack.sln` in Visual Studio. 11 | * Build the solution 12 | * Update `browserstack.yml` file with your [BrowserStack Username and Access Key](https://www.browserstack.com/accounts/settings) 13 | 14 | ### Running your tests from CLI 15 | * To run the test suite having cross-platform with parallelization, dotnet test --filter "Category=sample-test" 16 | * To run local tests, dotnet test --filter "Category=sample-local-test" 17 | ### Running your tests from Test Explorer 18 | - To run a parallel tests, run test with fixture `sample-test` 19 | - To run local tests, run test with fixture `sample-local-test` 20 | 21 | Understand how many parallel sessions you need by using our [Parallel Test Calculator](https://www.browserstack.com/automate/parallel-calculator?ref=github) 22 | 23 | ## Integrate your test suite 24 | 25 | This repository uses the BrowserStack SDK to run tests on BrowserStack. Follow the steps below to install the SDK in your test suite and run tests on BrowserStack: 26 | 27 | * Create sample browserstack.yml file with the browserstack related capabilities with your [BrowserStack Username and Access Key](https://www.browserstack.com/accounts/settings) and place it in your root folder. 28 | * Add nuget library BrowserStack.TestAdapter 29 | ```sh 30 | dotnet add BrowserStack.TestAdapter 31 | ``` 32 | * Build project `dotnet build` 33 | 34 | ## Notes 35 | * You can view your test results on the [BrowserStack automate dashboard](https://www.browserstack.com/automate) 36 | * To test on a different set of browsers, check out our [platform configurator](https://www.browserstack.com/automate/c-sharp#setting-os-and-browser) 37 | * You can export the environment variables for the Username and Access Key of your BrowserStack account 38 | 39 | ``` 40 | export BROWSERSTACK_USERNAME= && 41 | export BROWSERSTACK_ACCESS_KEY= 42 | ``` 43 | 44 | ## Additional Resources 45 | * [Documentation for writing automate test scripts in C#](https://www.browserstack.com/automate/c-sharp) 46 | * [Customizing your tests on BrowserStack](https://www.browserstack.com/automate/capabilities) 47 | * [Browsers & mobile devices for selenium testing on BrowserStack](https://www.browserstack.com/list-of-browsers-and-platforms?product=automate) 48 | * [Using REST API to access information about your tests via the command-line interface](https://www.browserstack.com/automate/rest-api) 49 | --------------------------------------------------------------------------------