From 48816f92eb985b8e8a2871e0a63d7626f6fe8db6 Mon Sep 17 00:00:00 2001 From: Vishant Kumar Date: Wed, 29 Apr 2026 17:31:31 +0530 Subject: [PATCH 1/4] Add serenity-cucumber sample folder Adds a Serenity-Cucumber BDD variant alongside the existing java/junit-4/junit-5/testng folders. Mirrors the junit-5 structure with two scenarios (add-to-cart, checkout) on bstackdemo.com expressed as Gherkin features and run via the JUnit 5 Platform Suite + Cucumber JUnit Platform Engine. Uses RemoteWebDriver against a local Selenium Grid hub at localhost:4444/wd/hub, matching the rest of the repo. --- serenity-cucumber/README.md | 54 ++++++++ serenity-cucumber/browserstack-load.yml | 44 +++++++ serenity-cucumber/pom.xml | 106 ++++++++++++++++ .../java/com/example/RunCucumberTest.java | 24 ++++ .../src/test/java/com/example/StepDefs.java | 119 ++++++++++++++++++ .../resources/features/bstackdemo.feature | 16 +++ 6 files changed, 363 insertions(+) create mode 100644 serenity-cucumber/README.md create mode 100644 serenity-cucumber/browserstack-load.yml create mode 100644 serenity-cucumber/pom.xml create mode 100644 serenity-cucumber/src/test/java/com/example/RunCucumberTest.java create mode 100644 serenity-cucumber/src/test/java/com/example/StepDefs.java create mode 100644 serenity-cucumber/src/test/resources/features/bstackdemo.feature diff --git a/serenity-cucumber/README.md b/serenity-cucumber/README.md new file mode 100644 index 0000000..fdcf8fe --- /dev/null +++ b/serenity-cucumber/README.md @@ -0,0 +1,54 @@ +# browserstack-selenium-load-testing-sample + +![BrowserStack Logo](https://d98b8t1nnulk5.cloudfront.net/production/images/layout/logo-header.png?1469004780) + +## Getting Started + +### Run Sample Build + +1. **Clone the repository** + + ```sh + git clone https://github.com/browserstack/browserstack-selenium-load-testing-sample.git + cd browserstack-selenium-load-testing-sample + cd serenity-cucumber + ``` + +2. **Install Maven dependencies** + + ```sh + mvn compile + ``` + +3. **Install BrowserStack CLI** + + Download the appropriate BrowserStack CLI binary based on your operating system: + + - **macOS x86** + [browserstack-cli-macOS-x86](https://load-api.browserstack.com/api/v1/binary?os=macos&arch=x64) + + - **macOS ARM** + [browserstack-cli-macOS-arm](https://load-api.browserstack.com/api/v1/binary?os=macos&arch=arm64) + + - **Windows x86** + [browserstack-cli-windows](https://load-api.browserstack.com/api/v1/binary?os=win&arch=x64) + + - **Linux x86** + [browserstack-cli-linux-x86](https://load-api.browserstack.com/api/v1/binary?os=linux&arch=arm64) + + - **Linux ARM** + [browserstack-cli-linux-arm](https://load-api.browserstack.com/api/v1/binary?os=linux&arch=x64) + + > Place the downloaded `browserstack-cli` binary in the root of your project. + +4. **Run tests using BrowserStack CLI** + + ```sh + ./browserstack-cli load run + ``` + +5. **View Test Results** + + Visit the [BrowserStack Load-Testing Dashboard](https://load.browserstack.com/projects) to monitor and analyze your test runs. + +--- diff --git a/serenity-cucumber/browserstack-load.yml b/serenity-cucumber/browserstack-load.yml new file mode 100644 index 0000000..6810ab2 --- /dev/null +++ b/serenity-cucumber/browserstack-load.yml @@ -0,0 +1,44 @@ +# ============================= +# Set BrowserStack Credentials +# ============================= +# Add your BrowserStack userName and accessKey here or set BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY as env variables. +userName: BROWSERSTACK_USERNAME +accessKey: BROWSERSTACK_ACCESS_KEY + +# ====================== +# BrowserStack Reporting +# ====================== +# The following parameters are used to set up reporting on BrowserStack Load Testing: +# Set 'projectName' to the name of your project. Example: 'Product ABC'. Tests under the same projectName will be grouped together. +projectName: Default Project + +# Set 'testName' to the name of your test. Example: 'First Load Test'. Test runs with the same testName will be grouped together. +testName: Default Test + +# ====================== +# Set Load Configuration +# ====================== +# The following parameters are used to set load configuration for your test: +# Set 'testType' to the type of load test that you want to execute. Example:'Playwright', 'Selenium'. This is a required parameter. +testType: Selenium + +# Set 'vus' to the maximum number of virtual users to simulate during the test. +vus: 1 + +# Set multiple regions from which you would want to generate the load (percent should total 100 across all loadzones). +regions: + - loadzone: us-east-1 + percent: 100 + +# Set language to the programming language used in your project. Example: 'java', 'nodejs'. +language: java + +# Set framework to the test framework used in your Selenium project. +framework: serenity-cucumber + +# Add list of file paths under 'dependencies' to help set up the test environment by installing required packages. Example: path to 'pom.xml' for Java projects using Maven, path to 'package.json' for Node.js projects. +# Add list of file paths under 'testConfigs' to define which configuration files should be used to run tests. Example: path to 'playwright.config.ts' for Playwright (Node.js), path to 'testng.xml' for Selenium (TestNG). +files: + dependencies: + - ./pom.xml + testConfigs: [] diff --git a/serenity-cucumber/pom.xml b/serenity-cucumber/pom.xml new file mode 100644 index 0000000..bfbaadb --- /dev/null +++ b/serenity-cucumber/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + com.example + selenium-serenity-cucumber-example + 1.0-SNAPSHOT + jar + + + 17 + 17 + UTF-8 + 4.15.0 + 4.2.16 + 7.20.1 + 5.10.1 + 1.10.1 + + + + + + org.seleniumhq.selenium + selenium-java + ${selenium.version} + + + + + net.serenity-bdd + serenity-core + ${serenity.version} + + + + + net.serenity-bdd + serenity-cucumber + ${serenity.version} + + + + + net.serenity-bdd + serenity-junit5 + ${serenity.version} + + + + + io.cucumber + cucumber-java + ${cucumber.version} + + + + + io.cucumber + cucumber-junit-platform-engine + ${cucumber.version} + + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + + + org.junit.platform + junit-platform-suite + ${junit.platform.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + **/RunCucumberTest.java + + false + + + + + diff --git a/serenity-cucumber/src/test/java/com/example/RunCucumberTest.java b/serenity-cucumber/src/test/java/com/example/RunCucumberTest.java new file mode 100644 index 0000000..ad6a814 --- /dev/null +++ b/serenity-cucumber/src/test/java/com/example/RunCucumberTest.java @@ -0,0 +1,24 @@ +package com.example; + +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.Suite; + +import static io.cucumber.core.options.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.core.options.Constants.PLUGIN_PROPERTY_NAME; + +/** + * JUnit 5 Platform Suite that runs all Cucumber feature files. + * Maven Surefire picks this class up via mvn test and executes the scenarios. + */ +@Suite +@IncludeEngines("cucumber") +@SelectClasspathResource("features") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example") +@ConfigurationParameter( + key = PLUGIN_PROPERTY_NAME, + value = "pretty, summary" +) +public class RunCucumberTest { +} diff --git a/serenity-cucumber/src/test/java/com/example/StepDefs.java b/serenity-cucumber/src/test/java/com/example/StepDefs.java new file mode 100644 index 0000000..b93f0bf --- /dev/null +++ b/serenity-cucumber/src/test/java/com/example/StepDefs.java @@ -0,0 +1,119 @@ +package com.example; + +import io.cucumber.java.After; +import io.cucumber.java.Before; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.remote.RemoteWebDriver; + +import java.net.URL; +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StepDefs { + + private WebDriver driver; + private String addedProductName; + private static final String HUB_URL = "http://localhost:4444/wd/hub"; + + @Before + public void setUp() throws Exception { + ChromeOptions chromeOptions = new ChromeOptions(); + chromeOptions.addArguments( + "--headless", + "--no-first-run", + "--no-default-browser-check", + "--disable-extensions", + "--disable-default-apps", + "--disable-gpu", + "--disable-dev-shm-usage", + "--disable-software-rasterizer", + "--no-sandbox", + "--disable-background-timer-throttling", + "--disable-backgrounding-occluded-windows", + "--disable-renderer-backgrounding", + "--disable-features=TranslateUI", + "--disable-ipc-flooding-protection", + "--disable-web-security", + "--disable-features=VizDisplayCompositor", + "--disable-logging", + "--silent" + ); + + driver = new RemoteWebDriver(new URL(HUB_URL), chromeOptions); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); + driver.manage().window().maximize(); + } + + @After + public void tearDown() { + if (driver != null) { + driver.quit(); + } + } + + @Given("I open the BrowserStack demo store") + public void iOpenTheBrowserStackDemoStore() { + driver.get("https://bstackdemo.com/"); + } + + @And("I sign in to BrowserStack demo") + public void iSignInToBrowserStackDemo() throws InterruptedException { + driver.findElement(By.id("signin")).click(); + driver.findElement(By.cssSelector("#username svg")).click(); + driver.findElement(By.id("react-select-2-option-0-0")).click(); + driver.findElement(By.cssSelector("#password svg")).click(); + driver.findElement(By.id("react-select-3-option-0-0")).click(); + driver.findElement(By.id("login-btn")).click(); + Thread.sleep(500); + } + + @When("I add the product at index {string} to my cart") + public void iAddTheProductAtIndexToMyCart(String index) { + WebElement productNameElem = driver.findElement(By.cssSelector("#\\3" + index + " > p")); + addedProductName = productNameElem.getText(); + WebElement addToCartBtn = driver.findElement(By.cssSelector("#\\3" + index + " > .shelf-item__buy-btn")); + addToCartBtn.click(); + } + + @Then("the same product should appear in my cart") + public void theSameProductShouldAppearInMyCart() { + WebElement productInCartElem = driver.findElement(By.cssSelector( + "#__next > div > div > div.float-cart.float-cart--open > div.float-cart__content > div.float-cart__shelf-container > div > div.shelf-item__details > p.title" + )); + assertEquals(addedProductName, productInCartElem.getText()); + } + + @And("I close the cart panel") + public void iCloseTheCartPanel() { + driver.findElement(By.cssSelector("div.float-cart__close-btn")).click(); + } + + @And("I proceed to checkout") + public void iProceedToCheckout() { + driver.findElement(By.cssSelector(".buy-btn")).click(); + } + + @And("I fill in the shipping address") + public void iFillInTheShippingAddress() { + driver.findElement(By.id("firstNameInput")).sendKeys("first"); + driver.findElement(By.id("lastNameInput")).sendKeys("last"); + driver.findElement(By.id("addressLine1Input")).sendKeys("address"); + driver.findElement(By.id("provinceInput")).sendKeys("province"); + driver.findElement(By.id("postCodeInput")).sendKeys("pincode"); + driver.findElement(By.id("checkout-shipping-continue")).click(); + } + + @Then("I should see the confirmation message {string}") + public void iShouldSeeTheConfirmationMessage(String expected) { + String message = driver.findElement(By.id("confirmation-message")).getText(); + assertEquals(expected, message); + } +} diff --git a/serenity-cucumber/src/test/resources/features/bstackdemo.feature b/serenity-cucumber/src/test/resources/features/bstackdemo.feature new file mode 100644 index 0000000..6d41a45 --- /dev/null +++ b/serenity-cucumber/src/test/resources/features/bstackdemo.feature @@ -0,0 +1,16 @@ +Feature: BrowserStack Demo cart and checkout + + Scenario: Add a product to the cart + Given I open the BrowserStack demo store + When I add the product at index "3" to my cart + Then the same product should appear in my cart + + Scenario: Complete a checkout + Given I open the BrowserStack demo store + And I sign in to BrowserStack demo + When I add the product at index "1" to my cart + And I close the cart panel + And I add the product at index "2" to my cart + And I proceed to checkout + And I fill in the shipping address + Then I should see the confirmation message "Your Order has been successfully placed." From 6ce457003431870e6a4353d91ef250b84f9f72c3 Mon Sep 17 00:00:00 2001 From: Vishant Kumar Date: Wed, 29 Apr 2026 17:56:55 +0530 Subject: [PATCH 2/4] Add failIfNoTests=true to Surefire config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cucumber's @ignore tag can mark every scenario as skipped — Maven then exits 0 with zero scenarios executed and the load test reports a false success on no work. failIfNoTests=true makes Maven fail the build instead. Cucumber-specific safety; not relevant to the JUnit/TestNG siblings. --- serenity-cucumber/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/serenity-cucumber/pom.xml b/serenity-cucumber/pom.xml index bfbaadb..a9d2438 100644 --- a/serenity-cucumber/pom.xml +++ b/serenity-cucumber/pom.xml @@ -99,6 +99,10 @@ **/RunCucumberTest.java false + + true From acee8304173e9e6872e37570cfdb51ad473633ac Mon Sep 17 00:00:00 2001 From: Vishant Kumar Date: Wed, 29 Apr 2026 19:04:23 +0530 Subject: [PATCH 3/4] Wait for cart panel text to populate before asserting The cart panel slides in async after the add-to-cart click, so getText() on the cart's product title can return an empty string before the panel finishes rendering. Add an explicit WebDriverWait to wait for the text to appear, then assert. Smoke tested locally end-to-end (Selenium Grid + bstackdemo.com): 2 scenarios, 11 steps, all passing. --- .../src/test/java/com/example/StepDefs.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/serenity-cucumber/src/test/java/com/example/StepDefs.java b/serenity-cucumber/src/test/java/com/example/StepDefs.java index b93f0bf..6488aae 100644 --- a/serenity-cucumber/src/test/java/com/example/StepDefs.java +++ b/serenity-cucumber/src/test/java/com/example/StepDefs.java @@ -11,6 +11,7 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.support.ui.WebDriverWait; import java.net.URL; import java.time.Duration; @@ -85,10 +86,14 @@ public void iAddTheProductAtIndexToMyCart(String index) { @Then("the same product should appear in my cart") public void theSameProductShouldAppearInMyCart() { - WebElement productInCartElem = driver.findElement(By.cssSelector( + By cartProductTitle = By.cssSelector( "#__next > div > div > div.float-cart.float-cart--open > div.float-cart__content > div.float-cart__shelf-container > div > div.shelf-item__details > p.title" - )); - assertEquals(addedProductName, productInCartElem.getText()); + ); + // The cart panel slides in async after the buy click; wait for its text to populate + // before asserting, otherwise getText() can return an empty string. + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(d -> !d.findElement(cartProductTitle).getText().isEmpty()); + assertEquals(addedProductName, driver.findElement(cartProductTitle).getText()); } @And("I close the cart panel") From 29cddd865850eee823de133f4af03de8f31064f5 Mon Sep 17 00:00:00 2001 From: Vishant Kumar Date: Mon, 4 May 2026 16:41:45 +0530 Subject: [PATCH 4/4] Surefire : also accept *Runner.java naming for Cucumber suites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous narrow include `**/RunCucumberTest.java` worked only as long as the runner class kept that exact filename. Combined with `failIfNoTests=true`, any rename (e.g. CucumberTestRunner.java — which is the more common Cucumber JUnit5 Suite convention) made Surefire find zero tests and Maven exit with BUILD FAILURE *before any test ran*. On the BrowserStack Load Testing pod this surfaced as `errorType: initialization` with no log artifacts uploaded. Switch to the four standard test-class patterns (Test*/Tests/TestCase) plus `*Runner.java` to cover both Cucumber Suite naming conventions. This keeps `failIfNoTests=true` honest while letting users rename the runner class without silently breaking their LT runs. Caught by the LTS-2849 QA watchdog (TC-21) on 2026-05-04 — see BStackAutomation PR #65129 commit 35655ce23df for the matching fix in the QA repo's local seed of this sample. --- serenity-cucumber/pom.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/serenity-cucumber/pom.xml b/serenity-cucumber/pom.xml index a9d2438..838c59c 100644 --- a/serenity-cucumber/pom.xml +++ b/serenity-cucumber/pom.xml @@ -95,8 +95,19 @@ maven-surefire-plugin 3.1.2 + - **/RunCucumberTest.java + **/*Runner.java + **/*Test.java + **/*Tests.java + **/*TestCase.java false