From f34f9da1455f4df221724197d9b796f9d4cf141a Mon Sep 17 00:00:00 2001
From: Abhi Singh <abhi.s@browserstack.com>
Date: Mon, 14 Mar 2022 17:46:24 +0530
Subject: [PATCH 01/14] adds selenium 4 capabilities

---
 pom.xml                                            |  2 +-
 .../com/browserstack/stepdefs/SearchSteps.java     | 14 +++++++++++---
 src/test/java/com/browserstack/util/Utility.java   | 12 ++++++++----
 src/test/resources/conf/local.conf.json            | 10 ++++++----
 src/test/resources/conf/parallel.conf.json         | 14 ++++++++------
 src/test/resources/conf/single.conf.json           |  8 +++++---
 6 files changed, 39 insertions(+), 21 deletions(-)

diff --git a/pom.xml b/pom.xml
index 37a685e..b37da18 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,7 @@
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
         <cucumber.version>6.9.1</cucumber.version>
-        <selenium-java.version>3.141.59</selenium-java.version>
+        <selenium-java.version>4.1.2</selenium-java.version>
         <browserstack-local-java.version>1.0.6</browserstack-local-java.version>
         <json-simple.version>1.1.1</json-simple.version>
         <slf4j.version>1.7.30</slf4j.version>
diff --git a/src/test/java/com/browserstack/stepdefs/SearchSteps.java b/src/test/java/com/browserstack/stepdefs/SearchSteps.java
index acce60e..76b12c3 100644
--- a/src/test/java/com/browserstack/stepdefs/SearchSteps.java
+++ b/src/test/java/com/browserstack/stepdefs/SearchSteps.java
@@ -14,6 +14,8 @@
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.remote.DesiredCapabilities;
 import org.openqa.selenium.remote.RemoteWebDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.net.URL;
 import java.util.HashMap;
@@ -26,6 +28,7 @@ public class SearchSteps {
     private WebDriver driver;
     private SearchPage searchPage;
     private Local l;
+    private final Logger LOG = LoggerFactory.getLogger(SearchSteps.class);
 
     @Before
     public void setUp(Scenario scenario) throws Exception {
@@ -34,10 +37,12 @@ public void setUp(Scenario scenario) throws Exception {
 
         DesiredCapabilities caps = new DesiredCapabilities(capability);
         caps.setCapability("name", scenario.getName());
-        if (caps.getCapability("browserstack.local")!=null && caps.getCapability("browserstack.local").toString().equals("true")) {
+        Map<String, Object> bstackOptions = (Map<String, Object>) caps.getCapability("bstack:options");
+        if (bstackOptions!=null && bstackOptions.get("local")!=null && bstackOptions.get("local").toString().equals("true")) {
+            LOG.info("Start BrowserStack Local");
             l = new Local();
             Map<String, String> options = new HashMap<String, String>();
-            options.put("key", caps.getCapability("browserstack.key").toString());
+            options.put("key", bstackOptions.get("accessKey").toString());
             l.start(options);
         }
 
@@ -77,6 +82,9 @@ public void teardown(Scenario scenario) throws Exception {
         }
         Thread.sleep(2000);
         driver.quit();
-        if (l != null) l.stop();
+        if (l != null) {
+            l.stop();
+            LOG.info("Stop BrowserStack Local");
+        }
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/browserstack/util/Utility.java b/src/test/java/com/browserstack/util/Utility.java
index 1f30bd1..af92e60 100644
--- a/src/test/java/com/browserstack/util/Utility.java
+++ b/src/test/java/com/browserstack/util/Utility.java
@@ -15,13 +15,13 @@ public static JSONObject getCombinedCapability(Map<String, String> envCapabiliti
         Iterator it = envCapabilities.entrySet().iterator();
         while (it.hasNext()) {
             Map.Entry pair = (Map.Entry) it.next();
-            capabilities.put(pair.getKey().toString(), pair.getValue().toString());
+            capabilities.put(pair.getKey().toString(), pair.getValue());
         }
         it = commonCapabilities.entrySet().iterator();
         while (it.hasNext()) {
             Map.Entry pair = (Map.Entry) it.next();
             if (capabilities.get(pair.getKey().toString()) == null) {
-                capabilities.put(pair.getKey().toString(), pair.getValue().toString());
+                capabilities.put(pair.getKey().toString(), pair.getValue());
             }
         }
         String username = System.getenv("BROWSERSTACK_USERNAME");
@@ -33,8 +33,12 @@ public static JSONObject getCombinedCapability(Map<String, String> envCapabiliti
         if(accessKey == null) {
             accessKey = (String) config.get("key");
         }
-        capabilities.put("browserstack.user", username);
-        capabilities.put("browserstack.key", accessKey);
+        JSONObject bstackOptions = (JSONObject) capabilities.get("bstack:options");
+        if(bstackOptions == null)
+            bstackOptions = new JSONObject();
+        bstackOptions.put("userName", username);
+        bstackOptions.put("accessKey", accessKey);
+        capabilities.put("bstack:options", bstackOptions);
         return capabilities;
     }
 
diff --git a/src/test/resources/conf/local.conf.json b/src/test/resources/conf/local.conf.json
index 306fa86..5b1f6d4 100644
--- a/src/test/resources/conf/local.conf.json
+++ b/src/test/resources/conf/local.conf.json
@@ -4,12 +4,14 @@
   "key": "BROWSERSTACK_ACCESS_KEY",
 
   "capabilities": {
-    "build": "cucumber-java-browserstack",
-    "browserstack.debug": true,
-    "browserstack.local": true
+    "bstack:options" : {
+      "buildName" : "cucumber-java-browserstack",
+      "local" : "true",
+      "debug" : "true"
+    }
   },
 
   "environments": [{
-    "browser": "chrome"
+    "browserName": "chrome"
   }]
 }
diff --git a/src/test/resources/conf/parallel.conf.json b/src/test/resources/conf/parallel.conf.json
index 67d5177..8be305b 100644
--- a/src/test/resources/conf/parallel.conf.json
+++ b/src/test/resources/conf/parallel.conf.json
@@ -4,22 +4,24 @@
   "key": "BROWSERSTACK_ACCESS_KEY",
 
   "capabilities": {
-    "build": "cucumber-java-browserstack",
-    "browserstack.debug": true
+    "bstack:options" : {
+      "buildName" : "cucumber-java-browserstack",
+      "debug" : "true"
+    }
   },
 
   "environments": [
     {
-      "browser": "chrome"
+      "browserName": "chrome"
     },
     {
-      "browser": "firefox"
+      "browserName": "firefox"
     },
     {
-      "browser": "safari"
+      "browserName": "safari"
     },
     {
-      "browser": "Internet Explorer"
+      "browserName": "IE"
     }
   ]
 }
diff --git a/src/test/resources/conf/single.conf.json b/src/test/resources/conf/single.conf.json
index 0f2dc7a..62547a1 100644
--- a/src/test/resources/conf/single.conf.json
+++ b/src/test/resources/conf/single.conf.json
@@ -4,11 +4,13 @@
   "key": "BROWSERSTACK_ACCESS_KEY",
 
   "capabilities": {
-    "build": "cucumber-java-browserstack",
-    "browserstack.debug": true
+    "bstack:options" : {
+      "buildName" : "cucumber-java-browserstack",
+      "debug" : "true"
+    }
   },
 
   "environments": [{
-    "browser": "chrome"
+    "browserName": "chrome"
   }]
 }

From 15a7fd4731477bbfcbe219d07b2426b6c485234d Mon Sep 17 00:00:00 2001
From: Abhi Singh <abhi.s@browserstack.com>
Date: Tue, 15 Mar 2022 16:15:40 +0530
Subject: [PATCH 02/14] adds EoF

---
 src/test/java/com/browserstack/stepdefs/SearchSteps.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/java/com/browserstack/stepdefs/SearchSteps.java b/src/test/java/com/browserstack/stepdefs/SearchSteps.java
index 76b12c3..34fbd88 100644
--- a/src/test/java/com/browserstack/stepdefs/SearchSteps.java
+++ b/src/test/java/com/browserstack/stepdefs/SearchSteps.java
@@ -87,4 +87,4 @@ public void teardown(Scenario scenario) throws Exception {
             LOG.info("Stop BrowserStack Local");
         }
     }
-}
\ No newline at end of file
+}

