diff --git a/.gitignore b/.gitignore
index a1c2a23..0d631f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,15 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+
+src/test/resources/Reports/*
+target/
+build/
+.project
+.settings/
+.classpath
+.idea/
+.gradle/
+*.iml
+.DS_Store
+!gradle/wrapper/*
diff --git a/LICENSE b/LICENSE
index 261eeb9..bc8e1b9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright (c) 2021, BrowserStack Limited. https://www.browserstack.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 378560c..084f399 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,265 @@
-# browserstack-examples-cucumber-testng
\ No newline at end of file
+
+
+# BrowserStack Examples Cucumber TestNG Selenium 4
+
+## Introduction
+
+This BrowserStack Example repository demonstrates a Selenium 4 [Page Factory Model](https://www.browserstack.com/guide/page-object-model-in-selenium) wrapped under Cucumber and TestNG with parallel testing capabilities. The Selenium test scripts are written for the open source [BrowserStack Demo web application](https://bstackdemo.com) ([Github](https://github.com/browserstack/browserstack-demo-app)). This BrowserStack Demo App is an e-commerce web application which showcases multiple real-world user scenarios, written in Next and React. The app is bundled with offers data, orders data and products data that contains everything you need to start using the app and run tests out-of-the-box.
+
+The Selenium tests are run on different platforms like on-prem and BrowserStack using various run configurations and test capabilities.
+
+## :pushpin: Key Features
+
+:globe_with_meridians: Empowered to run on various platforms including **on-premise browsers**, browsers running on a remote selenium grid such as
+ **[BrowserStack Automate](https://www.browserstack.com/automate)**
+
+:rocket: Enables concurrent execution of cucumber scenarios at scenario and feature level across different platforms.
+
+:bulb: Run the builds with max concurrency as per the parallel quota on your BrowserStack Account.
+
+:computer: Control concurrency of the tests by setting thread count from terminal.
+
+:white_check_mark: Three distinct layers of framework implementaion viz. Feature File, Step Definitions, Page Factory Classes to maintain the border between Business users and developers.
+
+:recycle: Can be ran on any installed browsers onprem without downloading the respective driver executables.
+
+:zap: Single Runner class required to configure all parameters and plugins.
+
+:bar_chart: HTML and PDF reports generation with screenshots.
+
+---
+
+## Repository setup
+
+- Clone the repository
+- Ensure you have the following dependencies installed on the machine
+ - Java >= 8
+ - Maven >= 3.1+
+
+
+ Maven:
+ ```sh
+ mvn install
+ ```
+---
+## About the tests in this repository
+
+This repository contains the following Cucumber Scenario tests:
+
+| Features | Tags | Test Name | Description |
+|--|--|--|--|
+| E2E | @e2e |End to End Scenario|This test scenario verifies successful product purchase lifecycle end-to-end with verifying that 9 Apple products are only shown if the Apple vendor filter option is applied and verifies that the product prices are in ascending order when the product sort “Lowest to Highest” is applied. It demonstrates the [Page Factory Model design pattern](https://www.browserstack.com/guide/page-object-model-in-selenium)|
+|User|@locked|Login as Locked User|This test verifies the login workflow error for a locked user.|
+|User|@noimage, @fail|Login as User with no image loaded| This test verifies that the product images load for user: “image_not_loading_user” on the e-commerce application. Since the images do not load, the test case assertion fails.|
+|User|@orders|Login as User with existing Orders| This test verifies that existing orders are shown for user: “existing_orders_user”|
+|Offers|@offers|Offers for Mumbai location|This test mocks the GPS location for Mumbai and verifies that the product offers applicable for the Mumbai location are shown. |
+
+ - `@test` will run all scenarios in **Offers** feature file.
+ - `@users` will run all scenarios in **Users** feature file.
+ - `@e2e` will run all scenarios in **E2E** feature file.
+ - `@regression` will run all scenarios in all the feature files.
+
+---
+
+## Test infrastructure environments
+
+- [On-premise/self-hosted](#On-Premise-or-Self-Hosted)
+- [BrowserStack](#browserstack)
+
+---
+## Configuring the maximum parallel test threads for this repository[SDK]
+
+For all the parallel run configuration profiles, tests will run with max parallels in your BrowserStack account, or you can configure the parallel test threads from the browserstack.yml file
+
+ parallelsPerPlatform
+
+|BrowserStack| onPrem |
+|--|--|
+| browserstack/conf/Run_Single_Test/single.testng.xml|testng.xml
+browserstack/conf/Run_Parallel_Test/parallel.testng.xml |
+
+
+
+---
+# Test Reporting
+
+- [Extent reports](https://ghchirp.tech/2098/)
+- Reports in HTML and PDF formats are generated in the [Reports](src/test/resources/Reports) with TimeStamp.
+---
+# On Premise or Self Hosted
+
+This infrastructure points to running the tests on your own machine using any browser (e.g. Chrome) using the browser's driver executables (e.g. ChromeDriver for Chrome). This driver dependency is resolved using the WebDriverManager and eliminates the need to download the webdriver on the local machine.
+
+## Running Your Tests
+
+### Run a specific test/entire suite on your own machine
+
+- How to run the test?
+
+ To run any test scenario (e.g. End to End Scenario) on your own machine, use the following command, you can specify any cucumber tag from the feature files to run the scenarios at feature/scenario outline level in this Maven Profile.
+
+ Eg. "@e2e", "@noimage", "@offers" or any of the other test scenario tags, as outlined in [About the tests in this repository](#About-the-tests-in-this-repository) section.
+`"-Dcucumber.filter.tags=@e2e"`
+
+ User can also specify the choice of browser in the `` tags of the profile to run the suite/scenarios in that browser.
+ Eg. `firefox`
+
+ thread-count can be set for the tests from the terminal
+ `"-DthreadCount=5"`
+
+ Maven:
+ ```sh
+ mvn test -P scenario-onprem
+ ```
+
+- Output
+
+ This run profile executes a specific test scenario/suite in single/parallel on the stated browser instance on your own machine.
+
+---
+# BrowserStack
+
+[BrowserStack](https://browserstack.com) provides instant access to 2,000+ real mobile devices and browsers on a highly reliable cloud infrastructure that effortlessly scales as testing needs grow.
+
+## Prerequisites
+
+- Create a new [BrowserStack account](https://www.browserstack.com/users/sign_up) or use an existing one.
+- Identify your BrowserStack username and access key from the [BrowserStack Automate Dashboard](https://automate.browserstack.com/) and export them as environment variables using the below commands.
+
+ - For \*nix based and Mac machines:
+
+ ```sh
+ export BROWSERSTACK_USERNAME= &&
+ export BROWSERSTACK_ACCESS_KEY=
+ ```
+
+ - For Windows:
+
+ ```shell
+ set BROWSERSTACK_USERNAME=
+ set BROWSERSTACK_ACCESS_KEY=
+ ```
+
+ Alternatively, you can also hardcode username and access_key objects in the respective file:
+ - [browserstack.yml](browserstack.yml) file
+
+
+Note:
+- We have configured a list of test capabilities in the json files. You can certainly update them based on your device / browser test requirements.
+- The exact test capability values can be easily identified using the [Browserstack W3C Capability Generator](https://www.browserstack.com/automate/capabilities?tag=selenium-4)
+
+
+## Running Your Tests
+
+### Run a specific test/entire test suite in parallel on a single BrowserStack browser
+
+In this section, we will run the tests in parallel on a single browser on Browserstack. Refer to [browserstack.yml](browserstack.yml) file to change test capabilities for this configuration.
+
+- How to run the test?
+
+ To run the tests in parallel on a single BrowserStack browser, use the following command:
+
+ Maven:
+ ```sh
+ mvn test -P scenario-bs
+ ```
+
+ You can mention any scenario from the feature files using the `-Dcucumber.filter.tags`, tags defined at Feature level Eg. `@users` will run all the scenarios in the [Users Feature](src/test/resources/Features/Users.feature) file in parallel. Likewise `@regression` will run all the scenarios from all the Feature files in parallel.
+
+- Output
+
+ This run profile executes any scenario/entire test suite in parallel on a single BrowserStack browser. Please refer to your [BrowserStack dashboard](https://automate.browserstack.com/) for test results.
+
+ - Note: By default, this execution would run maximum test threads based on the parallel quota on your BrowserStack Account. Thread count can be configured as below based on your requirements.
+
+ ```sh
+ mvn test -P scenario-bs "-Dcucumber.filter.tags=@regresssion"
+ ```
+
+
+### Run the entire test suite in parallel on multiple BrowserStack browsers
+
+In this section, we will run the tests in parallel on multiple browsers on Browserstack. Refer to the [browserstack.yml](browserstack.yml) file to change test capabilities for this configuration.
+
+- How to run the test?
+
+ To run the entire test suite in parallel on multiple BrowserStack browsers/devices, use the following command:
+
+ Maven:
+ ```sh
+ mvn test -P scenario-bs
+ ```
+
+ You can mention any scenario from the feature files using the `-Dcucumber.filter.tags`, tags defined at Feature level Eg. `@users` will run all the scenarios in the [Users Feature](src/test/resources/Features/Users.feature) file in parallel. Likewise `@regression` will run all the scenarios from all the Feature files in parallel across multiple browsers/devices.
+---
+### [Web application hosted on internal environment] Running your tests on BrowserStack using BrowserStackLocal
+
+#### Prerequisites
+
+- Clone the [BrowserStack demo application](https://github.com/browserstack/browserstack-demo-app) repository.
+ ```sh
+ git clone https://github.com/browserstack/browserstack-demo-app
+ ```
+- Please follow the README.md on the BrowserStack demo application repository to install and start the dev server on localhost.
+- In this section, we will run a single test case to test the BrowserStack Demo app hosted on your local machine i.e. localhost. Refer to the [browserstack.yml](browserstack.yml) file to change test capabilities for this configuration.
+- Note: You may need to provide additional BrowserStackLocal arguments to successfully connect your localhost environment with BrowserStack infrastructure. (e.g if you are behind firewalls, proxy or VPN).
+- Further details for successfully creating a BrowserStackLocal connection can be found here:
+
+ - [Local Testing with Automate](https://www.browserstack.com/local-testing/automate)
+ - [BrowserStackLocal Java GitHub](https://github.com/browserstack/browserstack-local-java)
+
+
+### [Web application hosted on internal environment] Run a specific test/entire test suite in parallel on a single BrowserStack browser/device using BrowserStackLocal
+
+- How to run the test?
+
+ - To run any test scenario (e.g. End to End Scenario) on a single BrowserStack browser using BrowserStackLocal, use the following command:
+
+ Maven:
+ ```sh
+ mvn test -P local-bs
+ ```
+
+You can mention any scenario from the feature files using the `-Dcucumber.filter.tags`, tags defined at Feature level Eg. `@users` will run all the scenarios in the [Users Feature](src/test/resources/Features/Users.feature) file in parallel. Likewise `@regression` will run all the scenarios from all the Feature files in parallel.
+
+- Output
+
+ This run profile executes a single test/entire suite on an internally hosted web application on a single browser on BrowserStack in parallel. Please refer to your [BrowserStack dashboard](https://automate.browserstack.com/) for test results.
+
+
+### [Web application hosted on internal environment] Run the entire test suite in parallel on multiple BrowserStack browser using BrowserStackLocal
+
+In this section, we will run the test cases to test the internally hosted website in parallel on multiple browsers/devices on Browserstack. Refer to the [browserstack.yml](browserstack.yml) file to change test capabilities for this configuration.
+
+- How to run the test?
+
+ To run the entire test suite in parallel on multiple devices and browsers using BrowserStackLocal, use the following command:
+
+ Maven:
+ ```sh
+ mvn test -P local-bs
+ ```
+
+- Output
+
+ This run profile executes the entire test suite on an internally hosted web application on multiple browsers on BrowserStack. Please refer to your [BrowserStack dashboard](https://automate.browserstack.com/) for test results.
+
+- Note: By default, this execution would run maximum test threads based on the parallel quota on your BrowserStack Account. Thread count can be configured as below based on your requirements.
+ ```sh
+ mvn test -P local-bs "-Dcucumber.filter.tags=@e2e"
+ ```
+
+
+## Additional Resources
+
+- View your test results on the [BrowserStack Automate dashboard](https://www.browserstack.com/automate)
+- Documentation for writing [Automate test scripts in Java](https://www.browserstack.com/automate/java)
+- Customising your tests capabilities for Selenium 4 on BrowserStack using our [test capability generator](https://www.browserstack.com/automate/capabilities?tag=selenium-4)
+- [List of Browsers & mobile devices](https://www.browserstack.com/list-of-browsers-and-platforms?product=automate) for automation testing on BrowserStack
+- [Using Automate REST API](https://www.browserstack.com/automate/rest-api) to access information about your tests via the command-line interface
+- Understand how many parallel sessions you need by using our [Parallel Test Calculator](https://www.browserstack.com/automate/parallel-calculator?ref=github)
+- For testing public web applications behind IP restriction, [Inbound IP Whitelisting](https://www.browserstack.com/local-testing/inbound-ip-whitelisting) can be enabled with the [BrowserStack Enterprise](https://www.browserstack.com/enterprise) offering
+
+
+## Open Issues
+- The PDF report breaks for the multiple scenarios executed in parallel on multiple devices/browsers.
diff --git a/browserstack.yml b/browserstack.yml
new file mode 100644
index 0000000..7ca1a9d
--- /dev/null
+++ b/browserstack.yml
@@ -0,0 +1,97 @@
+# =============================
+# 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 Cucumber TestNG # Automate - Map SDK
+# 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}
+buildTag: "regresson"
+selfHeal: true
+# =======================================
+# 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: OS X
+ osVersion: Big Sur
+ browserName: Chrome
+ browserVersion: 131.0
+ - os: Windows
+ osVersion: 10
+ browserName: Edge
+ browserVersion: 131.0
+ - deviceName: Samsung Galaxy S2*
+ browserName: chrome # Try 'samsung' for Samsung browser
+ osVersion: [12131415]
+ - deviceName: iPhone 1*
+ browserName: safari
+ osVersion: [15161718]
+# =======================
+# Parallels per Platform
+# =======================
+# The number of parallel threads to be used for each platform set.
+# BrowserStack's SDK runner will select the best strategy based on the configured value
+#
+# Example 1 - If you have configured 3 platforms and set `parallelsPerPlatform` as 2, a total of 6 (2 * 3) parallel threads will be used on BrowserStack
+#
+# Example 2 - If you have configured 1 platform and set `parallelsPerPlatform` as 5, a total of 5 (1 * 5) parallel threads will be used on BrowserStack
+parallelsPerPlatform: 2
+framework: cucumber-testng #junit,testng,java,cucumber-testng,serenity,cucumber-junit
+source: cucumber-testng:intellij:v1.1.2
+staticWebDriver: true
+browserstackAutomation: true
+# ==========================================
+# 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: false # (Default false)
+
+# Options to be passed to BrowserStack local in-case of advanced configurations
+#browserStackLocalOptions:
+ # localIdentifier: # (Default: null) Needed if you need to run multiple instances of local.
+ # forceLocal: false # (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
+
+# ===================
+# Debugging features
+# ===================
+debug: true # # Set to true if you need screenshots for every selenium command ran
+networkLogs: true # 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)
+
+# Test Observability is an intelligent test reporting & debugging product. It collects data using the SDK. Read more about what data is collected at https://www.browserstack.com/docs/test-observability/references/terms-and-conditions
+# Visit observability.browserstack.com to see your test reports and insights. To disable test observability, specify `testObservability: false` in the key below.
+testObservability: true
+accessibility: true
+accessibilityOptions:
+ wcagVersion: wcag21aaa # Default: wcag21aa
+ includeIssueType:
+ bestPractice: true # Default: false
+ needsReview: true # Default: true
+ experimental: true # Default: true
+ #includeTagsInTestingScope: [ '@users' ] # Tags for test cases to include
+ #excludeTagsInTestingScope: [ 'old', 'deprecated' ] # Tags for test cases to exclude
+
+performance: assert
+percy: true
+percyCaptureMode: auto
+#logLevel: debug
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c69281d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,141 @@
+
+ 4.0.0
+
+ org.BrowserStack
+ CucumberJava_Automate
+ 1.0-SNAPSHOT
+
+
+ 7.3.0
+ UTF-8
+ 11
+ 11
+ 3.0.0-M5
+ 7.4.0
+ 3.0.0-M5
+ https://bstackdemo.com/
+
+
+
+
+ io.cucumber
+ cucumber-java
+ ${cucumber.version}
+
+
+ io.cucumber
+ cucumber-testng
+ ${cucumber.version}
+ test
+
+
+ org.testng
+ testng
+ ${testng.version}
+ test
+
+
+ io.cucumber
+ cucumber-picocontainer
+ ${cucumber.version}
+ test
+
+
+ org.seleniumhq.selenium
+ selenium-java
+ 4.22.0
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ LATEST
+ test
+
+
+ commons-io
+ commons-io
+ LATEST
+
+
+ tech.grasshopper
+ extentreports-cucumber7-adapter
+ 1.1.0
+
+
+ com.googlecode.json-simple
+ json-simple
+ 1.1.1
+
+
+ com.browserstack
+ browserstack-local-java
+ 1.0.6
+
+
+ com.squareup.okhttp3
+ okhttp
+ 5.0.0-alpha.10
+
+
+ com.browserstack
+ browserstack-java-sdk
+ LATEST
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${surefire.version}
+
+
+ ${suiteXmlFile}
+ -javaagent:"${com.browserstack:browserstack-java-sdk:jar}"
+
+ ${browser-type}
+ ${Application_url}
+
+
+
+
+ maven-dependency-plugin
+ 3.3.0
+
+
+ getClasspathFilenames
+
+ properties
+
+
+
+
+
+
+
+
+
+ scenario-onprem
+
+ firefox
+ testng.xml
+
+
+
+ scenario-bs
+
+ remote
+ src/test/resources/browserstack/conf/Run_Single_Test/single.testng.xml
+
+
+
+ local-bs
+
+ remote
+ http://localhost:3000/
+ src/test/resources/browserstack/conf/Run_Parallel_Test/parallel.testng.xml
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/Runner/CucumberTest.java b/src/test/java/Runner/CucumberTest.java
new file mode 100644
index 0000000..4efc161
--- /dev/null
+++ b/src/test/java/Runner/CucumberTest.java
@@ -0,0 +1,139 @@
+package Runner;
+
+import com.browserstack.local.Local;
+import com.qa.util.BrowserstackTestStatusListener;
+import com.qa.util.CapabilityReader;
+import io.cucumber.testng.CucumberOptions;
+import io.cucumber.testng.FeatureWrapper;
+import io.cucumber.testng.PickleWrapper;
+import io.cucumber.testng.TestNGCucumberRunner;
+import io.github.bonigarcia.wdm.WebDriverManager;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.firefox.FirefoxDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.safari.SafariDriver;
+import org.testng.annotations.*;
+
+import java.io.FileReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@CucumberOptions(features = "src/test/resources/Features", glue = {"StepDefinitions"},
+ plugin = {"pretty","json:target/cucumber-reports/Cucumber.json"
+ //"com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:"
+ },
+ monochrome = true,publish = true
+ )
+@Listeners(BrowserstackTestStatusListener.class)
+public class CucumberTest {
+ private TestNGCucumberRunner testNGCucumberRunner;
+ public static ThreadLocal tlDriver = new ThreadLocal<>();
+ private Local l;
+ public static String buildname;
+ @Parameters(value = {"config"})
+ @BeforeSuite
+ public void localStart(String config_file) throws Exception {
+ JSONParser parser = new JSONParser();
+ if(!config_file.isEmpty()) {
+ JSONObject config = (JSONObject) parser.parse(new FileReader("src/test/resources/browserstack/conf/" + config_file));
+ JSONObject local = (JSONObject) config.get("capabilities");
+ boolean localKey = local.containsKey("local");
+ if (localKey && local.get("local").toString().contains("true")) {
+ l = new Local();
+ Map options = new HashMap();
+ if (System.getenv("BROWSERSTACK_ACCESS_KEY") == null)
+ options.put("key", (String) config.get("key"));
+ else
+ options.put("key", System.getenv("BROWSERSTACK_ACCESS_KEY"));
+ System.out.println("Starting Local");
+ l.start(options);
+ }
+ SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.YY hh.mm");
+ buildname = local.get("projectName") + "-" + sdf.format(new Date());
+ }
+ }
+ @BeforeClass(alwaysRun = true)
+ public void setUpClass() {
+ testNGCucumberRunner = new TestNGCucumberRunner(this.getClass());
+ }
+ @Parameters(value = { "config", "environment" })
+ @BeforeMethod()
+ public void setUpHooks(String config_file, String environment) throws Exception {
+ DesiredCapabilities capabilities = new DesiredCapabilities();
+ String browser=System.getProperty("browser-type").toLowerCase();
+ if (browser.equalsIgnoreCase("chrome")) {
+ WebDriverManager.chromedriver().setup();
+ tlDriver.set(new ChromeDriver());
+ } else if (browser.equalsIgnoreCase("firefox")) {
+ WebDriverManager.firefoxdriver().setup();
+ tlDriver.set(new FirefoxDriver());
+ } else if (browser.equalsIgnoreCase("safari")) {
+ WebDriverManager.safaridriver().setup();
+ tlDriver.set(new SafariDriver());
+ } else if (browser.equalsIgnoreCase("remote")) {
+ JSONParser parser = new JSONParser();
+ JSONObject config;
+ JSONObject envs;
+ config = (JSONObject) parser.parse(new FileReader("src/test/resources/browserstack/conf/"+config_file));
+ envs = (JSONObject) config.get("environments");
+ Object env = envs.get(environment);
+ capabilities = CapabilityReader.getCapability((Map) env, config);
+ String username = (String) config.get("user");
+ if (username == null || username.isEmpty()){
+ username = System.getenv("BROWSERSTACK_USERNAME");
+ }
+ String accessKey = (String) config.get("key");
+ if (accessKey == null || accessKey.isEmpty()) {
+ accessKey = System.getenv("BROWSERSTACK_ACCESS_KEY");
+ }
+ tlDriver.set( new RemoteWebDriver(
+ new URL("https://" + username + ":" + accessKey + "@" + config.get("server") + "/wd/hub"), capabilities));
+ }else
+ throw new AssertionError("Invalid input for browser");
+ Map deviceInfo = new HashMap<>();
+ JavascriptExecutor jse = (JavascriptExecutor) getDriver();
+ try{
+ deviceInfo = (Map) jse.executeScript("mobile:deviceInfo");
+ }catch(Exception e){
+ }
+ if(deviceInfo.isEmpty())
+ getDriver().manage().window().maximize();
+
+ }
+
+ public static synchronized WebDriver getDriver(){
+ return tlDriver.get();
+ }
+
+ @Test(dataProvider = "scenarios")
+ public void scenario(PickleWrapper pickleWrapper, FeatureWrapper featureWrapper) throws MalformedURLException {
+ JavascriptExecutor jse = (JavascriptExecutor)getDriver();
+ //if(System.getProperty("browser-type").equalsIgnoreCase("remote"))
+ //jse.executeScript("browserstack_executor: {\"action\": \"setSessionName\", \"arguments\": {\"name\":\" "+ pickleWrapper.getPickle().getName() +" \" }}");
+ testNGCucumberRunner.runScenario(pickleWrapper.getPickle());
+ }
+ @DataProvider(parallel = true)
+ public Object[][] scenarios(){
+ return testNGCucumberRunner.provideScenarios();
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void tearDownClass() {
+ //testNGCucumberRunner.finish(); //Conflict with the SDK Runner.CucumberTest.tearDownClass
+ // NoSuchMethod org.yaml.snakeyaml.constructor.Constructor.
+ }
+ @AfterSuite
+ public void shutLocal() throws Exception {
+ System.out.println("Stopping Local");
+ if(l != null) l.stop();
+ }
+}
diff --git a/src/test/java/StepDefinitions/ApplicationHooks.java b/src/test/java/StepDefinitions/ApplicationHooks.java
new file mode 100644
index 0000000..99f5eb1
--- /dev/null
+++ b/src/test/java/StepDefinitions/ApplicationHooks.java
@@ -0,0 +1,20 @@
+package StepDefinitions;
+
+import Runner.CucumberTest;
+import io.cucumber.java.After;
+import io.cucumber.java.Scenario;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.OutputType;
+import org.openqa.selenium.TakesScreenshot;
+import org.openqa.selenium.WebDriver;
+
+public class ApplicationHooks extends CucumberTest {
+ public WebDriver driver = CucumberTest.getDriver();
+ @After(order=1)
+ public void screenshot(Scenario sc){
+ String screenshotName = sc.getName().replaceAll(" ","_");
+ byte[] sourcePath = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
+ sc.attach(sourcePath,"image/png",screenshotName);
+ }
+
+}
diff --git a/src/test/java/StepDefinitions/E2ESteps.java b/src/test/java/StepDefinitions/E2ESteps.java
new file mode 100644
index 0000000..9a84208
--- /dev/null
+++ b/src/test/java/StepDefinitions/E2ESteps.java
@@ -0,0 +1,111 @@
+package StepDefinitions;
+
+import com.pages.bsCheckout;
+import com.pages.bsConfirmation;
+import com.pages.bsHome;
+import com.pages.bsSignin;
+import io.cucumber.java.Before;
+import io.cucumber.java.Scenario;
+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.JavascriptExecutor;
+import org.testng.Assert;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class E2ESteps {
+ ApplicationHooks hooks ;
+ private final bsHome bshome;
+ private final bsSignin bssignin;
+ private final bsCheckout bschecokout;
+ private final bsConfirmation bsconfirm ;
+ public E2ESteps(ApplicationHooks hooks){
+ this.hooks = hooks;
+ bshome = new bsHome(hooks.driver);
+ bssignin = new bsSignin(hooks.driver);
+ bschecokout = new bsCheckout(hooks.driver);
+ bsconfirm = new bsConfirmation(hooks.driver);
+ }
+ Scenario sc;
+
+ @Before
+ public void getScenario(Scenario scenario){
+ this.sc = scenario;
+ }
+ @Given("User is on home page")
+ public void user_is_on_home_page() throws FileNotFoundException {
+ JavascriptExecutor jse = (JavascriptExecutor) hooks.driver;
+ Map deviceInfo = new HashMap<>();
+ try{
+ deviceInfo = (Map) jse.executeScript("mobile:deviceInfo");
+ }catch(Exception e){
+ deviceInfo = null;
+ }
+ if(deviceInfo !=null && deviceInfo.get("model").toLowerCase().matches(".*(iphone|ipad).*"))
+ hooks.driver.get(System.getProperties().getProperty("Application_url").replaceAll("localhost","bs-local.com"));
+ else
+ hooks.driver.get(System.getProperties().getProperty("Application_url"));
+ Yaml yaml = new Yaml();
+ InputStream inputStream = new FileInputStream("browserstack.yml");
+ HashMap yamlMap = yaml.load(inputStream);
+ if(yamlMap.get("browserstackAutomation").toString().equalsIgnoreCase("true")) {
+ jse.executeScript("browserstack_executor: {\"action\":\"lighthouseAudit\",\"arguments\":{\"url\":\"" + hooks.driver.getCurrentUrl() + "\",\"executorOutput\":\"json\"}}");
+
+ jse.executeScript("browserstack_executor: {\"action\":\"lighthouseAudit\",\"arguments\":{\"url\":\"" + hooks.driver.getCurrentUrl() + "\",\"assertResult\":{\"categories\":{\"performance\":40,\"best-practices\":50},\"metrics\":{\"first-contentful-paint\":{\"moreThan\":50,\"metricUnit\":\"score\"},\"largest-contentful-paint\":{\"lessThan\":4000,\"metricUnit\":\"numeric\"},\"total-blocking-time\":{\"lessThan\":600,\"metricUnit\":\"numeric\"},\"cumulative-layout-shift\":{\"moreThan\":50,\"metricUnit\":\"score\"}}}}}");
+ }
+ }
+ @When("User clicks on sign in link")
+ public void user_clicks_on_sign_in_link() {
+ bshome.userSignIn();
+ }
+ @And("User enters {string} and {string} and clicks on sign in")
+ public void SigninDetails(String user, String pass) throws InterruptedException {
+ bssignin.userCredentials(user,pass);
+ }
+ @And("{string} is signed in to the App")
+ public void user_is_signed_in_to_the_app(String user) {
+ Boolean success = bshome.verifySignin();
+ if(success) System.out.println(user + " signed in successfully");
+ else{
+ Assert.fail(user+" login failed");
+ }
+ }
+ @And("User clicks on Apple in Vendor filter")
+ public void user_clicks_on_apple_in_vendor_filter() {
+ bshome.searchProduct();
+ }
+ @And("User sorts the search result by Price: Low to High")
+ public void sorts_the_search_result_by_price_low_to_high() throws InterruptedException {
+ bshome.sortResult();
+ }
+ @And("User can see the list of Apple products with Price in ascending order")
+ public void user_can_see_the_list_of_apple_products_with_price_in_ascending_order() {
+ Map iphonelist = bshome.searchResult();
+ //System.out.println("Details of Apple Products:");
+ sc.log("Details of Apple Products:");
+ //iphonelist.forEach((k,v)->System.out.println(k+" $"+v));
+ iphonelist.forEach((k,v)->sc.log(k+" $"+v));
+ }
+ @And("User clicks on Add to cart for the iPhone XR and clicks on CHECKOUT")
+ public void user_clicks_on_add_to_cart_for_the_i_phone_xr_and_clicks_on_checkout() {
+ bshome.AddtoCart();
+ }
+ @And("User enters {string},{string},{string},{string},{int} and clicks SUBMIT")
+ public void user_enters_and_clicks_submit(String first, String last, String add, String state, Integer pin) {
+ bschecokout.fillAddress(first,last,add,state,pin);
+ }
+ @Then("User can see the order id for the product")
+ public void user_can_see_the_order_id_for_the_product() {
+ String orderid = bsconfirm.verifyPurchase();
+ Assert.assertNotEquals("",orderid,"Order ID confirmation: "+orderid);
+ sc.log("Order ID confirmation: "+orderid);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/StepDefinitions/OffersSteps.java b/src/test/java/StepDefinitions/OffersSteps.java
new file mode 100644
index 0000000..6e52a2f
--- /dev/null
+++ b/src/test/java/StepDefinitions/OffersSteps.java
@@ -0,0 +1,50 @@
+package StepDefinitions;
+
+import com.pages.bsHome;
+import com.pages.bsOffers;
+import io.cucumber.java.Before;
+import io.cucumber.java.Scenario;
+import io.cucumber.java.en.And;
+import io.cucumber.java.en.Then;
+import org.junit.Assert;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class OffersSteps {
+ ApplicationHooks hooks;
+ private bsHome bshome;
+ private bsOffers bsoffer;
+ public OffersSteps(ApplicationHooks hooks){
+ this.hooks = hooks;
+ bshome = new bsHome(hooks.driver);
+ bsoffer = new bsOffers(hooks.driver);
+ }
+ Scenario sc;
+
+ @Before
+ public void getScenario(Scenario scenario){
+ this.sc = scenario;
+ }
+
+ @And("User Clicks on Offers")
+ public void user_clicks_on_offers() {
+ bshome.openOffers();
+ }
+ @Then("User can see offers list for Mumbai location")
+ public void user_can_see_offers_list_for_mumbai_location() {
+ List offerDetails = bsoffer.getOffers();
+ if(offerDetails.isEmpty()){
+ Assert.fail("No offers found for your location");
+ }else{
+ Iterator t = offerDetails.iterator();
+ //System.out.println("Offers for your location:");
+ sc.log("Offers for your location:");
+ while(t.hasNext()){
+ //System.out.println(t.next());
+ sc.log((String) t.next());
+ }
+ }
+ }
+
+}
diff --git a/src/test/java/StepDefinitions/UsersSteps.java b/src/test/java/StepDefinitions/UsersSteps.java
new file mode 100644
index 0000000..234ddbf
--- /dev/null
+++ b/src/test/java/StepDefinitions/UsersSteps.java
@@ -0,0 +1,73 @@
+package StepDefinitions;
+
+import com.pages.bsHome;
+import com.pages.bsOrders;
+import com.pages.bsSignin;
+import io.cucumber.java.Before;
+import io.cucumber.java.Scenario;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class UsersSteps {
+
+ public UsersSteps(ApplicationHooks hooks){
+ this.hooks = hooks;
+ bshome = new bsHome(hooks.driver);
+ bsorder = new bsOrders(hooks.driver);
+ bssign = new bsSignin(hooks.driver);
+ }
+
+ ApplicationHooks hooks;
+ private final bsHome bshome;
+ private final bsOrders bsorder;
+ private final bsSignin bssign ;
+ Scenario sc;
+
+ @Before
+ public void beforeScenario(Scenario scenario){
+ this.sc = scenario;
+ }
+
+ @Then("Product images are not loaded for the {string}")
+ public void product_images_are_not_loaded_for_the_user(String user) {
+ boolean imageload = bshome.verifyimageLoaded();
+ if (imageload){
+ System.out.println("Product images loaded for user");
+ sc.log("Product images loaded for user");
+ }else
+ throw new AssertionError("Product images not loaded for "+ user);
+ }
+ @When("User clicks on Orders")
+ public void user_clicks_on_orders() {
+ bshome.NavigatetoOrder();
+ }
+ @Then("Existing orders are shown for the {string}")
+ public void existing_orders_are_shown_for_the_user(String user) {
+ List orders = bsorder.VerifyOrders();
+ if (orders.isEmpty()){
+ throw new AssertionError("No orders found for the "+user);
+ }else{
+ //System.out.println("Details of Orders for "+user+":");
+ sc.log("Details of Orders for "+user+":");
+ Iterator t = orders.iterator();
+ while(t.hasNext()){
+ //System.out.println(t.next());
+ sc.log(t.next().toString());
+ }
+ }
+ }
+ @Then("{string} gets Your account has been locked.")
+ public void gets_your_account_has_been_locked(String user) throws InterruptedException {
+ boolean verify = bssign.verifyLockedUsersign();
+ if(verify){
+ //System.out.println(user+" was not able to login");
+ sc.log(user+" was not able to login");
+ }else
+ throw new AssertionError("Locked user "+user+" logged in!");
+
+ }
+
+}
diff --git a/src/test/java/com/pages/bsCheckout.java b/src/test/java/com/pages/bsCheckout.java
new file mode 100644
index 0000000..d2b2ed2
--- /dev/null
+++ b/src/test/java/com/pages/bsCheckout.java
@@ -0,0 +1,45 @@
+package com.pages;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.time.Duration;
+
+public class bsCheckout {
+
+ WebDriver driver;
+ WebDriverWait wait;
+ public bsCheckout(WebDriver driver){
+ this.driver = driver;
+ wait = new WebDriverWait(driver, Duration.ofSeconds(10));
+ PageFactory.initElements(driver,this);
+ }
+
+ @FindBy(id = "firstNameInput")
+ WebElement txtName;
+ @FindBy(id="lastNameInput")
+ WebElement txtLast;
+ @FindBy(id = "addressLine1Input")
+ WebElement txtAddress;
+ @FindBy(id = "provinceInput")
+ WebElement txtState;
+ @FindBy(id="postCodeInput")
+ WebElement txtPostal;
+ @FindBy(xpath = "//button[text()='Submit']")
+ WebElement btnSubmit;
+
+ public void fillAddress(String first, String last, String address, String state, Integer pin){
+ wait.until(ExpectedConditions.elementToBeClickable(txtName));
+ txtName.sendKeys(first);
+ txtLast.sendKeys(last);
+ txtAddress.sendKeys(address);
+ txtState.sendKeys(state);
+ txtPostal.sendKeys(Integer.toString(pin));
+ btnSubmit.submit();
+
+ }
+}
diff --git a/src/test/java/com/pages/bsConfirmation.java b/src/test/java/com/pages/bsConfirmation.java
new file mode 100644
index 0000000..61400b0
--- /dev/null
+++ b/src/test/java/com/pages/bsConfirmation.java
@@ -0,0 +1,34 @@
+package com.pages;
+
+import org.junit.Assert;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.time.Duration;
+
+public class bsConfirmation {
+ WebDriver driver;
+ WebDriverWait wait;
+ public bsConfirmation(WebDriver driver){
+ this.driver = driver;
+ wait = new WebDriverWait(driver, Duration.ofSeconds(10));
+ PageFactory.initElements(driver,this);
+ }
+
+ @FindBy(xpath="//button[contains(text(),'Continue Shopping')]")
+ WebElement btnContinue;
+ @FindBy(xpath ="//div/strong")
+ WebElement lblOrder;
+
+ public String verifyPurchase(){
+ wait.until(ExpectedConditions.elementToBeClickable(btnContinue));
+ Assert.assertTrue("Order not placed!",driver.getPageSource().contains("Your Order has been successfully placed."));
+ String orderid;
+ orderid = lblOrder.getAttribute("innerText");
+ return orderid;
+ }
+}
diff --git a/src/test/java/com/pages/bsHome.java b/src/test/java/com/pages/bsHome.java
new file mode 100644
index 0000000..5aa861e
--- /dev/null
+++ b/src/test/java/com/pages/bsHome.java
@@ -0,0 +1,128 @@
+package com.pages;
+
+import org.junit.Assert;
+import org.openqa.selenium.*;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.How;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.ui.*;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class bsHome {
+
+ WebDriver driver;
+ WebDriverWait wait;
+ @FindBy(how = How.XPATH, using="//span[text()='Apple']")
+ WebElement lblApple ;
+ @FindBy(xpath="//select")
+ WebElement ddPrice ;
+ @FindBy(id="signin")
+ WebElement lnksignIn;
+ @FindBy(xpath="//div[text()='Checkout']")
+ WebElement lnkCheckout;
+ @FindBy(id = "offers")
+ WebElement lnkOffers;
+ @FindBy(id="logout")
+ WebElement lnkLogout;
+ @FindBy(xpath = "(//div[@class='shelf-item__thumb']/img)[1]")
+ WebElement image;
+ @FindBy(id="orders")
+ WebElement lnkOrder;
+
+ public bsHome(WebDriver driver){
+ this.driver = driver;
+ wait = new WebDriverWait(driver, Duration.ofSeconds(10));
+ PageFactory.initElements(driver,this);
+ }
+ public void searchProduct(){
+ lblApple.click();
+ }
+ public void sortResult() throws InterruptedException {
+ Select sort = new Select(ddPrice);
+ sort.selectByValue("lowestprice");
+ Thread.sleep(5000);
+
+ }
+ public Map searchResult(){
+ List iphones = driver.findElements(By.cssSelector("div.shelf-item"));
+ Assert.assertEquals("9 products not shown as expected",9,iphones.size());
+ String title,price;
+ Map iphoneDetails= new HashMap();
+ for (int i = 0;i 0", image);
+
+ boolean loaded = false;
+ if (result instanceof Boolean) {
+ loaded = (Boolean) result;
+ return loaded;
+ }
+ return loaded;
+ }
+
+ public void AddtoCart(){
+ WebElement cart = driver.findElement(By.xpath("(//div[text()='Add to cart'])[1]"));
+ wait.until(ExpectedConditions.elementToBeClickable(cart));
+ cart.click();
+ wait.until(ExpectedConditions.elementToBeClickable(lnkCheckout));
+ lnkCheckout.click();
+ }
+ public void NavigatetoOrder(){
+ wait.until(ExpectedConditions.elementToBeClickable(lnkOrder));
+ lnkOrder.click();
+ }
+
+}
diff --git a/src/test/java/com/pages/bsOffers.java b/src/test/java/com/pages/bsOffers.java
new file mode 100644
index 0000000..7e29dfd
--- /dev/null
+++ b/src/test/java/com/pages/bsOffers.java
@@ -0,0 +1,37 @@
+package com.pages;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+public class bsOffers {
+ private WebDriver driver;
+ private WebDriverWait wait;
+ public bsOffers(WebDriver driver){
+ this.driver = driver;
+ wait = new WebDriverWait(driver, Duration.ofSeconds(10));
+ PageFactory.initElements(driver,this);
+ }
+
+ public List getOffers(){
+ List offers= null;
+ List offerDetails = new ArrayList<>();
+ try {
+ wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//div[contains(@class,'offers-listing')]")));
+ offers = driver.findElements(By.xpath("//div[contains(@class,'offers-listing')]/div/div"));
+ }catch(Exception e){
+ return offerDetails;
+ }
+ for (WebElement offer : offers){
+ offerDetails.add(offer.getText());
+ }
+ return offerDetails;
+ }
+}
diff --git a/src/test/java/com/pages/bsOrders.java b/src/test/java/com/pages/bsOrders.java
new file mode 100644
index 0000000..4f14a62
--- /dev/null
+++ b/src/test/java/com/pages/bsOrders.java
@@ -0,0 +1,38 @@
+package com.pages;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+public class bsOrders {
+
+ public bsOrders(WebDriver driver){
+ this.driver = driver;
+ wait = new WebDriverWait(driver, Duration.ofSeconds(10));
+ PageFactory.initElements(driver,this);
+ }
+ WebDriver driver;
+ WebDriverWait wait;
+
+ @FindBy(xpath = "//div[@id='__next']//div[contains(@class,'top-medium')]")
+ WebElement paneOrders;
+
+ public List VerifyOrders(){
+ wait.until(ExpectedConditions.visibilityOf(paneOrders));
+ List orderTitle = driver.findElements(By.xpath("//strong[text()='Title:']/.."));
+ List orderPrice = driver.findElements(By.xpath("//span[contains(@class,'price')]"));
+ List Allorders = new ArrayList<>();
+ for(int i=0;i 254) ? message.substring(0, 254) : message;
+ //markTestStatus("failed", reason.replaceAll("[^a-zA-Z0-9._-]", " "), driver);
+ driver.quit();
+ }
+
+}
diff --git a/src/test/java/com/qa/util/CapabilityReader.java b/src/test/java/com/qa/util/CapabilityReader.java
new file mode 100644
index 0000000..a300c03
--- /dev/null
+++ b/src/test/java/com/qa/util/CapabilityReader.java
@@ -0,0 +1,43 @@
+package com.qa.util;
+
+import Runner.CucumberTest;
+import com.browserstack.local.Local;
+import org.json.simple.JSONObject;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class CapabilityReader {
+ protected static HashMap browserstackOptions = new HashMap<>();
+
+ public static synchronized DesiredCapabilities getCapability(Map envCapabilities, JSONObject config) throws Exception {
+ DesiredCapabilities capabilities = new DesiredCapabilities();
+ Iterator it = envCapabilities.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry pair = (Map.Entry) it.next();
+ if (pair.getKey().toString().toLowerCase().contains("envoptions")) {
+ browserstackOptions = (HashMap) pair.getValue();
+ } else
+ capabilities.setCapability(pair.getKey().toString(), pair.getValue().toString());
+ }
+ Map commonCapabilities = (Map) config.get("capabilities");
+ it = commonCapabilities.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry pair = (Map.Entry) it.next();
+ browserstackOptions.put(pair.getKey().toString(), pair.getValue().toString());
+ }
+ // Set the build name for tests
+ browserstackOptions.put("buildName",CucumberTest.buildname);
+ if (System.getenv("BROWSERSTACK_BUILD_NAME") != null) {
+ browserstackOptions.put("buildName", System.getenv("BROWSERSTACK_BUILD_NAME"));
+ }
+
+ capabilities.setCapability("bstack:options", browserstackOptions);
+
+ return capabilities;
+ }
+}
diff --git a/src/test/java/com/qa/util/SuiteAlterer.java b/src/test/java/com/qa/util/SuiteAlterer.java
new file mode 100644
index 0000000..383ec0d
--- /dev/null
+++ b/src/test/java/com/qa/util/SuiteAlterer.java
@@ -0,0 +1,63 @@
+package com.qa.util;
+
+import org.apache.commons.codec.binary.Base64;
+import org.json.JSONObject;
+import org.testng.IAlterSuiteListener;
+import org.testng.xml.XmlSuite;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+public class SuiteAlterer implements IAlterSuiteListener {
+ @Override
+ public void alter(List suites) {
+ int count=5;
+ if(System.getProperty("threadCount")==null && System.getProperty("browser-type").equalsIgnoreCase("remote")) {
+ // This will fetch the parallel_sessions_max_allowed for the account and set the threadCount to it
+ URL url = null;
+ try {
+ url = new URL("https://api.browserstack.com/automate/plan.json");
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ String auth = System.getenv("BROWSERSTACK_USERNAME") + ":" + System.getenv("BROWSERSTACK_ACCESS_KEY");
+ byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.UTF_8));
+ String authHeaderValue = "Basic " + new String(encodedAuth);
+ HttpURLConnection http = null;
+ try {
+ http = (HttpURLConnection) url.openConnection ();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ http.setRequestProperty("Authorization", authHeaderValue);
+ BufferedReader in = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(http.getInputStream()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ String inputLine = "";
+ StringBuffer response = new StringBuffer();
+ while (true) {
+ try {
+ if (!((inputLine = in.readLine()) != null)) break;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ response.append(inputLine);
+ }
+ JSONObject myResponse = new JSONObject(response.toString());
+ count = (int) myResponse.get("parallel_sessions_max_allowed");
+ }else if(System.getProperty("threadCount")!=null)
+ //Sets the thread count value via CLI
+ count = Integer.parseInt(System.getProperty("threadCount"));
+ XmlSuite suite = suites.get(0);
+ suite.setDataProviderThreadCount(count);
+ }
+}
diff --git a/src/test/resources/Features/E2E.feature b/src/test/resources/Features/E2E.feature
new file mode 100644
index 0000000..90055d7
--- /dev/null
+++ b/src/test/resources/Features/E2E.feature
@@ -0,0 +1,19 @@
+#Author: Abdul Qadir Khan
+#Date: 06/12/21
+#Description: Demonstrate Cucumber Java framework with BS Automate product
+@e2e @regression
+Feature: E2E Flow
+ Scenario Outline: TC-3294 Signed in User makes a Purchase for a product with Price and Vendor filters
+ Given User is on home page
+ When User clicks on sign in link
+ And User enters and and clicks on sign in
+ And is signed in to the App
+ And User clicks on Apple in Vendor filter
+ And User sorts the search result by Price: Low to High
+ And User can see the list of Apple products with Price in ascending order
+ And User clicks on Add to cart for the iPhone XR and clicks on CHECKOUT
+ And User enters ,,,, and clicks SUBMIT
+ Then User can see the order id for the product
+ Examples:
+ |username|password|first|last|address|state|postal|
+ |"demouser"|"testingisfun99"|"first"|"last"|"test"|"test state"|1234|
diff --git a/src/test/resources/Features/Offers.feature b/src/test/resources/Features/Offers.feature
new file mode 100644
index 0000000..b181f7b
--- /dev/null
+++ b/src/test/resources/Features/Offers.feature
@@ -0,0 +1,16 @@
+#Author: Abdul Qadir Khan
+#Date: 14/12/21
+#Description: Demonstrate Cucumber Java framework with BS Automate product
+ @test @regression
+ Feature: Offers for user in Mumbai area
+ @offers
+ Scenario Outline: Signed in user can see promotional offers under Offers for Mumbai Location
+ Given User is on home page
+ When User clicks on sign in link
+ And User enters and and clicks on sign in
+ And is signed in to the App
+ And User Clicks on Offers
+ Then User can see offers list for Mumbai location
+ Examples:
+ | username | password |
+ |"fav_user"|"testingisfun99"|
\ No newline at end of file
diff --git a/src/test/resources/Features/Users.feature b/src/test/resources/Features/Users.feature
new file mode 100644
index 0000000..7bf8cbe
--- /dev/null
+++ b/src/test/resources/Features/Users.feature
@@ -0,0 +1,35 @@
+#Author: Abdul Qadir Khan
+#Date: 20/12/21
+#Description: Demonstrate Cucumber Java framework with BS Automate product
+@users @regression
+Feature: Different Users use cases in BStackDemo
+ Background:
+ Given User is on home page
+ When User clicks on sign in link
+
+ @noimage @fail
+ Scenario Outline: TC-1665 Login as User with no image loaded
+ When User enters and and clicks on sign in
+ And is signed in to the App
+ Then Product images are not loaded for the
+ Examples:
+ | username | password |
+ |"image_not_loading_user"|"testingisfun99"|
+
+ @orders
+ Scenario Outline: TC-1666 Login as existing user to verify orders
+ When User enters and and clicks on sign in
+ And is signed in to the App
+ And User clicks on Orders
+ Then Existing orders are shown for the
+ Examples:
+ | username | password |
+ |"existing_orders_user"|"testingisfun99"|
+
+ @locked
+ Scenario Outline: TC-1667 Login as a locked User
+ When User enters and and clicks on sign in
+ Then gets Your account has been locked.
+ Examples:
+ | username | password |
+ |"locked_user"|"testingisfun99"|
diff --git a/src/test/resources/Reports/Report.txt b/src/test/resources/Reports/Report.txt
new file mode 100644
index 0000000..86ec0ed
--- /dev/null
+++ b/src/test/resources/Reports/Report.txt
@@ -0,0 +1,3 @@
+Reports will be generated based on timestamp and will have pdf and html formats.
+The screenshots are attached in the same directory and can be zipped and shared over email without losing the screenshots in the folder.
+These configurations can be changed from the extent.properties file.
\ No newline at end of file
diff --git a/src/test/resources/browserstack/conf/Run_Parallel_Test/parallel.conf.json b/src/test/resources/browserstack/conf/Run_Parallel_Test/parallel.conf.json
new file mode 100644
index 0000000..ae18182
--- /dev/null
+++ b/src/test/resources/browserstack/conf/Run_Parallel_Test/parallel.conf.json
@@ -0,0 +1,33 @@
+{
+ "server": "hub-cloud.browserstack.com",
+ "user": "",
+ "key": "",
+
+ "capabilities": {
+ "projectName": "Cucumber Java",
+ "buildName": "BDDParallel-4",
+ "sessionName": "BS Suite",
+ "debug": "true",
+ "networkLogs": "true",
+ "consoleLogs": "errors"
+ },
+
+ "environments": {
+ "env1": {
+ "browser": "Edge",
+ "browser_version": "latest",
+ "envOptions": {
+ "os": "Windows",
+ "osVersion": "11"
+ }
+ },
+ "env2":{
+ "browser": "Safari",
+ "browser_version": "15.3",
+ "envOptions": {
+ "os": "OS X",
+ "osVersion": "Monterey"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/browserstack/conf/Run_Parallel_Test/parallel.testng.xml b/src/test/resources/browserstack/conf/Run_Parallel_Test/parallel.testng.xml
new file mode 100644
index 0000000..28058f2
--- /dev/null
+++ b/src/test/resources/browserstack/conf/Run_Parallel_Test/parallel.testng.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/browserstack/conf/Run_Single_Test/single.conf.json b/src/test/resources/browserstack/conf/Run_Single_Test/single.conf.json
new file mode 100644
index 0000000..590f319
--- /dev/null
+++ b/src/test/resources/browserstack/conf/Run_Single_Test/single.conf.json
@@ -0,0 +1,25 @@
+{
+ "server": "hub-cloud.browserstack.com",
+ "user": "",
+ "key": "",
+
+ "capabilities": {
+ "projectName": "Cucumber Java",
+ "buildName": "BStackDemo BDD",
+ "sessionName": "BS Suite",
+ "debug": "true",
+ "networkLogs": "true",
+ "consoleLogs": "errors"
+ },
+
+ "environments": {
+ "env": {
+ "browser": "Chrome",
+ "browser_version": "latest",
+ "envOptions": {
+ "os": "OS X",
+ "osVersion": "Monterey"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/browserstack/conf/Run_Single_Test/single.testng.xml b/src/test/resources/browserstack/conf/Run_Single_Test/single.testng.xml
new file mode 100644
index 0000000..56d05e6
--- /dev/null
+++ b/src/test/resources/browserstack/conf/Run_Single_Test/single.testng.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/extent-config.xml b/src/test/resources/extent-config.xml
new file mode 100644
index 0000000..8b640a6
--- /dev/null
+++ b/src/test/resources/extent-config.xml
@@ -0,0 +1,41 @@
+
+
+
+
+ standard
+
+
+ UTF-8
+
+
+ https
+
+
+ Cucumber TestNG Report
+
+
+ BS BDD Demo Report
+
+
+ yyyy-MM-dd
+
+
+ HH:mm:ss
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/extent.properties b/src/test/resources/extent.properties
new file mode 100644
index 0000000..4c7f509
--- /dev/null
+++ b/src/test/resources/extent.properties
@@ -0,0 +1,14 @@
+extent.reporter.spark.start=true
+extent.reporter.spark.config=src/test/resources/extent-config.xml
+
+basefolder.name=src/test/resources/Reports/SparkReport
+basefolder.datetimepattern=MMM-d-YY HH-mm-ss
+
+extent.reporter.spark.vieworder=dashboard,test,category,exception,author,device,log
+extent.reporter.spark.out=Reports/Spark.html
+
+screenshot.dir=Screenshots/
+screenshot.rel.path=../Screenshots/
+
+extent.reporter.pdf.start=true
+extent.reporter.pdf.out=Reports/Extent.pdf
\ No newline at end of file
diff --git a/testng.xml b/testng.xml
new file mode 100644
index 0000000..df49ee9
--- /dev/null
+++ b/testng.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file