diff --git a/.gitignore b/.gitignore index 1120b9d..4f9bd77 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ **/log **/browserstack-cli **/.DS_Store +**/node_modules/ +**/package-lock.json +**/yarn.lock +**/pnpm-lock.yaml diff --git a/jest/README.md b/jest/README.md new file mode 100644 index 0000000..2ec0072 --- /dev/null +++ b/jest/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 jest + ``` + +2. **Install Node dependencies** + + ```sh + npm install + ``` + +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/jest/browserstack-load.yml b/jest/browserstack-load.yml new file mode 100644 index 0000000..d2a0889 --- /dev/null +++ b/jest/browserstack-load.yml @@ -0,0 +1,48 @@ +# ============================= +# 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 'duration' to the total duration of the entire test, in minutes and seconds. The test will run infinite iterations until the duration is met. Example: '2m', '3m 40s'. This is not a required parameter. +duration: 1m + +# 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: nodejs + +# Set framework to the test framework used in your Selenium project. +framework: jest + +# 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: + - ./package.json + testConfigs: + - ./jest.config.js diff --git a/jest/jest.config.js b/jest/jest.config.js new file mode 100644 index 0000000..aa683f6 --- /dev/null +++ b/jest/jest.config.js @@ -0,0 +1,8 @@ +// Jest runs with --runInBand on BrowserStack so the single Selenium session +// per pod is never contended. testEnvironment must be "node" — Jest drives a +// remote WebDriver, not the in-process DOM. +module.exports = { + testEnvironment: "node", + testMatch: ["**/tests/**/*.test.js", "**/?(*.)+(spec|test).js"], + testTimeout: 120000, +}; diff --git a/jest/package.json b/jest/package.json new file mode 100644 index 0000000..771a186 --- /dev/null +++ b/jest/package.json @@ -0,0 +1,13 @@ +{ + "name": "browserstack-jest-load-testing-sample", + "version": "1.0.0", + "private": true, + "description": "Sample Jest + selenium-webdriver project for BrowserStack Load Testing", + "scripts": { + "test": "jest --runInBand --passWithNoTests=false" + }, + "dependencies": { + "jest": "^29.7.0", + "selenium-webdriver": "^4.21.0" + } +} diff --git a/jest/tests/add-to-cart.test.js b/jest/tests/add-to-cart.test.js new file mode 100644 index 0000000..0725684 --- /dev/null +++ b/jest/tests/add-to-cart.test.js @@ -0,0 +1,55 @@ +const { Builder, By, until } = require("selenium-webdriver"); + +// On BrowserStack Load Testing, the Selenium pod exposes a hub at +// http://localhost:4444/wd/hub. A plain `forBrowser("chrome").build()` +// would try to spawn a local browser the pod does not have. +const HUB_URL = "http://localhost:4444/wd/hub"; + +describe("BStackDemo test add to cart", () => { + let driver; + + beforeAll(async () => { + driver = await new Builder() + .usingServer(HUB_URL) + .forBrowser("chrome") + .build(); + }); + + afterAll(async () => { + if (driver) await driver.quit(); + }); + + test("should add product to cart successfully", async () => { + // visit the site + await driver.get("https://bstackdemo.com/"); + + // get name of product we want to add to cart + const productNameElem = await driver.wait( + until.elementLocated(By.xpath("//*[@id='3']/p")), + 10000, + ); + await driver.wait(until.elementIsVisible(productNameElem), 10000); + const productToAdd = (await productNameElem.getText()).trim(); + + // click on add to cart + const addToCartBtn = await driver.findElement( + By.css("#\\33 > .shelf-item__buy-btn"), + ); + await addToCartBtn.click(); + + // get name of item in cart + const productInCartElem = await driver.wait( + until.elementLocated( + By.css( + ".float-cart.float-cart--open .float-cart__shelf-container .shelf-item__details p.title", + ), + ), + 10000, + ); + await driver.wait(until.elementIsVisible(productInCartElem), 10000); + const productInCart = (await productInCartElem.getText()).trim(); + + // check if product in cart is same as one added + expect(productInCart).toBe(productToAdd); + }); +}); diff --git a/jest/tests/checkout.test.js b/jest/tests/checkout.test.js new file mode 100644 index 0000000..666e6a9 --- /dev/null +++ b/jest/tests/checkout.test.js @@ -0,0 +1,98 @@ +const { Builder, By, until } = require("selenium-webdriver"); + +// On BrowserStack Load Testing, the Selenium pod exposes a hub at +// http://localhost:4444/wd/hub. A plain `forBrowser("chrome").build()` +// would try to spawn a local browser the pod does not have. +const HUB_URL = "http://localhost:4444/wd/hub"; +const WAIT_MS = 10000; + +const waitForClickable = async (driver, locator) => { + const el = await driver.wait(until.elementLocated(locator), WAIT_MS); + await driver.wait(until.elementIsVisible(el), WAIT_MS); + await driver.wait(until.elementIsEnabled(el), WAIT_MS); + return el; +}; + +const click = async (driver, locator) => { + const el = await waitForClickable(driver, locator); + await el.click(); +}; + +const type = async (driver, locator, text) => { + const el = await waitForClickable(driver, locator); + await el.sendKeys(text); +}; + +describe("BStackDemo test checkout flow", () => { + let driver; + + beforeAll(async () => { + driver = await new Builder() + .usingServer(HUB_URL) + .forBrowser("chrome") + .build(); + // Implicit wait so findElement polls instead of failing the moment a + // React modal/transition hasn't painted yet. + await driver.manage().setTimeouts({ implicit: WAIT_MS }); + }); + + afterAll(async () => { + if (driver) await driver.quit(); + }); + + test("should complete checkout flow successfully", async () => { + // visit the site + await driver.get("https://bstackdemo.com/"); + + // sign in + await click(driver, By.id("signin")); + await click(driver, By.css("#username svg")); + await click(driver, By.id("react-select-2-option-0-0")); + await click(driver, By.css("#password svg")); + await click(driver, By.id("react-select-3-option-0-0")); + await click(driver, By.id("login-btn")); + + // click first item + await click(driver, By.css("#\\31 > .shelf-item__buy-btn")); + + // cart overlay opens + await driver.wait( + until.elementLocated(By.css(".float-cart.float-cart--open")), + WAIT_MS, + ); + + // close cart (best-effort) + try { + await click(driver, By.css(".float-cart__close-btn")); + } catch (e) { + console.log("[WARN] Could not close cart overlay:", e.message); + } + + // click second item + await click(driver, By.css("#\\32 > .shelf-item__buy-btn")); + + // proceed to checkout + await click(driver, By.css(".buy-btn")); + + // add address details + await type(driver, By.id("firstNameInput"), "first"); + await type(driver, By.id("lastNameInput"), "last"); + await type(driver, By.id("addressLine1Input"), "address"); + await type(driver, By.id("provinceInput"), "province"); + await type(driver, By.id("postCodeInput"), "pincode"); + + // checkout + await click(driver, By.id("checkout-shipping-continue")); + + // continue shopping confirmation + await click( + driver, + By.xpath("//*[@id='checkout-app']/div/div/div/div/a/button"), + ); + + // navigate to orders + await click(driver, By.xpath("//*[text()='Orders']")); + + expect(true).toBe(true); + }); +});