From fdfcc46d195fe55309be6c9c696a61e93eb10a46 Mon Sep 17 00:00:00 2001
From: Abhi Singh <abhi.s@browserstack.com>
Date: Tue, 15 Mar 2022 16:16:11 +0530
Subject: [PATCH 03/14] fixes JSON formatting

---
 src/test/resources/conf/local.conf.json    | 8 ++++----
 src/test/resources/conf/parallel.conf.json | 6 +++---
 src/test/resources/conf/single.conf.json   | 6 +++---
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/test/resources/conf/local.conf.json b/src/test/resources/conf/local.conf.json
index 5b1f6d4..6703b3a 100644
--- a/src/test/resources/conf/local.conf.json
+++ b/src/test/resources/conf/local.conf.json
@@ -4,10 +4,10 @@
   "key": "BROWSERSTACK_ACCESS_KEY",
 
   "capabilities": {
-    "bstack:options" : {
-      "buildName" : "cucumber-java-browserstack",
-      "local" : "true",
-      "debug" : "true"
+    "bstack:options": {
+      "buildName": "cucumber-java-browserstack",
+      "local": "true",
+      "debug": "true"
     }
   },
 
diff --git a/src/test/resources/conf/parallel.conf.json b/src/test/resources/conf/parallel.conf.json
index 8be305b..3a15cff 100644
--- a/src/test/resources/conf/parallel.conf.json
+++ b/src/test/resources/conf/parallel.conf.json
@@ -4,9 +4,9 @@
   "key": "BROWSERSTACK_ACCESS_KEY",
 
   "capabilities": {
-    "bstack:options" : {
-      "buildName" : "cucumber-java-browserstack",
-      "debug" : "true"
+    "bstack:options": {
+      "buildName": "cucumber-java-browserstack",
+      "debug": "true"
     }
   },
 
diff --git a/src/test/resources/conf/single.conf.json b/src/test/resources/conf/single.conf.json
index 62547a1..ee140d3 100644
--- a/src/test/resources/conf/single.conf.json
+++ b/src/test/resources/conf/single.conf.json
@@ -4,9 +4,9 @@
   "key": "BROWSERSTACK_ACCESS_KEY",
 
   "capabilities": {
-    "bstack:options" : {
-      "buildName" : "cucumber-java-browserstack",
-      "debug" : "true"
+    "bstack:options": {
+      "buildName": "cucumber-java-browserstack",
+      "debug": "true"
     }
   },
 

From 4cf6bef004d7ea9cc291d2ad0837858286030e44 Mon Sep 17 00:00:00 2001
From: Abhi Singh <abhi.s@browserstack.com>
Date: Wed, 30 Mar 2022 22:50:39 +0530
Subject: [PATCH 04/14] added BstackDemo test

---
 .../browserstack/pageobjects/HomePage.java    | 52 +++++++++++++++++++
 .../browserstack/pageobjects/SearchPage.java  | 23 --------
 .../{SearchSteps.java => StackDemoSteps.java} | 32 ++++++------
 .../resources/features/test/Search.feature    |  6 ---
 .../resources/features/test/StackDemo.feature |  6 +++
 5 files changed, 73 insertions(+), 46 deletions(-)
 create mode 100644 src/test/java/com/browserstack/pageobjects/HomePage.java
 delete mode 100644 src/test/java/com/browserstack/pageobjects/SearchPage.java
 rename src/test/java/com/browserstack/stepdefs/{SearchSteps.java => StackDemoSteps.java} (75%)
 delete mode 100644 src/test/resources/features/test/Search.feature
 create mode 100644 src/test/resources/features/test/StackDemo.feature

diff --git a/src/test/java/com/browserstack/pageobjects/HomePage.java b/src/test/java/com/browserstack/pageobjects/HomePage.java
new file mode 100644
index 0000000..33dfd00
--- /dev/null
+++ b/src/test/java/com/browserstack/pageobjects/HomePage.java
@@ -0,0 +1,52 @@
+package com.browserstack.pageobjects;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.time.Duration;
+
+public class HomePage {
+    private WebDriver webDriver;
+
+    private String selectedProductName;
+
+    public HomePage(WebDriver webDriver) {
+        this.webDriver = webDriver;
+        this.selectedProductName = "";
+    }
+
+    private By firstProductName = By.xpath("//*[@id=\"1\"]/p");
+
+    private By firstProductAddToCartButton = By.xpath("//*[@id=\"1\"]/div[4]");
+
+    private By cartPane = By.cssSelector(".float-cart__content");
+
+    private By productCartText = By.xpath("//*[@id=\"__next\"]/div/div/div[2]/div[2]/div[2]/div/div[3]/p[1]");
+
+    public void selectFirstProductName() {
+        String firstProduct = webDriver.findElement(firstProductName).getText();
+        setSelectedProductName(firstProduct);
+    }
+
+    public void clickAddToCartButton() {
+        webDriver.findElement(firstProductAddToCartButton).click();
+    }
+
+    public void waitForCartToOpen() {
+        new WebDriverWait(webDriver, Duration.ofSeconds(30)).until(ExpectedConditions.visibilityOfAllElementsLocatedBy(cartPane));
+    }
+
+    public String getProductCartText() {
+        return webDriver.findElement(productCartText).getText();
+    }
+
+    public void setSelectedProductName(String selectedProductName) {
+        this.selectedProductName = selectedProductName;
+    }
+
+    public String getSelectedProductName() {
+        return selectedProductName;
+    }
+}
diff --git a/src/test/java/com/browserstack/pageobjects/SearchPage.java b/src/test/java/com/browserstack/pageobjects/SearchPage.java
deleted file mode 100644
index e7cb592..0000000
--- a/src/test/java/com/browserstack/pageobjects/SearchPage.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.browserstack.pageobjects;
-
-import org.openqa.selenium.By;
-import org.openqa.selenium.WebDriver;
-
-public class SearchPage {
-    private WebDriver webDriver;
-
-    public SearchPage(WebDriver webDriver) {
-        this.webDriver = webDriver;
-    }
-
-    private By searchTermField = By.name("q");
-
-    public void enterSearchTerm(String searchTerm) {
-        webDriver.findElement(searchTermField).sendKeys(searchTerm);
-    }
-
-    public void submitSearch() {
-        webDriver.findElement(searchTermField).submit();
-    }
-
-}
diff --git a/src/test/java/com/browserstack/stepdefs/SearchSteps.java b/src/test/java/com/browserstack/stepdefs/StackDemoSteps.java
similarity index 75%
rename from src/test/java/com/browserstack/stepdefs/SearchSteps.java
rename to src/test/java/com/browserstack/stepdefs/StackDemoSteps.java
index 34fbd88..4cb50be 100644
--- a/src/test/java/com/browserstack/stepdefs/SearchSteps.java
+++ b/src/test/java/com/browserstack/stepdefs/StackDemoSteps.java
@@ -2,7 +2,7 @@
 
 import com.browserstack.TestRunner;
 import com.browserstack.local.Local;
-import com.browserstack.pageobjects.SearchPage;
+import com.browserstack.pageobjects.HomePage;
 import com.browserstack.util.Utility;
 import io.cucumber.java.After;
 import io.cucumber.java.Before;
@@ -24,11 +24,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-public class SearchSteps {
+public class StackDemoSteps {
     private WebDriver driver;
-    private SearchPage searchPage;
+    private HomePage homePage;
     private Local l;
-    private final Logger LOG = LoggerFactory.getLogger(SearchSteps.class);
+    private final Logger LOG = LoggerFactory.getLogger(StackDemoSteps.class);
 
     @Before
     public void setUp(Scenario scenario) throws Exception {
@@ -47,7 +47,7 @@ public void setUp(Scenario scenario) throws Exception {
         }
 
         driver = new RemoteWebDriver(new URL(URL), caps);
-        searchPage = new SearchPage(driver);
+        homePage = new HomePage(driver);
     }
 
     @Given("^I am on the website '(.+)'$")
@@ -56,20 +56,21 @@ public void I_am_on_the_website(String url) throws Throwable {
         Thread.sleep(2000);
     }
 
-    @When("^I submit the search term '(.+)'$")
-    public void I_submit_the_search_term(String searchTerm) throws Throwable {
-        searchPage.enterSearchTerm(searchTerm);
-        searchPage.submitSearch();
+    @When("^I select a product and click on 'Add to cart' button")
+    public void I_select_a_product_and_add_to_cart() throws Throwable {
+        homePage.selectFirstProductName();
+        homePage.clickAddToCartButton();
         Thread.sleep(2000);
     }
 
-    @Then("the page title should be '(.+)'$")
-    public void I_should_see_pagetitle(String expectedTitle) throws Throwable {
-        assertEquals(expectedTitle, driver.getTitle());
+    @Then("the product should be added to cart")
+    public void product_should_be_added_to_cart() {
+        homePage.waitForCartToOpen();
+        assertEquals(homePage.getSelectedProductName(), homePage.getProductCartText());
     }
 
     @Then("the page should contain '(.+)'$")
-    public void page_should_contain(String expectedTitle) throws Throwable {
+    public void page_should_contain(String expectedTitle) {
         assertTrue(driver.getPageSource().contains(expectedTitle));
     }
 
@@ -82,9 +83,6 @@ public void teardown(Scenario scenario) throws Exception {
         }
         Thread.sleep(2000);
         driver.quit();
-        if (l != null) {
-            l.stop();
-            LOG.info("Stop BrowserStack Local");
-        }
+        if (l != null) l.stop();
     }
 }
diff --git a/src/test/resources/features/test/Search.feature b/src/test/resources/features/test/Search.feature
deleted file mode 100644
index 48e0bac..0000000
--- a/src/test/resources/features/test/Search.feature
+++ /dev/null
@@ -1,6 +0,0 @@
-Feature: Automatically correct mistyped search terms
-
-  Scenario: Enter search term and get page title
-    Given I am on the website 'http://www.google.com'
-    When I submit the search term 'BrowserStack'
-    Then the page title should be 'BrowserStack - Google Search'
\ No newline at end of file
diff --git a/src/test/resources/features/test/StackDemo.feature b/src/test/resources/features/test/StackDemo.feature
new file mode 100644
index 0000000..9e3a2e5
--- /dev/null
+++ b/src/test/resources/features/test/StackDemo.feature
@@ -0,0 +1,6 @@
+Feature: BrowserStack Demo
+
+  Scenario: Add product to cart
+    Given I am on the website 'https://www.bstackdemo.com'
+    When I select a product and click on 'Add to cart' button
+    Then the product should be added to cart

From c514e39c299f26906eaf452546e3f4952aed9236 Mon Sep 17 00:00:00 2001
From: Abhi Singh <abhi.s@browserstack.com>
Date: Thu, 31 Mar 2022 16:37:53 +0530
Subject: [PATCH 05/14] removed IE browser

---
 pom.xml                                    | 2 +-
 src/test/resources/conf/parallel.conf.json | 3 ---
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index b37da18..07ee094 100644
--- a/pom.xml
+++ b/pom.xml
@@ -144,7 +144,7 @@
                                 </property>
                                 <property>
                                     <key>parallel-count</key>
-                                    <value>5</value>
+                                    <value>3</value>
                                 </property>
                                 <property>
                                     <key>features</key>
diff --git a/src/test/resources/conf/parallel.conf.json b/src/test/resources/conf/parallel.conf.json
index 3a15cff..6590da0 100644
--- a/src/test/resources/conf/parallel.conf.json
+++ b/src/test/resources/conf/parallel.conf.json
@@ -19,9 +19,6 @@
     },
     {
       "browserName": "safari"
-    },
-    {
-      "browserName": "IE"
     }
   ]
 }

From f3bb60b8d4dc5429fafeac94728eb87aac4630b0 Mon Sep 17 00:00:00 2001
From: Abhi Singh <abhi.s@browserstack.com>
Date: Fri, 22 Apr 2022 15:44:59 +0530
Subject: [PATCH 06/14] Added TestNG runner

---
 .gitignore                                    |   1 +
 README.md                                     |   6 +-
 pom.xml                                       | 133 ++++++++----------
 .../RunWebDriverCucumberTests.java            |  80 +++++++++++
 .../java/com/browserstack/TestRunner.java     |  74 ----------
 .../browserstack/stepdefs/StackDemoSteps.java |  33 +----
 .../java/com/browserstack/util/Utility.java   |  21 +++
 .../webdriver/LazyInitWebDriverIterator.java  |  76 ++++++++++
 .../webdriver/ManagedWebDriver.java           |  33 +++++
 .../webdriver/WebDriverFactory.java           |  73 ++++++++++
 src/test/resources/testng.xml                 |   9 ++
 11 files changed, 358 insertions(+), 181 deletions(-)
 create mode 100644 src/test/java/com/browserstack/RunWebDriverCucumberTests.java
 delete mode 100644 src/test/java/com/browserstack/TestRunner.java
 create mode 100644 src/test/java/com/browserstack/webdriver/LazyInitWebDriverIterator.java
 create mode 100644 src/test/java/com/browserstack/webdriver/ManagedWebDriver.java
 create mode 100644 src/test/java/com/browserstack/webdriver/WebDriverFactory.java
 create mode 100644 src/test/resources/testng.xml

diff --git a/.gitignore b/.gitignore
index 65692b7..0959467 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@ target/
 *.iml
 
 local.log
+reports
diff --git a/README.md b/README.md
index 7ef6726..3f5331d 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,9 @@
 * Update `*.conf.js` files inside the `src/test/resources/conf/` directory to update desired capabilities.
 
 ## Running your tests
-* To run a single test, run `mvn compile exec:java -P single`
-* To run parallel tests, run `mvn compile exec:java -P parallel` 
-* To run local tests, run `mvn compile exec:java -P local`
+* To run a single test, run `mvn test -P single`
+* To run parallel tests, run `mvn test -P parallel` 
+* To run local tests, run `mvn test -P local`
 
 
 
diff --git a/pom.xml b/pom.xml
index 07ee094..8c097af 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,7 @@
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
         <cucumber.version>6.9.1</cucumber.version>
+        <testng.version>7.4.0</testng.version>
         <selenium-java.version>4.1.2</selenium-java.version>
         <browserstack-local-java.version>1.0.6</browserstack-local-java.version>
         <json-simple.version>1.1.1</json-simple.version>
@@ -34,12 +35,24 @@
             <version>${slf4j.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <version>${testng.version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>io.cucumber</groupId>
             <artifactId>cucumber-java</artifactId>
             <version>${cucumber.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>io.cucumber</groupId>
+            <artifactId>cucumber-testng</artifactId>
+            <version>${cucumber.version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.seleniumhq.selenium</groupId>
             <artifactId>selenium-java</artifactId>
@@ -87,31 +100,21 @@
             <build>
                 <plugins>
                     <plugin>
-                        <groupId>org.codehaus.mojo</groupId>
-                        <artifactId>exec-maven-plugin</artifactId>
-                        <version>3.0.0</version>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>3.0.0-M5</version>
                         <configuration>
-                            <classpathScope>test</classpathScope>
-                            <mainClass>com.browserstack.TestRunner</mainClass>
-                            <cleanupDaemonThreads>false</cleanupDaemonThreads>
-                            <systemProperties>
-                                <property>
-                                    <key>cucumber.publish.quiet</key>
-                                    <value>true</value>
-                                </property>
-                                <property>
-                                    <key>cucumber.plugin</key>
-                                    <value>html:target/cucumber-html-reports.html</value>
-                                </property>
-                                <property>
-                                    <key>caps</key>
-                                    <value>src/test/resources/conf/single.conf.json</value>
-                                </property>
-                                <property>
-                                    <key>features</key>
-                                    <value>src/test/resources/features/test</value>
-                                </property>
-                            </systemProperties>
+                            <suiteXmlFiles>
+                                <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
+                            </suiteXmlFiles>
+                            <argLine>
+                                -Ddataproviderthreadcount=1
+                            </argLine>
+                            <systemPropertyVariables>
+                                <caps>src/test/resources/conf/single.conf.json</caps>
+                                <cucumber.publish.quiet>true</cucumber.publish.quiet>
+                                <cucumber.features>src/test/resources/features/test</cucumber.features>
+                            </systemPropertyVariables>
                         </configuration>
                     </plugin>
                 </plugins>
@@ -122,35 +125,27 @@
             <build>
                 <plugins>
                     <plugin>
-                        <groupId>org.codehaus.mojo</groupId>
-                        <artifactId>exec-maven-plugin</artifactId>
-                        <version>3.0.0</version>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>3.0.0-M5</version>
                         <configuration>
-                            <classpathScope>test</classpathScope>
-                            <mainClass>com.browserstack.TestRunner</mainClass>
-                            <cleanupDaemonThreads>false</cleanupDaemonThreads>
-                            <systemProperties>
+                            <suiteXmlFiles>
+                                <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
+                            </suiteXmlFiles>
+                            <argLine>
+                                -Ddataproviderthreadcount=4
+                            </argLine>
+                            <systemPropertyVariables>
+                                <caps>src/test/resources/conf/parallel.conf.json</caps>
+                                <cucumber.publish.quiet>true</cucumber.publish.quiet>
+                                <cucumber.features>src/test/resources/features/test</cucumber.features>
+                            </systemPropertyVariables>
+                            <properties>
                                 <property>
-                                    <key>cucumber.publish.quiet</key>
-                                    <value>true</value>
+                                    <name>parallel</name>
+                                    <value>methods</value>
                                 </property>
-                                <property>
-                                    <key>cucumber.plugin</key>
-                                    <value>html:target/cucumber-html-reports.html</value>
-                                </property>
-                                <property>
-                                    <key>caps</key>
-                                    <value>src/test/resources/conf/parallel.conf.json</value>
-                                </property>
-                                <property>
-                                    <key>parallel-count</key>
-                                    <value>3</value>
-                                </property>
-                                <property>
-                                    <key>features</key>
-                                    <value>src/test/resources/features/test</value>
-                                </property>
-                            </systemProperties>
+                            </properties>
                         </configuration>
                     </plugin>
                 </plugins>
@@ -161,31 +156,21 @@
             <build>
                 <plugins>
                     <plugin>
-                        <groupId>org.codehaus.mojo</groupId>
-                        <artifactId>exec-maven-plugin</artifactId>
-                        <version>3.0.0</version>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>3.0.0-M5</version>
                         <configuration>
-                            <classpathScope>test</classpathScope>
-                            <mainClass>com.browserstack.TestRunner</mainClass>
-                            <cleanupDaemonThreads>false</cleanupDaemonThreads>
-                            <systemProperties>
-                                <property>
-                                    <key>cucumber.publish.quiet</key>
-                                    <value>true</value>
-                                </property>
-                                <property>
-                                    <key>cucumber.plugin</key>
-                                    <value>html:target/cucumber-html-reports.html</value>
-                                </property>
-                                <property>
-                                    <key>caps</key>
-                                    <value>src/test/resources/conf/local.conf.json</value>
-                                </property>
-                                <property>
-                                    <key>features</key>
-                                    <value>src/test/resources/features/localtest</value>
-                                </property>
-                            </systemProperties>
+                            <suiteXmlFiles>
+                                <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
+                            </suiteXmlFiles>
+                            <argLine>
+                                -Ddataproviderthreadcount=1
+                            </argLine>
+                            <systemPropertyVariables>
+                                <caps>src/test/resources/conf/local.conf.json</caps>
+                                <cucumber.publish.quiet>true</cucumber.publish.quiet>
+                                <cucumber.features>src/test/resources/features/localtest</cucumber.features>
+                            </systemPropertyVariables>
                         </configuration>
                     </plugin>
                 </plugins>
diff --git a/src/test/java/com/browserstack/RunWebDriverCucumberTests.java b/src/test/java/com/browserstack/RunWebDriverCucumberTests.java
new file mode 100644
index 0000000..cdf1d2d
--- /dev/null
+++ b/src/test/java/com/browserstack/RunWebDriverCucumberTests.java
@@ -0,0 +1,80 @@
+package com.browserstack;
+
+import com.browserstack.local.Local;
+import com.browserstack.util.Utility;
+import com.browserstack.webdriver.LazyInitWebDriverIterator;
+import com.browserstack.webdriver.ManagedWebDriver;
+import io.cucumber.testng.CucumberOptions;
+import io.cucumber.testng.FeatureWrapper;
+import io.cucumber.testng.PickleWrapper;
+import io.cucumber.testng.TestNGCucumberRunner;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.util.Iterator;
+
+@CucumberOptions(
+        glue = "com.browserstack.stepdefs",
+        plugin = {
+                "pretty",
+                "html:reports/tests/cucumber/html",
+                "timeline:reports/tests/cucumber/timeline",
+                "junit:reports/tests/cucumber/junit/cucumber.xml",
+                "testng:reports/tests/cucumber/testng/cucumber.xml",
+                "json:reports/tests/cucumber/json/cucumber.json"
+        }
+)
+public class RunWebDriverCucumberTests {
+
+    private TestNGCucumberRunner testNGCucumberRunner;
+    private Local local;
+    private static final ThreadLocal<ManagedWebDriver> threadLocalWebDriver = new ThreadLocal<>();
+
+    @BeforeClass(alwaysRun = true)
+    public void setUpClass() {
+        testNGCucumberRunner = new TestNGCucumberRunner(this.getClass());
+    }
+
+    private synchronized static void setThreadLocalWebDriver(ManagedWebDriver managedWebDriver) {
+        threadLocalWebDriver.set(managedWebDriver);
+    }
+
+    public synchronized static ManagedWebDriver getManagedWebDriver() {
+        return threadLocalWebDriver.get();
+    }
+
+    @Test(groups = "cucumber", description = "Runs Cucumber Feature", dataProvider = "scenarios")
+    public void feature(PickleWrapper pickleWrapper, FeatureWrapper featureWrapper, ManagedWebDriver managedWebDriver) {
+        if(Utility.isLocal(managedWebDriver) && local==null){
+            local = new Local();
+            Utility.startLocal(local, managedWebDriver);
+        }
+        managedWebDriver.setTestName(pickleWrapper.getPickle().getName());
+        setThreadLocalWebDriver(managedWebDriver);
+        testNGCucumberRunner.runScenario(pickleWrapper.getPickle());
+    }
+
+    @DataProvider(name = "scenarios", parallel = true)
+    public Iterator<Object[]> scenarios() {
+        Object[][] scenarios = testNGCucumberRunner.provideScenarios();
+        return new LazyInitWebDriverIterator(true, scenarios);
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void tearDownClass() {
+        if(local != null){
+            try {
+                local.stop();
+            } catch (Exception e) {
+                throw new Error("Unable to stop BrowserStack Local.");
+            }
+        }
+        if (testNGCucumberRunner == null) {
+            return;
+        }
+        testNGCucumberRunner.finish();
+    }
+
+}
diff --git a/src/test/java/com/browserstack/TestRunner.java b/src/test/java/com/browserstack/TestRunner.java
deleted file mode 100644
index 1993e98..0000000
--- a/src/test/java/com/browserstack/TestRunner.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.browserstack;
-
-import com.browserstack.util.Utility;
-import io.cucumber.core.cli.Main;
-import org.apache.commons.lang3.StringUtils;
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class TestRunner {
-    public static ThreadLocal<JSONObject> threadLocalValue = new ThreadLocal<>();
-
-    public static void main(String[] args) throws IOException, ParseException {
-        JSONObject testConfig;
-        int threadCount = 1;
-        JSONParser parser = new JSONParser();
-        if (StringUtils.isNoneEmpty(System.getProperty("parallel-count")) && StringUtils.isNumeric(System.getProperty("parallel-count"))) {
-            threadCount = Integer.parseInt(System.getProperty("parallel-count"));
-        }
-        if (System.getProperty("caps") != null) {
-            testConfig = (JSONObject) parser.parse(new FileReader(System.getProperty("caps")));
-        } else {
-            testConfig = (JSONObject) parser.parse(new FileReader("src/test/resources/conf/single.conf.json"));
-        }
-        JSONArray environments = (JSONArray) testConfig.get("environments");
-        ExecutorService pool = Executors.newFixedThreadPool(threadCount);
-        for (Object obj : environments) {
-            JSONObject singleConfig = Utility.getCombinedCapability((Map<String, String>) obj, testConfig);
-            Runnable task = new Task(singleConfig, threadLocalValue, System.getProperty("features"), testConfig.get("server").toString());
-            pool.execute(task);
-        }
-        pool.shutdown();
-    }
-
-}
-
-class Task implements Runnable {
-    private JSONObject singleConfig;
-    private ThreadLocal<JSONObject> threadLocalValue;
-    private String features;
-    private String server;
-
-    public static Logger log = LoggerFactory.getLogger(TestRunner.class);
-
-    public Task(JSONObject singleConfig, ThreadLocal<JSONObject> threadLocalValue, String features, String server) {
-        this.singleConfig = singleConfig;
-        this.threadLocalValue = threadLocalValue;
-        this.features = features;
-        this.server = server;
-    }
-
-    public void run() {
-        System.setProperty("server", server);
-        threadLocalValue.set(singleConfig);
-        try {
-            String[] argv = new String[]{"-g", "", features };
-            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
-            Main.run(argv, contextClassLoader);
-        } catch (Exception e) {
-            log.error("Error with parallel test", e);
-        } finally {
-            threadLocalValue.remove();
-        }
-    }
-}
diff --git a/src/test/java/com/browserstack/stepdefs/StackDemoSteps.java b/src/test/java/com/browserstack/stepdefs/StackDemoSteps.java
index 4cb50be..27b2df5 100644
--- a/src/test/java/com/browserstack/stepdefs/StackDemoSteps.java
+++ b/src/test/java/com/browserstack/stepdefs/StackDemoSteps.java
@@ -1,7 +1,6 @@
 package com.browserstack.stepdefs;
 
-import com.browserstack.TestRunner;
-import com.browserstack.local.Local;
+import com.browserstack.RunWebDriverCucumberTests;
 import com.browserstack.pageobjects.HomePage;
 import com.browserstack.util.Utility;
 import io.cucumber.java.After;
@@ -10,16 +9,7 @@
 import io.cucumber.java.en.Given;
 import io.cucumber.java.en.Then;
 import io.cucumber.java.en.When;
-import org.json.simple.JSONObject;
 import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.remote.DesiredCapabilities;
-import org.openqa.selenium.remote.RemoteWebDriver;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -27,26 +17,10 @@
 public class StackDemoSteps {
     private WebDriver driver;
     private HomePage homePage;
-    private Local l;
-    private final Logger LOG = LoggerFactory.getLogger(StackDemoSteps.class);
 
     @Before
-    public void setUp(Scenario scenario) throws Exception {
-        JSONObject capability = TestRunner.threadLocalValue.get();
-        String URL = String.format("https://%s/wd/hub",System.getProperty("server"));
-
-        DesiredCapabilities caps = new DesiredCapabilities(capability);
-        caps.setCapability("name", scenario.getName());
-        Map<String, Object> bstackOptions = (Map<String, Object>) caps.getCapability("bstack:options");
-        if (bstackOptions!=null && bstackOptions.get("local")!=null && bstackOptions.get("local").toString().equals("true")) {
-            LOG.info("Start BrowserStack Local");
-            l = new Local();
-            Map<String, String> options = new HashMap<String, String>();
-            options.put("key", bstackOptions.get("accessKey").toString());
-            l.start(options);
-        }
-
-        driver = new RemoteWebDriver(new URL(URL), caps);
+    public void setUp() {
+        driver = RunWebDriverCucumberTests.getManagedWebDriver().getWebDriver();
         homePage = new HomePage(driver);
     }
 
@@ -83,6 +57,5 @@ public void teardown(Scenario scenario) throws Exception {
         }
         Thread.sleep(2000);
         driver.quit();
-        if (l != null) l.stop();
     }
 }
diff --git a/src/test/java/com/browserstack/util/Utility.java b/src/test/java/com/browserstack/util/Utility.java
index af92e60..f97092e 100644
--- a/src/test/java/com/browserstack/util/Utility.java
+++ b/src/test/java/com/browserstack/util/Utility.java
@@ -1,9 +1,12 @@
 package com.browserstack.util;
 
+import com.browserstack.local.Local;
+import com.browserstack.webdriver.ManagedWebDriver;
 import org.json.simple.JSONObject;
 import org.openqa.selenium.JavascriptExecutor;
 import org.openqa.selenium.WebDriver;
 
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
@@ -46,4 +49,22 @@ public static void setSessionStatus(WebDriver webDriver, String status, String r
         JavascriptExecutor jse = (JavascriptExecutor) webDriver;
         jse.executeScript(String.format("browserstack_executor: {\"action\": \"setSessionStatus\", \"arguments\": {\"status\": \"%s\", \"reason\": \"%s\"}}", status, reason));
     }
+
+    public static boolean isLocal(ManagedWebDriver managedWebDriver) {
+        JSONObject platform = managedWebDriver.getPlatform();
+        Map<String, Object> bstackOptions = (Map<String, Object>) platform.get("bstack:options");
+        return bstackOptions!=null && bstackOptions.get("local")!=null && bstackOptions.get("local").toString().equals("true");
+    }
+
+    public static void startLocal(Local local, ManagedWebDriver managedWebDriver) {
+        Map<String, String> options = new HashMap<>();
+        JSONObject platform = managedWebDriver.getPlatform();
+        Map<String, Object> bstackOptions = (Map<String, Object>) platform.get("bstack:options");
+        options.put("key", bstackOptions.get("accessKey").toString());
+        try {
+            local.start(options);
+        } catch (Exception e) {
+            throw new Error("Unable to start BrowserStack Local.");
+        }
+    }
 }
diff --git a/src/test/java/com/browserstack/webdriver/LazyInitWebDriverIterator.java b/src/test/java/com/browserstack/webdriver/LazyInitWebDriverIterator.java
new file mode 100644
index 0000000..5f31fcf
--- /dev/null
+++ b/src/test/java/com/browserstack/webdriver/LazyInitWebDriverIterator.java
@@ -0,0 +1,76 @@
+package com.browserstack.webdriver;
+
+import org.json.simple.JSONObject;
+import org.openqa.selenium.WebDriver;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class LazyInitWebDriverIterator implements Iterator<Object[]> {
+    private final String testMethodName;
+    private final List<JSONObject> platforms;
+    private final List<Object[]> testParams;
+    private final boolean createManagedWebDriver;
+    private int paramIdx = 0;
+
+    public LazyInitWebDriverIterator(Boolean createManagedWebDriver, Object[][] testParams) {
+        this.testMethodName = "";
+        this.platforms = WebDriverFactory.getInstance().getPlatforms();
+        this.createManagedWebDriver = createManagedWebDriver;
+        List<Object[]> testParamsList = new ArrayList();
+        if (testParams != null) {
+            testParamsList = (List)Arrays.stream(testParams).collect(Collectors.toList());
+        }
+
+        this.testParams = this.populateTestParams((List)testParamsList);
+    }
+
+    private List<Object[]> populateTestParams(List<Object[]> testParams) {
+        int idx = 0;
+        ArrayList tempTestParams = new ArrayList();
+
+        do {
+            Object[] testParam = (Object[])testParams.get(idx);
+            if (testParam == null) {
+                testParam = new Object[0];
+            }
+
+            Iterator var5 = this.platforms.iterator();
+
+            while(var5.hasNext()) {
+                JSONObject platform = (JSONObject)var5.next();
+                Object[] paramsWithPlatform = Arrays.copyOf(testParam, testParam.length + 1);
+                paramsWithPlatform[paramsWithPlatform.length - 1] = platform;
+                tempTestParams.add(paramsWithPlatform);
+            }
+
+            ++idx;
+        } while(idx < testParams.size());
+
+        return tempTestParams;
+    }
+
+    public boolean hasNext() {
+        return this.paramIdx < this.testParams.size();
+    }
+
+    public Object[] next() {
+        if (this.paramIdx >= this.testParams.size()) {
+            throw new NoSuchElementException("No More Platforms configured to create WebDriver for.");
+        } else {
+            Object[] methodTestParams = (Object[])this.testParams.get(this.paramIdx++);
+            if (methodTestParams[methodTestParams.length - 1] instanceof JSONObject) {
+                JSONObject platform = (JSONObject)methodTestParams[methodTestParams.length - 1];
+                if (this.createManagedWebDriver) {
+                    ManagedWebDriver managedWebDriver = new ManagedWebDriver(this.testMethodName, platform);
+                    methodTestParams[methodTestParams.length - 1] = managedWebDriver;
+                } else {
+                    WebDriver webDriver = WebDriverFactory.getInstance().createWebDriverForPlatform(platform, this.testMethodName);
+                    methodTestParams[methodTestParams.length - 1] = webDriver;
+                }
+            }
+
+            return methodTestParams;
+        }
+    }
+}
diff --git a/src/test/java/com/browserstack/webdriver/ManagedWebDriver.java b/src/test/java/com/browserstack/webdriver/ManagedWebDriver.java
new file mode 100644
index 0000000..51d09e2
--- /dev/null
+++ b/src/test/java/com/browserstack/webdriver/ManagedWebDriver.java
@@ -0,0 +1,33 @@
+package com.browserstack.webdriver;
+
+import org.json.simple.JSONObject;
+import org.openqa.selenium.WebDriver;
+
+public class ManagedWebDriver {
+    private String testName;
+    private final WebDriverFactory webDriverFactory;
+    private final JSONObject platform;
+    private WebDriver webDriver;
+
+    public ManagedWebDriver(String testMethodName, JSONObject platform) {
+        this.testName = testMethodName;
+        this.platform = platform;
+        this.webDriverFactory = WebDriverFactory.getInstance();
+    }
+
+    public void setTestName(String testName) {
+        this.testName = testName;
+    }
+
+    public JSONObject getPlatform() {
+        return this.platform;
+    }
+
+    public WebDriver getWebDriver() {
+        if (this.webDriver == null) {
+            this.webDriver = this.webDriverFactory.createWebDriverForPlatform(this.platform, this.testName);
+        }
+
+        return this.webDriver;
+    }
+}
diff --git a/src/test/java/com/browserstack/webdriver/WebDriverFactory.java b/src/test/java/com/browserstack/webdriver/WebDriverFactory.java
new file mode 100644
index 0000000..dfeeb4c
--- /dev/null
+++ b/src/test/java/com/browserstack/webdriver/WebDriverFactory.java
@@ -0,0 +1,73 @@
+package com.browserstack.webdriver;
+
+import com.browserstack.util.Utility;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.RemoteWebDriver;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class WebDriverFactory {
+
+    private static volatile WebDriverFactory instance;
+    private final JSONObject testConfig = this.parseWebDriverConfig();
+
+    private WebDriverFactory() {
+    }
+
+    public static WebDriverFactory getInstance() {
+        if (instance == null) {
+            Class var0 = WebDriverFactory.class;
+            synchronized(WebDriverFactory.class) {
+                if (instance == null) {
+                    instance = new WebDriverFactory();
+                }
+            }
+        }
+
+        return instance;
+    }
+
+    private JSONObject parseWebDriverConfig() {
+        JSONParser parser = new JSONParser();
+        String capabilitiesConfigFile = System.getProperty("caps", "src/test/resources/conf/single.conf.json");
+        try {
+            JSONObject testConfig = (JSONObject) parser.parse(new FileReader(capabilitiesConfigFile));
+            return testConfig;
+        } catch (IOException | ParseException var6) {
+            throw new Error("Unable to parse capabilities file " + capabilitiesConfigFile, var6);
+        }
+    }
+
+    public List<JSONObject> getPlatforms() {
+        JSONArray environments = (JSONArray) testConfig.get("environments");
+        List<JSONObject> platforms = new ArrayList<>();
+        for (Object obj : environments) {
+            JSONObject singleConfig = Utility.getCombinedCapability((Map<String, String>) obj, testConfig);
+            platforms.add(singleConfig);
+        }
+        return platforms;
+    }
+
+    public WebDriver createWebDriverForPlatform(JSONObject platform, String testName) {
+        try {
+            String URL = String.format("https://%s/wd/hub", testConfig.get("server"));
+            DesiredCapabilities caps = new DesiredCapabilities(platform);
+            caps.setCapability("name", testName);
+            return new RemoteWebDriver(new URL(URL), caps);
+        } catch (MalformedURLException var4) {
+            throw new Error("Unable to create WebDriver", var4);
+        }
+    }
+
+}
diff --git a/src/test/resources/testng.xml b/src/test/resources/testng.xml
new file mode 100644
index 0000000..28c168b
--- /dev/null
+++ b/src/test/resources/testng.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite name="BDD Test Suite" verbose="1">
+    <test name="Cucumber Tests">
+        <classes>
+            <class name="com.browserstack.RunWebDriverCucumberTests" />
+        </classes>
+    </test>
+</suite>

From 15537c19693804eb2daeae546dc0d5efbcba6dd1 Mon Sep 17 00:00:00 2001
From: Abhi Singh <abhi.s@browserstack.com>
Date: Thu, 12 May 2022 17:44:26 +0530
Subject: [PATCH 07/14] added comments and improved code quality

---
 .../RunWebDriverCucumberTests.java            | 16 +++---
 .../webdriver/LazyInitWebDriverIterator.java  | 49 +++++++++----------
 .../webdriver/WebDriverFactory.java           |  1 -
 3 files changed, 32 insertions(+), 34 deletions(-)

diff --git a/src/test/java/com/browserstack/RunWebDriverCucumberTests.java b/src/test/java/com/browserstack/RunWebDriverCucumberTests.java
index cdf1d2d..6224b5b 100644
--- a/src/test/java/com/browserstack/RunWebDriverCucumberTests.java
+++ b/src/test/java/com/browserstack/RunWebDriverCucumberTests.java
@@ -1,19 +1,20 @@
 package com.browserstack;
 
+import java.util.Iterator;
+
 import com.browserstack.local.Local;
 import com.browserstack.util.Utility;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
 import com.browserstack.webdriver.LazyInitWebDriverIterator;
 import com.browserstack.webdriver.ManagedWebDriver;
 import io.cucumber.testng.CucumberOptions;
 import io.cucumber.testng.FeatureWrapper;
 import io.cucumber.testng.PickleWrapper;
 import io.cucumber.testng.TestNGCucumberRunner;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-import java.util.Iterator;
 
 @CucumberOptions(
         glue = "com.browserstack.stepdefs",
@@ -59,7 +60,8 @@ public void feature(PickleWrapper pickleWrapper, FeatureWrapper featureWrapper,
     @DataProvider(name = "scenarios", parallel = true)
     public Iterator<Object[]> scenarios() {
         Object[][] scenarios = testNGCucumberRunner.provideScenarios();
-        return new LazyInitWebDriverIterator(true, scenarios);
+        //Get Iterator of Object arrays consisting PickleWrapper, FeatureWrapper and ManagedWebDriver
+        return new LazyInitWebDriverIterator(scenarios);
     }
 
     @AfterClass(alwaysRun = true)
diff --git a/src/test/java/com/browserstack/webdriver/LazyInitWebDriverIterator.java b/src/test/java/com/browserstack/webdriver/LazyInitWebDriverIterator.java
index 5f31fcf..f7ead4a 100644
--- a/src/test/java/com/browserstack/webdriver/LazyInitWebDriverIterator.java
+++ b/src/test/java/com/browserstack/webdriver/LazyInitWebDriverIterator.java
@@ -1,75 +1,72 @@
 package com.browserstack.webdriver;
 
-import org.json.simple.JSONObject;
-import org.openqa.selenium.WebDriver;
-
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.stream.Collectors;
 
+import org.json.simple.JSONObject;
+
 public class LazyInitWebDriverIterator implements Iterator<Object[]> {
     private final String testMethodName;
     private final List<JSONObject> platforms;
     private final List<Object[]> testParams;
-    private final boolean createManagedWebDriver;
-    private int paramIdx = 0;
+    private int paramIndex = 0;
 
-    public LazyInitWebDriverIterator(Boolean createManagedWebDriver, Object[][] testParams) {
+    public LazyInitWebDriverIterator(Object[][] testParams) {
         this.testMethodName = "";
         this.platforms = WebDriverFactory.getInstance().getPlatforms();
-        this.createManagedWebDriver = createManagedWebDriver;
         List<Object[]> testParamsList = new ArrayList();
         if (testParams != null) {
             testParamsList = (List)Arrays.stream(testParams).collect(Collectors.toList());
         }
 
+        //Create list of combinations of Scenarios and Platforms
         this.testParams = this.populateTestParams((List)testParamsList);
     }
 
     private List<Object[]> populateTestParams(List<Object[]> testParams) {
-        int idx = 0;
+        int index = 0;
         ArrayList tempTestParams = new ArrayList();
 
         do {
-            Object[] testParam = (Object[])testParams.get(idx);
+            Object[] testParam = (Object[])testParams.get(index);
             if (testParam == null) {
                 testParam = new Object[0];
             }
 
-            Iterator var5 = this.platforms.iterator();
+            Iterator platformsIterator = this.platforms.iterator();
 
-            while(var5.hasNext()) {
-                JSONObject platform = (JSONObject)var5.next();
+            while(platformsIterator.hasNext()) {
+                JSONObject platform = (JSONObject)platformsIterator.next();
                 Object[] paramsWithPlatform = Arrays.copyOf(testParam, testParam.length + 1);
                 paramsWithPlatform[paramsWithPlatform.length - 1] = platform;
                 tempTestParams.add(paramsWithPlatform);
             }
 
-            ++idx;
-        } while(idx < testParams.size());
+            ++index;
+        } while(index < testParams.size());
 
         return tempTestParams;
     }
 
     public boolean hasNext() {
-        return this.paramIdx < this.testParams.size();
+        return this.paramIndex < this.testParams.size();
     }
 
     public Object[] next() {
-        if (this.paramIdx >= this.testParams.size()) {
+        if (this.paramIndex >= this.testParams.size()) {
             throw new NoSuchElementException("No More Platforms configured to create WebDriver for.");
         } else {
-            Object[] methodTestParams = (Object[])this.testParams.get(this.paramIdx++);
+            Object[] methodTestParams = (Object[])this.testParams.get(this.paramIndex++);
             if (methodTestParams[methodTestParams.length - 1] instanceof JSONObject) {
                 JSONObject platform = (JSONObject)methodTestParams[methodTestParams.length - 1];
-                if (this.createManagedWebDriver) {
-                    ManagedWebDriver managedWebDriver = new ManagedWebDriver(this.testMethodName, platform);
-                    methodTestParams[methodTestParams.length - 1] = managedWebDriver;
-                } else {
-                    WebDriver webDriver = WebDriverFactory.getInstance().createWebDriverForPlatform(platform, this.testMethodName);
-                    methodTestParams[methodTestParams.length - 1] = webDriver;
-                }
+                ManagedWebDriver managedWebDriver = new ManagedWebDriver(this.testMethodName, platform);
+                methodTestParams[methodTestParams.length - 1] = managedWebDriver;
             }
-
+            //return an Object array consisting PickleWrapper, FeatureWrapper and ManagedWebDriver
             return methodTestParams;
         }
     }
diff --git a/src/test/java/com/browserstack/webdriver/WebDriverFactory.java b/src/test/java/com/browserstack/webdriver/WebDriverFactory.java
index dfeeb4c..24314c1 100644
--- a/src/test/java/com/browserstack/webdriver/WebDriverFactory.java
+++ b/src/test/java/com/browserstack/webdriver/WebDriverFactory.java
@@ -27,7 +27,6 @@ private WebDriverFactory() {
 
     public static WebDriverFactory getInstance() {
         if (instance == null) {
-            Class var0 = WebDriverFactory.class;
             synchronized(WebDriverFactory.class) {
                 if (instance == null) {
                     instance = new WebDriverFactory();

From 14c9925cce2c5810c10e02e02743995a6e026347 Mon Sep 17 00:00:00 2001
From: Abhi Singh <abhi.s@browserstack.com>
Date: Thu, 12 May 2022 18:43:53 +0530
Subject: [PATCH 08/14] added localOptions to config

---
 src/test/java/com/browserstack/util/Utility.java | 9 +++++++++
 src/test/resources/conf/local.conf.json          | 6 +++++-
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/test/java/com/browserstack/util/Utility.java b/src/test/java/com/browserstack/util/Utility.java
index f97092e..5fe2a1f 100644
--- a/src/test/java/com/browserstack/util/Utility.java
+++ b/src/test/java/com/browserstack/util/Utility.java
@@ -3,9 +3,11 @@
 import com.browserstack.local.Local;
 import com.browserstack.webdriver.ManagedWebDriver;
 import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
 import org.openqa.selenium.JavascriptExecutor;
 import org.openqa.selenium.WebDriver;
 
+import java.io.FileReader;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -57,11 +59,18 @@ public static boolean isLocal(ManagedWebDriver managedWebDriver) {
     }
 
     public static void startLocal(Local local, ManagedWebDriver managedWebDriver) {
+        JSONParser parser = new JSONParser();
         Map<String, String> options = new HashMap<>();
         JSONObject platform = managedWebDriver.getPlatform();
         Map<String, Object> bstackOptions = (Map<String, Object>) platform.get("bstack:options");
         options.put("key", bstackOptions.get("accessKey").toString());
+        String capabilitiesConfigFile = System.getProperty("caps", "src/test/resources/conf/local.conf.json");
         try {
+            JSONObject testConfig = (JSONObject) parser.parse(new FileReader(capabilitiesConfigFile));
+            if(testConfig.containsKey("localOptions")) {
+                JSONObject localOptions = (JSONObject) testConfig.get("localOptions");
+                options.forEach(localOptions::putIfAbsent);
+            }
             local.start(options);
         } catch (Exception e) {
             throw new Error("Unable to start BrowserStack Local.");
diff --git a/src/test/resources/conf/local.conf.json b/src/test/resources/conf/local.conf.json
index 6703b3a..c6ad5b5 100644
--- a/src/test/resources/conf/local.conf.json
+++ b/src/test/resources/conf/local.conf.json
@@ -13,5 +13,9 @@
 
   "environments": [{
     "browserName": "chrome"
-  }]
+  }],
+
+  "localOptions": {
+    "forceLocal": true
+  }
 }

From 29e42dea4bf0d82715076466fd669e21863f8ee5 Mon Sep 17 00:00:00 2001
From: Neha Agarwal <agarneha1331@gmail.com>
Date: Thu, 23 Mar 2023 14:13:43 +0530
Subject: [PATCH 09/14] Made parallel test as default

---
 README.md | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 3f5331d..fef1a68 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,7 @@
 * Update `*.conf.js` files inside the `src/test/resources/conf/` directory to update desired capabilities.
 
 ## Running your tests
-* To run a single test, run `mvn test -P single`
-* To run parallel tests, run `mvn test -P parallel` 
+* To run tests, run `mvn test -P parallel` 
 * To run local tests, run `mvn test -P local`
 
 

From 74f55f0870724b63868a48444c454ee9861e179c Mon Sep 17 00:00:00 2001
From: Neha Agarwal <agarneha1331@gmail.com>
Date: Fri, 24 Mar 2023 12:36:50 +0530
Subject: [PATCH 10/14] Fix for parallel with local

---
 pom.xml                                               |  6 +++---
 .../com/browserstack/RunWebDriverCucumberTests.java   | 11 ++++++-----
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/pom.xml b/pom.xml
index 8c097af..76fe56d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,9 +94,6 @@
     <profiles>
         <profile>
             <id>single</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
             <build>
                 <plugins>
                     <plugin>
@@ -122,6 +119,9 @@
         </profile>
         <profile>
             <id>parallel</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
             <build>
                 <plugins>
                     <plugin>
diff --git a/src/test/java/com/browserstack/RunWebDriverCucumberTests.java b/src/test/java/com/browserstack/RunWebDriverCucumberTests.java
index 6224b5b..7ff3935 100644
--- a/src/test/java/com/browserstack/RunWebDriverCucumberTests.java
+++ b/src/test/java/com/browserstack/RunWebDriverCucumberTests.java
@@ -30,7 +30,7 @@
 public class RunWebDriverCucumberTests {
 
     private TestNGCucumberRunner testNGCucumberRunner;
-    private Local local;
+    private static Local local;
     private static final ThreadLocal<ManagedWebDriver> threadLocalWebDriver = new ThreadLocal<>();
 
     @BeforeClass(alwaysRun = true)
@@ -40,6 +40,11 @@ public void setUpClass() {
 
     private synchronized static void setThreadLocalWebDriver(ManagedWebDriver managedWebDriver) {
         threadLocalWebDriver.set(managedWebDriver);
+        if(Utility.isLocal(managedWebDriver) && local==null){
+            local = new Local();
+            Utility.startLocal(local, managedWebDriver);
+        }
+      
     }
 
     public synchronized static ManagedWebDriver getManagedWebDriver() {
@@ -48,10 +53,6 @@ public synchronized static ManagedWebDriver getManagedWebDriver() {
 
     @Test(groups = "cucumber", description = "Runs Cucumber Feature", dataProvider = "scenarios")
     public void feature(PickleWrapper pickleWrapper, FeatureWrapper featureWrapper, ManagedWebDriver managedWebDriver) {
-        if(Utility.isLocal(managedWebDriver) && local==null){
-            local = new Local();
-            Utility.startLocal(local, managedWebDriver);
-        }
         managedWebDriver.setTestName(pickleWrapper.getPickle().getName());
         setThreadLocalWebDriver(managedWebDriver);
         testNGCucumberRunner.runScenario(pickleWrapper.getPickle());

From e12573d400125f38b3249b60eb39f45e88ee267a Mon Sep 17 00:00:00 2001
From: Neha Agarwal <agarneha1331@gmail.com>
Date: Tue, 28 Mar 2023 13:03:51 +0530
Subject: [PATCH 11/14] Update source

---
 src/test/java/com/browserstack/util/Utility.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/test/java/com/browserstack/util/Utility.java b/src/test/java/com/browserstack/util/Utility.java
index 5fe2a1f..6ff23d0 100644
--- a/src/test/java/com/browserstack/util/Utility.java
+++ b/src/test/java/com/browserstack/util/Utility.java
@@ -43,6 +43,7 @@ public static JSONObject getCombinedCapability(Map<String, String> envCapabiliti
             bstackOptions = new JSONObject();
         bstackOptions.put("userName", username);
         bstackOptions.put("accessKey", accessKey);
+        bstackOptions.put("source", "cucumber-java:sample-selenium-4:v1.1");
         capabilities.put("bstack:options", bstackOptions);
         return capabilities;
     }

From 35316c4536a9ddb17a69a1835a52044cd85d2b74 Mon Sep 17 00:00:00 2001
From: Neha Agarwal <agarneha1331@gmail.com>
Date: Wed, 21 Jun 2023 14:01:06 +0530
Subject: [PATCH 12/14] Added BStack to scenerio

---
 src/test/resources/features/localtest/Local.feature | 2 +-
 src/test/resources/features/test/StackDemo.feature  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/test/resources/features/localtest/Local.feature b/src/test/resources/features/localtest/Local.feature
index 8d1601f..6c65e4e 100644
--- a/src/test/resources/features/localtest/Local.feature
+++ b/src/test/resources/features/localtest/Local.feature
@@ -1,5 +1,5 @@
 Feature: Verify Local test
 
-  Scenario: Navigate to Local check page
+  Scenario: BStack Local Test: Navigate to Local check page
     Given I am on the website 'http://bs-local.com:45691/check'
     Then the page should contain 'Up and running'
\ No newline at end of file
diff --git a/src/test/resources/features/test/StackDemo.feature b/src/test/resources/features/test/StackDemo.feature
index 9e3a2e5..be84742 100644
--- a/src/test/resources/features/test/StackDemo.feature
+++ b/src/test/resources/features/test/StackDemo.feature
@@ -1,6 +1,6 @@
 Feature: BrowserStack Demo
 
-  Scenario: Add product to cart
+  Scenario: BStack Sample Test: Add product to cart
     Given I am on the website 'https://www.bstackdemo.com'
     When I select a product and click on 'Add to cart' button
     Then the product should be added to cart

From e090e755e47e2fcc0fe33eb1f31356230be105a3 Mon Sep 17 00:00:00 2001
From: Neha Agarwal <agarneha1331@gmail.com>
Date: Wed, 21 Jun 2023 14:07:47 +0530
Subject: [PATCH 13/14] Added newline

---
 src/test/resources/features/localtest/Local.feature | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/resources/features/localtest/Local.feature b/src/test/resources/features/localtest/Local.feature
index 6c65e4e..ce1381b 100644
--- a/src/test/resources/features/localtest/Local.feature
+++ b/src/test/resources/features/localtest/Local.feature
@@ -2,4 +2,4 @@ Feature: Verify Local test
 
   Scenario: BStack Local Test: Navigate to Local check page
     Given I am on the website 'http://bs-local.com:45691/check'
-    Then the page should contain 'Up and running'
\ No newline at end of file
+    Then the page should contain 'Up and running'

From a1b797663b833e82282bd45a1cdbcf27dc403bbd Mon Sep 17 00:00:00 2001
From: Neha Agarwal <agarneha1331@gmail.com>
Date: Fri, 23 Jun 2023 17:26:21 +0530
Subject: [PATCH 14/14] fixed syntax in feature file

---
 src/test/resources/features/localtest/Local.feature | 2 +-
 src/test/resources/features/test/StackDemo.feature  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/test/resources/features/localtest/Local.feature b/src/test/resources/features/localtest/Local.feature
index ce1381b..1e3d812 100644
--- a/src/test/resources/features/localtest/Local.feature
+++ b/src/test/resources/features/localtest/Local.feature
@@ -1,5 +1,5 @@
 Feature: Verify Local test
 
-  Scenario: BStack Local Test: Navigate to Local check page
+  Scenario: BStack Local Test - Navigate to Local check page
     Given I am on the website 'http://bs-local.com:45691/check'
     Then the page should contain 'Up and running'
diff --git a/src/test/resources/features/test/StackDemo.feature b/src/test/resources/features/test/StackDemo.feature
index be84742..f0df316 100644
--- a/src/test/resources/features/test/StackDemo.feature
+++ b/src/test/resources/features/test/StackDemo.feature
@@ -1,6 +1,6 @@
 Feature: BrowserStack Demo
 
-  Scenario: BStack Sample Test: Add product to cart
+  Scenario: BStack Sample Test - Add product to cart
     Given I am on the website 'https://www.bstackdemo.com'
     When I select a product and click on 'Add to cart' button
     Then the product should be added to cart
