diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18beea4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Build output +bin/ +obj/ + +# .NET +*.user +*.suo +.vs/ + +# Reqnroll / SpecFlow generated files +*.feature.cs + +# Playwright +**/bin/**/playwright.ps1 +.playwright/ + +# BrowserStack +log/ +local.log +.browserstack/ + +# Environment +.env + +# OS +.DS_Store diff --git a/README.md b/README.md index 5025f0c..1879162 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,63 @@ -# nunit-csharp-reqnroll-playwright-browserstack -Sample repo for customers +# Reqnroll + NUnit + Playwright with BrowserStack + +Sample repository demonstrating how to run C# Reqnroll (NUnit runner) + Playwright tests on +BrowserStack Automate using the [BrowserStack C# SDK](https://www.browserstack.com/docs/automate/selenium/sdk-overview). + +## Prerequisites + +- [.NET SDK](https://dotnet.microsoft.com/download) 6.0 or higher (8.0 recommended) +- A [BrowserStack account](https://www.browserstack.com/users/sign_up) (username + access key) +- Git + +## Setup + +1. Clone this repository and switch to the project directory: + + ```bash + git clone https://github.com/browserstack/nunit-csharp-reqnroll-playwright-browserstack.git + cd nunit-csharp-reqnroll-playwright-browserstack/Reqnroll-Playwright-BrowserStack + ``` + +2. Restore the BrowserStack SDK tool and project dependencies: + + ```bash + dotnet tool restore + dotnet restore + ``` + +3. Set your BrowserStack credentials as environment variables: + + ```bash + export BROWSERSTACK_USERNAME="YOUR_USERNAME" + export BROWSERSTACK_ACCESS_KEY="YOUR_ACCESS_KEY" + ``` + + Alternatively, edit `browserstack.yml` and set the `userName` and `accessKey` values directly. + +## Run Sample Test + +Runs the BStack Demo "add to cart" scenario against the platforms configured in `browserstack.yml`: + +```bash +dotnet test +``` + +To run only the sample (non-local) scenario, filter by its tag: + +```bash +dotnet test --filter "TestCategory=sample-test" +``` + +## Run Local Test + +Runs the BrowserStack Local scenario (`browserstackLocal: true` is already enabled in `browserstack.yml`): + +```bash +dotnet test --filter "TestCategory=sample-local-test" +``` + +## Notes + +- View your test results on the [BrowserStack Automate dashboard](https://automate.browserstack.com/). +- The framework is detected as `nunit` because Reqnroll runs on the NUnit test runner. +- Test Observability is enabled by default (`testObservability: true` in `browserstack.yml`). diff --git a/Reqnroll-Playwright-BrowserStack.sln b/Reqnroll-Playwright-BrowserStack.sln new file mode 100644 index 0000000..cae3efa --- /dev/null +++ b/Reqnroll-Playwright-BrowserStack.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reqnroll-Playwright-BrowserStack", "Reqnroll-Playwright-BrowserStack\Reqnroll-Playwright-BrowserStack.csproj", "{C3215C4E-FED4-47A9-9808-7B561D6A9F0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C3215C4E-FED4-47A9-9808-7B561D6A9F0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3215C4E-FED4-47A9-9808-7B561D6A9F0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3215C4E-FED4-47A9-9808-7B561D6A9F0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3215C4E-FED4-47A9-9808-7B561D6A9F0B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Reqnroll-Playwright-BrowserStack/.config/dotnet-tools.json b/Reqnroll-Playwright-BrowserStack/.config/dotnet-tools.json new file mode 100644 index 0000000..9800c45 --- /dev/null +++ b/Reqnroll-Playwright-BrowserStack/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "browserstack-sdk": { + "version": "0.0.0", + "commands": [ + "browserstack-sdk" + ] + } + } +} diff --git a/Reqnroll-Playwright-BrowserStack/Features/SampleLocalTest.feature b/Reqnroll-Playwright-BrowserStack/Features/SampleLocalTest.feature new file mode 100644 index 0000000..d982da0 --- /dev/null +++ b/Reqnroll-Playwright-BrowserStack/Features/SampleLocalTest.feature @@ -0,0 +1,5 @@ +@sample-local-test +Feature: BStack Local + Scenario: Open BrowserStack Local + Given I navigate to the local website + Then the page title should be "BrowserStack Local" diff --git a/Reqnroll-Playwright-BrowserStack/Features/SampleTest.feature b/Reqnroll-Playwright-BrowserStack/Features/SampleTest.feature new file mode 100644 index 0000000..641083a --- /dev/null +++ b/Reqnroll-Playwright-BrowserStack/Features/SampleTest.feature @@ -0,0 +1,7 @@ +@sample-test +Feature: BStack Sample + Scenario: Can add product to cart + Given I navigate to the StackDemo website + When I add the first product to the cart + And the cart pane is opened + Then the product in the cart matches the product on the page diff --git a/Reqnroll-Playwright-BrowserStack/Reqnroll-Playwright-BrowserStack.csproj b/Reqnroll-Playwright-BrowserStack/Reqnroll-Playwright-BrowserStack.csproj new file mode 100644 index 0000000..c39f0eb --- /dev/null +++ b/Reqnroll-Playwright-BrowserStack/Reqnroll-Playwright-BrowserStack.csproj @@ -0,0 +1,25 @@ + + + + net6.0;net8.0 + ReqnrollPlaywrightBrowserStack + ReqnrollPlaywrightBrowserStack + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/Reqnroll-Playwright-BrowserStack/StepDefinitions/PlaywrightHooks.cs b/Reqnroll-Playwright-BrowserStack/StepDefinitions/PlaywrightHooks.cs new file mode 100644 index 0000000..e232519 --- /dev/null +++ b/Reqnroll-Playwright-BrowserStack/StepDefinitions/PlaywrightHooks.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Reqnroll; + +namespace ReqnrollPlaywrightBrowserStack +{ + // Manages the Playwright browser/page lifecycle for each scenario. + // The BrowserStack SDK intercepts the Playwright connection and routes it + // to the BrowserStack cloud using the platforms defined in browserstack.yml. + [Binding] + public class PlaywrightHooks + { + private readonly ScenarioContext _scenarioContext; + + private IPlaywright? _playwright; + private IBrowser? _browser; + private IBrowserContext? _context; + private IPage? _page; + + public PlaywrightHooks(ScenarioContext scenarioContext) + { + _scenarioContext = scenarioContext; + } + + [BeforeScenario] + public async Task InitializeAsync() + { + _playwright = await Playwright.CreateAsync(); + _browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions + { + Headless = true + }); + _context = await _browser.NewContextAsync(); + _page = await _context.NewPageAsync(); + + // Expose the page to the step definitions via the scenario container. + _scenarioContext.ScenarioContainer.RegisterInstanceAs(_page); + } + + [AfterScenario] + public async Task TearDownAsync() + { + if (_page != null) + { + await _page.CloseAsync(); + } + if (_context != null) + { + await _context.CloseAsync(); + } + if (_browser != null) + { + await _browser.CloseAsync(); + } + _playwright?.Dispose(); + } + } +} diff --git a/Reqnroll-Playwright-BrowserStack/StepDefinitions/SampleLocalTestSteps.cs b/Reqnroll-Playwright-BrowserStack/StepDefinitions/SampleLocalTestSteps.cs new file mode 100644 index 0000000..0bfe8bb --- /dev/null +++ b/Reqnroll-Playwright-BrowserStack/StepDefinitions/SampleLocalTestSteps.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using Microsoft.Playwright; +using NUnit.Framework; +using Reqnroll; + +namespace ReqnrollPlaywrightBrowserStack +{ + [Binding] + public class SampleLocalTestSteps + { + private readonly IPage _page; + + public SampleLocalTestSteps(ScenarioContext scenarioContext) + { + _page = scenarioContext.ScenarioContainer.Resolve(); + } + + [Given(@"I navigate to the local website")] + public async Task GivenINavigateToTheLocalWebsite() + { + await _page.GotoAsync("http://bs-local.com:45454"); + } + + [Then(@"the page title should be ""(.*)""")] + public async Task ThenThePageTitleShouldBe(string expectedTitle) + { + string title = await _page.TitleAsync(); + Assert.That(title, Is.EqualTo(expectedTitle)); + } + } +} diff --git a/Reqnroll-Playwright-BrowserStack/StepDefinitions/SampleTestSteps.cs b/Reqnroll-Playwright-BrowserStack/StepDefinitions/SampleTestSteps.cs new file mode 100644 index 0000000..d7428f8 --- /dev/null +++ b/Reqnroll-Playwright-BrowserStack/StepDefinitions/SampleTestSteps.cs @@ -0,0 +1,47 @@ +using System.Threading.Tasks; +using Microsoft.Playwright; +using NUnit.Framework; +using Reqnroll; + +namespace ReqnrollPlaywrightBrowserStack +{ + [Binding] + public class SampleTestSteps + { + private readonly IPage _page; + private string? _productOnPageText; + + public SampleTestSteps(ScenarioContext scenarioContext) + { + _page = scenarioContext.ScenarioContainer.Resolve(); + } + + [Given(@"I navigate to the StackDemo website")] + public async Task GivenINavigateToTheStackDemoWebsite() + { + await _page.GotoAsync("https://bstackdemo.com/"); + } + + [When(@"I add the first product to the cart")] + public async Task WhenIAddTheFirstProductToTheCart() + { + _productOnPageText = await _page.Locator("//*[@id=\"1\"]/p").InnerTextAsync(); + await _page.Locator("//*[@id=\"1\"]/div[4]").ClickAsync(); + } + + [When(@"the cart pane is opened")] + public async Task WhenTheCartPaneIsOpened() + { + await _page.Locator(".float-cart__content").WaitForAsync(); + } + + [Then(@"the product in the cart matches the product on the page")] + public async Task ThenTheProductInTheCartMatchesTheProductOnThePage() + { + string productInCartText = await _page + .Locator("//*[@id=\"__next\"]/div/div/div[2]/div[2]/div[2]/div/div[3]/p[1]") + .InnerTextAsync(); + Assert.That(productInCartText, Is.EqualTo(_productOnPageText)); + } + } +} diff --git a/Reqnroll-Playwright-BrowserStack/browserstack.yml b/Reqnroll-Playwright-BrowserStack/browserstack.yml new file mode 100644 index 0000000..4b4e564 --- /dev/null +++ b/Reqnroll-Playwright-BrowserStack/browserstack.yml @@ -0,0 +1,26 @@ +userName: YOUR_USERNAME +accessKey: YOUR_ACCESS_KEY +projectName: BrowserStack Samples +buildName: browserstack build +buildIdentifier: '#${BUILD_NUMBER}' +framework: nunit +platforms: + - os: OS X + osVersion: Big Sur + browserName: Chrome + browserVersion: latest + - os: Windows + osVersion: 10 + browserName: Edge + browserVersion: latest + - deviceName: Samsung Galaxy S22 Ultra + browserName: chrome + osVersion: 12.0 +parallelsPerPlatform: 1 +browserstackAutomation: true +browserstackLocal: true +source: nunit-csharp-reqnroll-playwright-browserstack:sample-sdk:v1.0 +testObservability: true +debug: false +networkLogs: false +consoleLogs: errors