diff --git a/.gitignore b/.gitignore index 4818878..df0e817 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ TestResult.xml bin obj .vs +log .DS_Store diff --git a/CODEOWNERS b/CODEOWNERS index 444c87b..09a587d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,3 @@ -* @browserstack/afd-dev +.github/* @browserstack/asi-devs + +* @browserstack/automate-public-repos diff --git a/CSharp-Playwright-BrowserStack.sln b/CSharp-Playwright-BrowserStack.sln index d48a267..0c7f63a 100644 --- a/CSharp-Playwright-BrowserStack.sln +++ b/CSharp-Playwright-BrowserStack.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 25.0.1706.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharp-Playwright-BrowserStack", "CSharp-Playwright-BrowserStack\CSharp-Playwright-BrowserStack.csproj", "{D309DDB3-1E3B-428B-B00B-1257F2532079}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharp-Playwright-BrowserStack", "CSharp-Playwright-BrowserStack\CSharp-Playwright-BrowserStack.csproj", "{5DC1EC78-C0C5-4869-9769-90718B820BB9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,15 +11,15 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D309DDB3-1E3B-428B-B00B-1257F2532079}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D309DDB3-1E3B-428B-B00B-1257F2532079}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D309DDB3-1E3B-428B-B00B-1257F2532079}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D309DDB3-1E3B-428B-B00B-1257F2532079}.Release|Any CPU.Build.0 = Release|Any CPU + {5DC1EC78-C0C5-4869-9769-90718B820BB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DC1EC78-C0C5-4869-9769-90718B820BB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DC1EC78-C0C5-4869-9769-90718B820BB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DC1EC78-C0C5-4869-9769-90718B820BB9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {4977FC04-06AD-47AF-90C6-66010738CC2D} + SolutionGuid = {0A720641-5ECB-4F2D-8678-EB726CF8DDA7} EndGlobalSection EndGlobal diff --git a/CSharp-Playwright-BrowserStack/BrowserStackNUnitTest.cs b/CSharp-Playwright-BrowserStack/BrowserStackNUnitTest.cs deleted file mode 100644 index 64dd56e..0000000 --- a/CSharp-Playwright-BrowserStack/BrowserStackNUnitTest.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Microsoft.Playwright; -using Newtonsoft.Json; -using NUnit.Framework; -using BrowserStack; -using Newtonsoft.Json.Linq; - -namespace CSharpPlaywrightBrowserStack -{ - [TestFixture] - public class BrowserStackNUnitTest - { - protected IBrowser browser; - protected IPage page; - protected string profile; - protected string environment; - protected string configFile; - - private Local browserStackLocal; - - public BrowserStackNUnitTest(string profile, string environment, string configFile) - { - this.profile = profile; - this.environment = environment; - this.configFile = configFile; - } - - [SetUp] - public async Task Init() - { - // Get Configuration for correct profile - string currentDirectory = Directory.GetCurrentDirectory(); - string path = Path.Combine(currentDirectory, configFile); - JObject config = JObject.Parse(File.ReadAllText(path)); - if (config is null) - throw new Exception("Configuration not found!"); - - // Get Environment specific capabilities - JObject capabilitiesJsonArr = config.GetValue("environments") as JObject; - JObject capabilities = capabilitiesJsonArr.GetValue(environment) as JObject; - - // Get Common Capabilities - JObject commonCapabilities = config.GetValue("capabilities") as JObject; - - // Merge Capabilities - capabilities.Merge(commonCapabilities); - - // Get username and accesskey - string? username = Environment.GetEnvironmentVariable("BROWSERSTACK_USERNAME"); - if (username is null) - username = config.GetValue("user").ToString(); - - string? accessKey = Environment.GetEnvironmentVariable("BROWSERSTACK_ACCESS_KEY"); - if (accessKey is null) - accessKey = config.GetValue("key").ToString(); - - capabilities["browserstack.user"] = username; - capabilities["browserstack.key"] = accessKey; - - // Start Local if browserstack.local is set to true - if (profile.Equals("local") && accessKey is not null) - { - capabilities["browserstack.local"] = true; - browserStackLocal = new Local(); - List> bsLocalArgs = new List>() { - new KeyValuePair("key", accessKey) - }; - foreach (var localOption in config.GetValue("localOptions") as JObject) - { - if (localOption.Value is not null) - { - bsLocalArgs.Add(new KeyValuePair(localOption.Key, localOption.Value.ToString())); - } - } - browserStackLocal.start(bsLocalArgs); - } - - string capsJson = JsonConvert.SerializeObject(capabilities); - string cdpUrl = "wss://cdp.browserstack.com/playwright?caps=" + Uri.EscapeDataString(capsJson); - - var playwright = await Playwright.CreateAsync(); - browser = await playwright.Chromium.ConnectAsync(cdpUrl); - page = await browser.NewPageAsync(); - } - - [TearDown] - public async Task Cleanup() - { - if (browser != null) - { - browser.CloseAsync(); - } - if (browserStackLocal != null) - { - browserStackLocal.stop(); - } - } - - public static async Task SetStatus(IPage browserPage, bool passed) - { - if (browserPage is not null) - { - if (passed) - await browserPage.EvaluateAsync("_ => {}", "browserstack_executor: {\"action\": \"setSessionStatus\", \"arguments\": {\"status\":\"passed\", \"reason\": \"Test Passed!\"}}"); - else - await browserPage.EvaluateAsync("_ => {}", "browserstack_executor: {\"action\": \"setSessionStatus\", \"arguments\": {\"status\":\"failed\", \"reason\": \"Test Failed!\"}}"); - } - } - } -} diff --git a/CSharp-Playwright-BrowserStack/CSharp-Playwright-BrowserStack.csproj b/CSharp-Playwright-BrowserStack/CSharp-Playwright-BrowserStack.csproj index 0cb3371..54ad78c 100644 --- a/CSharp-Playwright-BrowserStack/CSharp-Playwright-BrowserStack.csproj +++ b/CSharp-Playwright-BrowserStack/CSharp-Playwright-BrowserStack.csproj @@ -9,9 +9,7 @@ false - - @@ -19,21 +17,8 @@ all - - - - Always - - - Always - - - Always - - - - - - + + + diff --git a/CSharp-Playwright-BrowserStack/LocalTest.cs b/CSharp-Playwright-BrowserStack/LocalTest.cs deleted file mode 100644 index c0aab93..0000000 --- a/CSharp-Playwright-BrowserStack/LocalTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using NUnit.Framework; - -namespace CSharpPlaywrightBrowserStack -{ - [TestFixture("local", "chrome", "local.conf.json")] - [Category("sample-local-test")] - public class LocalTest : BrowserStackNUnitTest - { - public LocalTest(string profile, string environment, string configFile) : base(profile, environment, configFile) { } - - [Test] - public async Task HealthCheck() - { - try - { - // Navigate to the base url - await page.GotoAsync("http://bs-local.com:45454/"); - - // Verify if BrowserStackLocal running - var title = await page.TitleAsync(); - StringAssert.Contains("BrowserStack Local", title); - SetStatus(page, title.Contains("BrowserStack Local")); - } catch (Exception) - { - SetStatus(page, false); - throw; - } - } - } -} - diff --git a/CSharp-Playwright-BrowserStack/ParallelTest.cs b/CSharp-Playwright-BrowserStack/ParallelTest.cs deleted file mode 100644 index 2367ba0..0000000 --- a/CSharp-Playwright-BrowserStack/ParallelTest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using NUnit.Framework; - -namespace CSharpPlaywrightBrowserStack -{ - [TestFixture("parallel", "chrome", "parallel.conf.json")] - [TestFixture("parallel", "playwright-firefox", "parallel.conf.json")] - [TestFixture("parallel", "playwright-webkit", "parallel.conf.json")] - [Parallelizable(ParallelScope.Fixtures)] - [Category("sample-parallel-test")] - public class ParallelTest : SingleTest - { - public ParallelTest(string profile, string environment, string configFile) : base(profile, environment, configFile) { } - } -} diff --git a/CSharp-Playwright-BrowserStack/README.md b/CSharp-Playwright-BrowserStack/README.md deleted file mode 100644 index 141cd5e..0000000 --- a/CSharp-Playwright-BrowserStack/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# csharp-playwright-browserstack -Creating a sample repo for CSharp Playwright - -This sample elaborates the [NUnit](http://www.nunit.org/) Integration with BrowserStack. - -![BrowserStack Logo](https://d98b8t1nnulk5.cloudfront.net/production/images/layout/logo-header.png?1469004780) - - - -## Run Sample Build -* Clone the repo -* Open the solution `CSharp-Playwright-BrowserStack.sln` in Visual Studio -* Build the solution -* Update `browserstack.yml` file with your [BrowserStack Username and Access Key](https://www.browserstack.com/accounts/settings) - -### Running your tests from CLI -- To run a single test, run test with fixture `sample-test` or test class `SingleTest` -```sh -dotnet test --filter "SingleTest" -``` -- To run local tests, run test with fixture `sample-local-test` or test class `LocalTest` -```sh -dotnet test --filter "LocalTest" -``` -- To run parallel tests, run test with fixture `sample-parallel-test` or test class `ParallelTest` -```sh -dotnet test --filter "ParallelTest" -``` - - Understand how many parallel sessions you need by using our [Parallel Test Calculator](https://www.browserstack.com/automate/parallel-calculator?ref=github) - -## Notes -* You can view your test results on the [BrowserStack automate dashboard](https://www.browserstack.com/automate) -* To test on a different set of browsers, check out our [platform configurator](https://www.browserstack.com/automate/c-sharp#setting-os-and-browser) -* You can export the environment variables for the Username and Access Key of your BrowserStack account - - * For Unix-like or Mac machines: - ``` - export BROWSERSTACK_USERNAME= && - export BROWSERSTACK_ACCESS_KEY= - ``` - - * For Windows Cmd: - ``` - set BROWSERSTACK_USERNAME= - set BROWSERSTACK_ACCESS_KEY= - ``` - - * For Windows Powershell: - ``` - $env:BROWSERSTACK_USERNAME= - $env:BROWSERSTACK_ACCESS_KEY= - ``` - -## Additional Resources -* [Documentation for writing automate playwright test scripts in C#](https://www.browserstack.com/docs/automate/playwright/getting-started/c-sharp) -* [Customizing your tests on BrowserStack](https://www.browserstack.com/automate/capabilities) -* [Browsers & mobile devices for selenium testing on BrowserStack](https://www.browserstack.com/list-of-browsers-and-platforms?product=automate) -* [Using REST API to access information about your tests via the command-line interface](https://www.browserstack.com/automate/rest-api) diff --git a/CSharp-Playwright-BrowserStack/SampleLocalTest.cs b/CSharp-Playwright-BrowserStack/SampleLocalTest.cs new file mode 100644 index 0000000..d0996ae --- /dev/null +++ b/CSharp-Playwright-BrowserStack/SampleLocalTest.cs @@ -0,0 +1,24 @@ +using Microsoft.Playwright.NUnit; +using NUnit.Framework; + +namespace CSharpPlaywrightBrowserStack +{ + [TestFixture] + [Category("sample-local-test")] + public class SampleLocalTest : PageTest + { + public SampleLocalTest() : base() { } + + [Test] + public async Task BStackHealthCheck() + { + // Navigate to the base url + await Page.GotoAsync("http://bs-local.com:45454/"); + + // Verify if BrowserStackLocal running + var title = await Page.TitleAsync(); + StringAssert.Contains("BrowserStack Local", title); + } + } +} + diff --git a/CSharp-Playwright-BrowserStack/SampleTest.cs b/CSharp-Playwright-BrowserStack/SampleTest.cs new file mode 100644 index 0000000..3f6326b --- /dev/null +++ b/CSharp-Playwright-BrowserStack/SampleTest.cs @@ -0,0 +1,44 @@ +using NUnit.Framework; +using Microsoft.Playwright; +using Microsoft.Playwright.NUnit; + +namespace CSharpPlaywrightBrowserStack +{ + [TestFixture] + [Category("sample-test")] + public class SampleTest : PageTest + { + public SampleTest() : base() { } + + [Test] + public async Task SearchBstackDemo() + { + //Navigate to the bstackdemo url + _ = await Page.GotoAsync("https://bstackdemo.com/"); + + // Add the first item to cart + await Page.Locator("[id=\"\\31 \"]").GetByText("Add to Cart").ClickAsync(); + IReadOnlyList phone = await Page.Locator("[id=\"\\31 \"]").Locator(".shelf-item__title").AllInnerTextsAsync(); + Console.WriteLine("Phone =>" + phone[0]); + + + // Get the items from Cart + IReadOnlyList quantity = await Page.Locator(".bag__quantity").AllInnerTextsAsync(); + Console.WriteLine("Bag quantity =>" + quantity[0]); + + // Verify if there is a shopping cart + StringAssert.Contains("1", await Page.Locator(".bag__quantity").InnerTextAsync()); + + + //Get the handle for cart item + ILocator cartItem = Page.Locator(".shelf-item__details").Locator(".title"); + + // Verify if the cart has the right item + StringAssert.Contains(await cartItem.InnerTextAsync(), string.Join(" ", phone)); + IReadOnlyList cartItemText = await cartItem.AllInnerTextsAsync(); + Console.WriteLine("Cart item => " + cartItemText[0]); + + Assert.That(phone[0], Is.EqualTo(cartItemText[0])); + } + } +} diff --git a/CSharp-Playwright-BrowserStack/SingleTest.cs b/CSharp-Playwright-BrowserStack/SingleTest.cs deleted file mode 100644 index ea6d4bc..0000000 --- a/CSharp-Playwright-BrowserStack/SingleTest.cs +++ /dev/null @@ -1,53 +0,0 @@ -using NUnit.Framework; -using Microsoft.Playwright; - -namespace CSharpPlaywrightBrowserStack -{ - [TestFixture("single", "chrome", "single.conf.json")] - [Category("sample-test")] - public class SingleTest : BrowserStackNUnitTest - { - public SingleTest(string profile, string environment, string configFile) : base(profile, environment, configFile) { } - - [Test] - public async Task SearchBstackDemo() - { - try - { - //Navigate to the bstackdemo url - _ = await page.GotoAsync("https://bstackdemo.com/"); - - // Add the first item to cart - await page.Locator("[id=\"\\31 \"]").GetByText("Add to Cart").ClickAsync(); - IReadOnlyList phone = await page.Locator("[id=\"\\31 \"]").Locator(".shelf-item__title").AllInnerTextsAsync(); - Console.WriteLine("Phone =>" + phone[0]); - - - // Get the items from Cart - IReadOnlyList quantity = await page.Locator(".bag__quantity").AllInnerTextsAsync(); - Console.WriteLine("Bag quantity =>" + quantity[0]); - - // Verify if there is a shopping cart - StringAssert.Contains("1", await page.Locator(".bag__quantity").InnerTextAsync()); - - - //Get the handle for cart item - ILocator cartItem = page.Locator(".shelf-item__details").Locator(".title"); - - // Verify if the cart has the right item - StringAssert.Contains(await cartItem.InnerTextAsync(), string.Join(" ", phone)); - IReadOnlyList cartItemText = await cartItem.AllInnerTextsAsync(); - Console.WriteLine("Cart item => " + cartItemText[0]); - - //Assert.Equals(cartItemText[0], phone[0]); - Assert.That(phone[0], Is.EqualTo(cartItemText[0])); - - SetStatus(page, phone[0].Equals(cartItemText[0])); - } catch (Exception) - { - SetStatus(page, false); - throw; - } - } - } -} diff --git a/CSharp-Playwright-BrowserStack/browserstack.yml b/CSharp-Playwright-BrowserStack/browserstack.yml new file mode 100644 index 0000000..aaa1611 --- /dev/null +++ b/CSharp-Playwright-BrowserStack/browserstack.yml @@ -0,0 +1,65 @@ +# ============================= +# Set BrowserStack Credentials +# ============================= +# Add your BrowserStack userName and accessKey here or set BROWSERSTACK_USERNAME and +# BROWSERSTACK_ACCESS_KEY as env variables +userName: YOUR_USERNAME +accessKey: YOUR_ACCESS_KEY + +# ====================== +# BrowserStack Reporting +# ====================== +# The following capabilities are used to set up reporting on BrowserStack: +# Set 'projectName' to the name of your project. Example, Marketing Website +projectName: BrowserStack Samples +# Set `buildName` as the name of the job / testsuite being run +buildName: browserstack build +# `buildIdentifier` is a unique id to differentiate every execution that gets appended to +# buildName. Choose your buildIdentifier format from the available expressions: +# ${BUILD_NUMBER} (Default): Generates an incremental counter with every execution +# ${DATE_TIME}: Generates a Timestamp with every execution. Eg. 05-Nov-19:30 +# Read more about buildIdentifiers here -> https://www.browserstack.com/docs/automate/selenium/organize-tests +buildIdentifier: '#${BUILD_NUMBER}' # Supports strings along with either/both ${expression} + +# ======================================= +# Platforms (Browsers / Devices to test) +# ======================================= +# Platforms object contains all the browser / device combinations you want to test on. +# Entire list available here -> (https://www.browserstack.com/list-of-browsers-and-platforms/automate) +platforms: + - os: Windows + osVersion: 11 + browserName: chrome + browserVersion: latest + - os: OS X + osVersion: Ventura + browserName: playwright-webkit + browserVersion: latest + - os: Windows + osVersion: 11 + browserName: playwright-firefox + browserVersion: latest + +# ========================================== +# BrowserStack Local +# (For localhost, staging/private websites) +# ========================================== +# Set browserStackLocal to true if your website under test is not accessible publicly over the internet +# Learn more about how BrowserStack Local works here -> https://www.browserstack.com/docs/automate/selenium/local-testing-introduction +browserstackLocal: true # (Default false) +# browserStackLocalOptions: +# Options to be passed to BrowserStack local in-case of advanced configurations + # localIdentifier: # (Default: null) Needed if you need to run multiple instances of local. + # forceLocal: true # (Default: false) Set to true if you need to resolve all your traffic via BrowserStack Local tunnel. + # Entire list of arguments available here -> https://www.browserstack.com/docs/automate/selenium/manage-incoming-connections + +source: csharp-playwright-browserstack:sample-sdk:v1.0 + +# =================== +# Debugging features +# =================== +debug: false # # Set to true if you need screenshots for every selenium command ran +networkLogs: false # Set to true to enable HAR logs capturing +consoleLogs: errors # Remote browser's console debug levels to be printed (Default: errors) +# Available options are `disable`, `errors`, `warnings`, `info`, `verbose` (Default: errors) + diff --git a/CSharp-Playwright-BrowserStack/local.conf.json b/CSharp-Playwright-BrowserStack/local.conf.json deleted file mode 100644 index 9773d50..0000000 --- a/CSharp-Playwright-BrowserStack/local.conf.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "user": "BROWSERSTACK_USERNAME", - "key": "BROWSERSTACK_ACCESS_KEY", - - "capabilities": { - "build": "browserstack-build-1", - "name": "BStack Playwright local test", - "browserstack.debug": true, - "browserstack.local": true - }, - - "environments": { - "chrome": { - "browser": "chrome" - } - }, - - "localOptions": { - "forceLocal": true - } -} diff --git a/CSharp-Playwright-BrowserStack/parallel.conf.json b/CSharp-Playwright-BrowserStack/parallel.conf.json deleted file mode 100644 index b94610b..0000000 --- a/CSharp-Playwright-BrowserStack/parallel.conf.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "user": "BROWSERSTACK_USERNAME", - "key": "BROWSERSTACK_ACCESS_KEY", - - "capabilities": { - "build": "browserstack-build-1", - "name": "BStack Playwright parallel test", - "browserstack.debug": true - }, - - "environments": { - "chrome": { - "browser": "chrome" - }, - "playwright-webkit": { - "browser": "playwright-webkit" - }, - "playwright-firefox": { - "os": "OS X", - "browser": "playwright-firefox" - } - } -} diff --git a/CSharp-Playwright-BrowserStack/single.conf.json b/CSharp-Playwright-BrowserStack/single.conf.json deleted file mode 100644 index c1aea43..0000000 --- a/CSharp-Playwright-BrowserStack/single.conf.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "user": "BROWSERSTACK_USERNAME", - "key": "BROWSERSTACK_ACCESS_KEY", - - "capabilities": { - "build": "browserstack-build-1", - "name": "BStack Playwright single test", - "browserstack.debug": true - }, - - "environments": { - "chrome": { - "browser": "chrome" - } - } -} diff --git a/README.md b/README.md index d381fe3..c9c9e54 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,62 @@ # csharp-playwright-browserstack -Creating a sample repo for different Playwright languages and runners +Creating a sample repo for CSharp Playwright + +This sample elaborates the [NUnit](http://www.nunit.org/) Integration with BrowserStack. + +![BrowserStack Logo](https://d98b8t1nnulk5.cloudfront.net/production/images/layout/logo-header.png?1469004780) + + + +## Run Sample Build +* Clone the repo +* Open the solution `CSharp-Playwright-BrowserStack.sln` in Visual Studio +* Build the solution +* Update `browserstack.yml` file with your [BrowserStack Username and Access Key](https://www.browserstack.com/accounts/settings) +### Running your tests from CLI +* To run the test suite having cross-platform with parallelization, dotnet test --filter "Category=sample-test" +* To run local tests, dotnet test --filter "Category=sample-local-test" +### Running your tests from Test Explorer +- To run a parallel tests, run test with fixture `sample-test` +- To run local tests, run test with fixture `sample-local-test` + + Understand how many parallel sessions you need by using our [Parallel Test Calculator](https://www.browserstack.com/automate/parallel-calculator?ref=github) + +## Integrate your test suite + +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: + +* 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. +* Add nuget library BrowserStack.TestAdapter +```sh +dotnet add BrowserStack.TestAdapter +``` +* Build project `dotnet build` + +## Notes +* You can view your test results on the [BrowserStack automate dashboard](https://www.browserstack.com/automate) +* To test on a different set of browsers, check out our [platform configurator](https://www.browserstack.com/automate/c-sharp#setting-os-and-browser) +* You can export the environment variables for the Username and Access Key of your BrowserStack account + + * For Unix-like or Mac machines: + ``` + export BROWSERSTACK_USERNAME= && + export BROWSERSTACK_ACCESS_KEY= + ``` + + * For Windows Cmd: + ``` + set BROWSERSTACK_USERNAME= + set BROWSERSTACK_ACCESS_KEY= + ``` + + * For Windows Powershell: + ``` + $env:BROWSERSTACK_USERNAME= + $env:BROWSERSTACK_ACCESS_KEY= + ``` + +## Additional Resources +* [Documentation for writing automate playwright test scripts in C#](https://www.browserstack.com/docs/automate/playwright/getting-started/c-sharp) +* [Customizing your tests on BrowserStack](https://www.browserstack.com/automate/capabilities) +* [Browsers & mobile devices for selenium testing on BrowserStack](https://www.browserstack.com/list-of-browsers-and-platforms?product=automate) +* [Using REST API to access information about your tests via the command-line interface](https://www.browserstack.com/automate/rest-api)