diff --git a/.gitignore b/.gitignore index 690fb34..fd647c0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ browserstack.err **/.DS_Store .vscode/ .env +**/package-lock.json diff --git a/android/WikipediaSample.apk b/android/WikipediaSample.apk index 03d19e6..286e346 100644 Binary files a/android/WikipediaSample.apk and b/android/WikipediaSample.apk differ diff --git a/android/browserstack.yml b/android/browserstack.yml index 77e7181..dc3629f 100644 --- a/android/browserstack.yml +++ b/android/browserstack.yml @@ -23,6 +23,8 @@ buildIdentifier: '#${BUILD_NUMBER}' # Supports strings along with either/both ${ source: cucumber-js:appium-sample-master:v1.0 +CUSTOM_TAG_1: "bstack_sample" + # Set `app` to define the app that is to be used for testing. # It can either take the id of any uploaded app or the path of the app directly. app: ./WikipediaSample.apk diff --git a/android/cucumber.js b/android/cucumber.js new file mode 100644 index 0000000..14f2ddb --- /dev/null +++ b/android/cucumber.js @@ -0,0 +1,3 @@ +module.exports = { + default: '--require "features/step_definitions/module*.steps.js" --require features/support/*.js --retry 2 --retryTagFilter @retry' +}; diff --git a/android/features/moduleA/moduleA.feature b/android/features/moduleA/moduleA.feature new file mode 100644 index 0000000..fb89e47 --- /dev/null +++ b/android/features/moduleA/moduleA.feature @@ -0,0 +1,48 @@ +Feature: BStackDemo Tests Module A + + #Background: + # Given I skip the Wikipedia onboarding for moduleA + #And I tap on the search bar for moduleA + + @p1 + Scenario: flaky test - random product selection + When I randomly select a search term and search in moduleA + Then there should be at least one result in moduleA + + @regression + Scenario: always failing test - missing element 1 + When I try to click a non-existent element "non-existent-1" in moduleA + # No Then step - this should fail on the When step + + Scenario: always passing test - example C + Then true should be true in moduleA + + Scenario: always failing test - same stacktrace 1 + When I try to click a non-existent element "common-error" in moduleA + # No Then step - this should fail on the When step + + @regression + Scenario: always failing test - same stacktrace 2 + When I try to click a non-existent element "common-error" in moduleA + # No Then step - this should fail on the When step + + @regression @must-pass + Scenario: always passing test - example D + Then true should be true in moduleA + + @must-pass + Scenario: always passing test - example A + Then 1 plus 1 should be 2 in moduleA + + @retry + Scenario: Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleA + Then it should eventually pass or fail in moduleA + + @retry + Scenario: Another Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleA + Then it should eventually pass or fail in moduleA + + Scenario: always passing test - example B + Then "Browser" plus "Stack" should be "BrowserStack" in moduleA diff --git a/android/features/moduleB/moduleB.feature b/android/features/moduleB/moduleB.feature new file mode 100644 index 0000000..c9ff7c8 --- /dev/null +++ b/android/features/moduleB/moduleB.feature @@ -0,0 +1,47 @@ +Feature: BStackDemo Tests Module B + + #Background: + # Given I skip the Wikipedia onboarding for moduleB + # And I tap on the search bar for moduleB + + @regression @p1 + Scenario: flaky test - random product selection + When I randomly select a search term and search in moduleB + Then there should be at least one result in moduleB + + Scenario: always failing test - missing element 1 + When I try to click a non-existent element "non-existent-1" in moduleB + # No Then step - this should fail on the When step + + @regression + Scenario: always passing test - example C + Then true should be true in moduleB + + Scenario: always failing test - same stacktrace 1 + When I try to click a non-existent element "common-error" in moduleB + # No Then step - this should fail on the When step + + Scenario: always failing test - same stacktrace 2 + When I try to click a non-existent element "common-error" in moduleB + # No Then step - this should fail on the When step + + @regression + Scenario: always passing test - example D + Then true should be true in moduleB + + @regression + Scenario: always passing test - example A + Then 1 plus 1 should be 2 in moduleB + + @retry + Scenario: Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleB + Then it should eventually pass or fail in moduleB + + @retry + Scenario: Another Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleB + Then it should eventually pass or fail in moduleB + + Scenario: always passing test - example B + Then "Browser" plus "Stack" should be "BrowserStack" in moduleB diff --git a/android/features/moduleC/moduleC.feature b/android/features/moduleC/moduleC.feature new file mode 100644 index 0000000..8b4c27c --- /dev/null +++ b/android/features/moduleC/moduleC.feature @@ -0,0 +1,47 @@ +Feature: BStackDemo Tests Module C + + #Background: + # Given I skip the Wikipedia onboarding for moduleC + # And I tap on the search bar for moduleC + + @p1 + Scenario: flaky test - random product selection + When I randomly select a search term and search in moduleC + Then there should be at least one result in moduleC + + Scenario: always failing test - missing element 1 + When I try to click a non-existent element "non-existent-1" in moduleC + # No Then step - this should fail on the When step + + Scenario: always passing test - example C + Then true should be true in moduleC + + @must-pass + Scenario: always passing test - example D + Then true should be true in moduleC + + Scenario: always failing test - same stacktrace 1 + When I try to click a non-existent element "common-error" in moduleC + # No Then step - this should fail on the When step + + Scenario: always failing test - same stacktrace 2 + When I try to click a non-existent element "common-error" in moduleC + # No Then step - this should fail on the When step + + @p1 @must-pass + Scenario: always passing test - example A + Then 1 plus 1 should be 2 in moduleC + + @retry + Scenario: Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleC + Then it should eventually pass or fail in moduleC + + @retry + Scenario: Another Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleC + Then it should eventually pass or fail in moduleC + + @must-pass + Scenario: always passing test - example B + Then "Browser" plus "Stack" should be "BrowserStack" in moduleC diff --git a/android/features/step_definitions/moduleA.steps.js b/android/features/step_definitions/moduleA.steps.js new file mode 100644 index 0000000..d6e4d7a --- /dev/null +++ b/android/features/step_definitions/moduleA.steps.js @@ -0,0 +1,83 @@ +const { Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); +const { By, until } = require('selenium-webdriver'); +const assert = require('assert'); + +// Increase timeout for Appium commands +setDefaultTimeout(60 * 1000); + +// Background steps +Given('I skip the Wikipedia onboarding for moduleA', async function() { + const skipButton = await this.driver.wait( + until.elementLocated(By.id('org.wikipedia.alpha:id/fragment_onboarding_skip_button')), + 30000 + ); + await skipButton.click(); +}); + +Given('I tap on the search bar for moduleA', async function() { + const searchSelector = await this.driver.wait( + until.elementLocated(By.xpath("//*[@content-desc='Search Wikipedia']")), + 30000 + ); + await searchSelector.click(); +}); + +// Module A specific step implementations - will fail naturally without try/catch +When('I randomly select a search term and search in moduleA', async function() { + const useValidSelector = Math.random() > 0.5; + let searchField; + + if (useValidSelector) { + searchField = await this.driver.wait( + until.elementLocated(By.id('org.wikipedia.alpha:id/search_src_text')), + 30000 + ); + } else { + // This will fail naturally + searchField = await this.driver.wait( + until.elementLocated(By.id('falseSelector')), + 5000 + ); + } + + const randomTerm = Math.random() > 0.5 ? "BrowserStack" : "akjsdhfakjsdhf"; + await searchField.sendKeys(randomTerm); + await this.driver.sleep(5000); +}); + +Then('there should be at least one result in moduleA', async function() { + const allResults = await this.driver.findElements(By.className('android.widget.TextView')); + assert(allResults.length > 0, 'Expected to find at least one result'); +}); + +// This will fail naturally without try/catch +When('I try to click a non-existent element {string} in moduleA', async function(elementName) { + const nonExistent = await this.driver.wait( + until.elementLocated(By.xpath(`//*[@content-desc='${elementName}']`)), + 3000 + ); + await nonExistent.click(); +}); + +Then('true should be true in moduleA', function() { + assert.strictEqual(true, true); +}); + +Then('1 plus 1 should be 2 in moduleA', function() { + assert.strictEqual(1 + 1, 2); +}); + +When('I run a flaky test with retry in moduleA', function() { + const randomOutcome = Math.random() > 0.7; + if (!randomOutcome) { + throw new Error("Test failed, will retry..."); + } +}); + +Then('it should eventually pass or fail in moduleA', function() { + // No-op +}); + +Then('{string} plus {string} should be {string} in moduleA', function(a, b, expected) { + assert.strictEqual(a + b, expected); +}); diff --git a/android/features/step_definitions/moduleB.steps.js b/android/features/step_definitions/moduleB.steps.js new file mode 100644 index 0000000..45c5006 --- /dev/null +++ b/android/features/step_definitions/moduleB.steps.js @@ -0,0 +1,83 @@ +const { Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); +const { By, until } = require('selenium-webdriver'); +const assert = require('assert'); + +// Increase timeout for Appium commands +setDefaultTimeout(60 * 1000); + +// Background steps +Given('I skip the Wikipedia onboarding for moduleB', async function() { + const skipButton = await this.driver.wait( + until.elementLocated(By.id('org.wikipedia.alpha:id/fragment_onboarding_skip_button')), + 30000 + ); + await skipButton.click(); +}); + +Given('I tap on the search bar for moduleB', async function() { + const searchSelector = await this.driver.wait( + until.elementLocated(By.xpath("//*[@content-desc='Search Wikipedia']")), + 30000 + ); + await searchSelector.click(); +}); + +// Module B specific step implementations - will fail naturally without try/catch +When('I randomly select a search term and search in moduleB', async function() { + const useValidSelector = Math.random() > 0.5; + let searchField; + + if (useValidSelector) { + searchField = await this.driver.wait( + until.elementLocated(By.id('org.wikipedia.alpha:id/search_src_text')), + 30000 + ); + } else { + // This will fail naturally + searchField = await this.driver.wait( + until.elementLocated(By.id('falseSelector')), + 5000 + ); + } + + const randomTerm = Math.random() > 0.5 ? "BrowserStack" : "akjsdhfakjsdhf"; + await searchField.sendKeys(randomTerm); + await this.driver.sleep(5000); +}); + +Then('there should be at least one result in moduleB', async function() { + const allResults = await this.driver.findElements(By.className('android.widget.TextView')); + assert(allResults.length > 0, 'Expected to find at least one result'); +}); + +// This will fail naturally without try/catch +When('I try to click a non-existent element {string} in moduleB', async function(elementName) { + const nonExistent = await this.driver.wait( + until.elementLocated(By.xpath(`//*[@content-desc='${elementName}']`)), + 3000 + ); + await nonExistent.click(); +}); + +Then('true should be true in moduleB', function() { + assert.strictEqual(true, true); +}); + +Then('1 plus 1 should be 2 in moduleB', function() { + assert.strictEqual(1 + 1, 2); +}); + +When('I run a flaky test with retry in moduleB', function() { + const randomOutcome = Math.random() > 0.7; + if (!randomOutcome) { + throw new Error("Test failed, will retry..."); + } +}); + +Then('it should eventually pass or fail in moduleB', function() { + // No-op +}); + +Then('{string} plus {string} should be {string} in moduleB', function(a, b, expected) { + assert.strictEqual(a + b, expected); +}); diff --git a/android/features/step_definitions/moduleC.steps.js b/android/features/step_definitions/moduleC.steps.js new file mode 100644 index 0000000..f14081c --- /dev/null +++ b/android/features/step_definitions/moduleC.steps.js @@ -0,0 +1,83 @@ +const { Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); +const { By, until } = require('selenium-webdriver'); +const assert = require('assert'); + +// Increase timeout for Appium commands +setDefaultTimeout(60 * 1000); + +// Background steps +Given('I skip the Wikipedia onboarding for moduleC', async function() { + const skipButton = await this.driver.wait( + until.elementLocated(By.id('org.wikipedia.alpha:id/fragment_onboarding_skip_button')), + 30000 + ); + await skipButton.click(); +}); + +Given('I tap on the search bar for moduleC', async function() { + const searchSelector = await this.driver.wait( + until.elementLocated(By.xpath("//*[@content-desc='Search Wikipedia']")), + 30000 + ); + await searchSelector.click(); +}); + +// Module C specific step implementations - will fail naturally without try/catch +When('I randomly select a search term and search in moduleC', async function() { + const useValidSelector = Math.random() > 0.5; + let searchField; + + if (useValidSelector) { + searchField = await this.driver.wait( + until.elementLocated(By.id('org.wikipedia.alpha:id/search_src_text')), + 30000 + ); + } else { + // This will fail naturally + searchField = await this.driver.wait( + until.elementLocated(By.id('falseSelector')), + 5000 + ); + } + + const randomTerm = Math.random() > 0.5 ? "BrowserStack" : "akjsdhfakjsdhf"; + await searchField.sendKeys(randomTerm); + await this.driver.sleep(5000); +}); + +Then('there should be at least one result in moduleC', async function() { + const allResults = await this.driver.findElements(By.className('android.widget.TextView')); + assert(allResults.length > 0, 'Expected to find at least one result'); +}); + +// This will fail naturally without try/catch +When('I try to click a non-existent element {string} in moduleC', async function(elementName) { + const nonExistent = await this.driver.wait( + until.elementLocated(By.xpath(`//*[@content-desc='${elementName}']`)), + 3000 + ); + await nonExistent.click(); +}); + +Then('true should be true in moduleC', function() { + assert.strictEqual(true, true); +}); + +Then('1 plus 1 should be 2 in moduleC', function() { + assert.strictEqual(1 + 1, 2); +}); + +When('I run a flaky test with retry in moduleC', function() { + const randomOutcome = Math.random() > 0.7; + if (!randomOutcome) { + throw new Error("Test failed, will retry..."); + } +}); + +Then('it should eventually pass or fail in moduleC', function() { + // No-op +}); + +Then('{string} plus {string} should be {string} in moduleC', function(a, b, expected) { + assert.strictEqual(a + b, expected); +}); diff --git a/android/features/support/hooks.js b/android/features/support/hooks.js index 3c949b5..bb431e7 100644 --- a/android/features/support/hooks.js +++ b/android/features/support/hooks.js @@ -1,7 +1,9 @@ 'use strict'; -const { Builder, Capabilities } = require("selenium-webdriver"); -const { Before, After } = require("@cucumber/cucumber"); +const { Builder, Capabilities, By, until } = require("selenium-webdriver"); +const { BeforeAll, Before, After, AfterAll } = require("@cucumber/cucumber"); + +let driver; var createBrowserStackSession = function(){ return new Builder(). @@ -9,14 +11,57 @@ var createBrowserStackSession = function(){ build(); } -Before(function (scenario, callback) { - var world = this; - world.driver = createBrowserStackSession(); - callback(); +// Before(function (scenario, callback) { +// var world = this; +// world.driver = createBrowserStackSession(); +// callback(); +// }); + +// After(function(scenario, callback){ +// this.driver.quit().then(function(){ +// callback(); +// }); +// }); + +BeforeAll(async function() { + driver = createBrowserStackSession(); + const skipButton = await driver.wait( + until.elementLocated(By.id('org.wikipedia.alpha:id/fragment_onboarding_skip_button')), + 30000 + ); + await skipButton.click(); + + const searchSelector = await driver.wait( + until.elementLocated(By.xpath("//*[@content-desc='Search Wikipedia']")), + 30000 + ); + await searchSelector.click(); }); -After(function(scenario, callback){ - this.driver.quit().then(function(){ - callback(); - }); +// Before(function (scenario, callback) { +// var world = this; +// world.driver = driver; +// callback(); +// }); + +// No driver.quit() after each scenario +After(async function() { + // Optionally reset app state between scenarios if needed + // await this.driver.resetApp(); // Uncomment if you need to reset app state }); + +// Cleanup driver after all scenarios have run +AfterAll(async function() { + // console.log("Tearing down global driver instance..."); + if (driver) { + await driver.quit(); + } +}); + +const { setWorldConstructor } = require('@cucumber/cucumber'); +class CustomWorld { + get driver() { + return driver; + } +} +setWorldConstructor(CustomWorld); diff --git a/android/package.json b/android/package.json index ee08461..57847fd 100644 --- a/android/package.json +++ b/android/package.json @@ -8,7 +8,8 @@ "url": "git+https://github.com/browserstack/cucumber-js-appium-app-browserstack.git" }, "scripts": { - "sample-test": "browserstack-node-sdk cucumber-js features/single.feature", + "sample-test-old": "browserstack-node-sdk cucumber-js features/single.feature", + "sample-test": "browserstack-node-sdk cucumber-js features/module*/*.feature", "sample-local-test": "browserstack-node-sdk cucumber-js features/local.feature", "postinstall": "npm update browserstack-node-sdk" }, diff --git a/ios/browserstack.yml b/ios/browserstack.yml index 27f90e6..afdeba6 100644 --- a/ios/browserstack.yml +++ b/ios/browserstack.yml @@ -28,6 +28,8 @@ source: cucumber-js:appium-sample-master:v1.0 app: ./BStackSampleApp.ipa # app: ./LocalSample.ipa #For running local tests +CUSTOM_TAG_1: "bstack_sample" + # ======================================= # Platforms (Browsers / Devices to test) # ======================================= diff --git a/ios/cucumber.js b/ios/cucumber.js new file mode 100644 index 0000000..14f2ddb --- /dev/null +++ b/ios/cucumber.js @@ -0,0 +1,3 @@ +module.exports = { + default: '--require "features/step_definitions/module*.steps.js" --require features/support/*.js --retry 2 --retryTagFilter @retry' +}; diff --git a/ios/features/moduleA/moduleA.feature b/ios/features/moduleA/moduleA.feature new file mode 100644 index 0000000..53d64dc --- /dev/null +++ b/ios/features/moduleA/moduleA.feature @@ -0,0 +1,47 @@ +Feature: BStackDemo Tests Module A + + #Background: + # Given I skip the Wikipedia onboarding for moduleA + #And I tap on the search bar for moduleA + + @p1 + Scenario: flaky test - passes and fails intermittently + When flaky test in moduleA + + @regression + Scenario: always failing test - missing element 1 + When I try to click a non-existent element "non-existent-1" in moduleA + # No Then step - this should fail on the When step + + Scenario: always passing test - example C + Then true should be true in moduleA + + Scenario: always failing test - same stacktrace 1 + When I try to click a non-existent element "common-error" in moduleA + # No Then step - this should fail on the When step + + @regression + Scenario: always failing test - same stacktrace 2 + When I try to click a non-existent element "common-error" in moduleA + # No Then step - this should fail on the When step + + @regression @must-pass + Scenario: always passing test - example D + Then true should be true in moduleA + + @must-pass + Scenario: always passing test - example A + Then 1 plus 1 should be 2 in moduleA + + @retry + Scenario: Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleA + Then it should eventually pass or fail in moduleA + + @retry + Scenario: Another Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleA + Then it should eventually pass or fail in moduleA + + Scenario: always passing test - example B + Then "Browser" plus "Stack" should be "BrowserStack" in moduleA diff --git a/ios/features/moduleB/moduleB.feature b/ios/features/moduleB/moduleB.feature new file mode 100644 index 0000000..89d7bf2 --- /dev/null +++ b/ios/features/moduleB/moduleB.feature @@ -0,0 +1,46 @@ +Feature: BStackDemo Tests Module B + + #Background: + # Given I skip the Wikipedia onboarding for moduleB + # And I tap on the search bar for moduleB + + @regression @p1 + Scenario: flaky test - passes and fails intermittently + When flaky test in moduleB + + Scenario: always failing test - missing element 1 + When I try to click a non-existent element "non-existent-1" in moduleB + # No Then step - this should fail on the When step + + @regression + Scenario: always passing test - example C + Then true should be true in moduleB + + Scenario: always failing test - same stacktrace 1 + When I try to click a non-existent element "common-error" in moduleB + # No Then step - this should fail on the When step + + Scenario: always failing test - same stacktrace 2 + When I try to click a non-existent element "common-error" in moduleB + # No Then step - this should fail on the When step + + @regression + Scenario: always passing test - example D + Then true should be true in moduleB + + @regression + Scenario: always passing test - example A + Then 1 plus 1 should be 2 in moduleB + + @retry + Scenario: Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleB + Then it should eventually pass or fail in moduleB + + @retry + Scenario: Another Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleB + Then it should eventually pass or fail in moduleB + + Scenario: always passing test - example B + Then "Browser" plus "Stack" should be "BrowserStack" in moduleB diff --git a/ios/features/moduleC/moduleC.feature b/ios/features/moduleC/moduleC.feature new file mode 100644 index 0000000..d9c29da --- /dev/null +++ b/ios/features/moduleC/moduleC.feature @@ -0,0 +1,46 @@ +Feature: BStackDemo Tests Module C + + #Background: + # Given I skip the Wikipedia onboarding for moduleC + # And I tap on the search bar for moduleC + + @p1 + Scenario: flaky test - passes and fails intermittently + When flaky test in moduleC + + Scenario: always failing test - missing element 1 + When I try to click a non-existent element "non-existent-1" in moduleC + # No Then step - this should fail on the When step + + Scenario: always passing test - example C + Then true should be true in moduleC + + @must-pass + Scenario: always passing test - example D + Then true should be true in moduleC + + Scenario: always failing test - same stacktrace 1 + When I try to click a non-existent element "common-error" in moduleC + # No Then step - this should fail on the When step + + Scenario: always failing test - same stacktrace 2 + When I try to click a non-existent element "common-error" in moduleC + # No Then step - this should fail on the When step + + @p1 @must-pass + Scenario: always passing test - example A + Then 1 plus 1 should be 2 in moduleC + + @retry + Scenario: Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleC + Then it should eventually pass or fail in moduleC + + @retry + Scenario: Another Test with framework-level retry - 2 retries configured + When I run a flaky test with retry in moduleC + Then it should eventually pass or fail in moduleC + + @must-pass + Scenario: always passing test - example B + Then "Browser" plus "Stack" should be "BrowserStack" in moduleC diff --git a/ios/features/step_definitions/moduleA.steps.js b/ios/features/step_definitions/moduleA.steps.js new file mode 100644 index 0000000..19de7c2 --- /dev/null +++ b/ios/features/step_definitions/moduleA.steps.js @@ -0,0 +1,50 @@ +const { Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); +const { By, until } = require('selenium-webdriver'); +const assert = require('assert'); + +// Increase timeout for Appium commands +setDefaultTimeout(60 * 1000); + +Given('I tap on the search bar for moduleA', async function() { + const searchSelector = await this.driver.wait( + until.elementLocated(By.xpath("//*[@content-desc='Search Wikipedia']")), + 30000 + ); + await searchSelector.click(); +}); + +When('flaky test in moduleA', async function() { + assert(Math.random() > 0.5, 'Flaky test failed'); +}); + +// This will fail naturally without try/catch +When('I try to click a non-existent element {string} in moduleA', async function(elementName) { + const nonExistent = await this.driver.wait( + until.elementLocated(By.xpath(`//*[@content-desc='${elementName}']`)), + 3000 + ); + await nonExistent.click(); +}); + +Then('true should be true in moduleA', function() { + assert.strictEqual(true, true); +}); + +Then('1 plus 1 should be 2 in moduleA', function() { + assert.strictEqual(1 + 1, 2); +}); + +When('I run a flaky test with retry in moduleA', function() { + const randomOutcome = Math.random() > 0.7; + if (!randomOutcome) { + throw new Error("Test failed, will retry..."); + } +}); + +Then('it should eventually pass or fail in moduleA', function() { + // No-op +}); + +Then('{string} plus {string} should be {string} in moduleA', function(a, b, expected) { + assert.strictEqual(a + b, expected); +}); diff --git a/ios/features/step_definitions/moduleB.steps.js b/ios/features/step_definitions/moduleB.steps.js new file mode 100644 index 0000000..7fbc2e2 --- /dev/null +++ b/ios/features/step_definitions/moduleB.steps.js @@ -0,0 +1,50 @@ +const { Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); +const { By, until } = require('selenium-webdriver'); +const assert = require('assert'); + +// Increase timeout for Appium commands +setDefaultTimeout(60 * 1000); + +Given('I tap on the search bar for moduleB', async function() { + const searchSelector = await this.driver.wait( + until.elementLocated(By.xpath("//*[@content-desc='Search Wikipedia']")), + 30000 + ); + await searchSelector.click(); +}); + +When('flaky test in moduleB', async function() { + assert(Math.random() > 0.5, 'Flaky test failed'); +}); + +// This will fail naturally without try/catch +When('I try to click a non-existent element {string} in moduleB', async function(elementName) { + const nonExistent = await this.driver.wait( + until.elementLocated(By.xpath(`//*[@content-desc='${elementName}']`)), + 3000 + ); + await nonExistent.click(); +}); + +Then('true should be true in moduleB', function() { + assert.strictEqual(true, true); +}); + +Then('1 plus 1 should be 2 in moduleB', function() { + assert.strictEqual(1 + 1, 2); +}); + +When('I run a flaky test with retry in moduleB', function() { + const randomOutcome = Math.random() > 0.7; + if (!randomOutcome) { + throw new Error("Test failed, will retry..."); + } +}); + +Then('it should eventually pass or fail in moduleB', function() { + // No-op +}); + +Then('{string} plus {string} should be {string} in moduleB', function(a, b, expected) { + assert.strictEqual(a + b, expected); +}); diff --git a/ios/features/step_definitions/moduleC.steps.js b/ios/features/step_definitions/moduleC.steps.js new file mode 100644 index 0000000..22b992a --- /dev/null +++ b/ios/features/step_definitions/moduleC.steps.js @@ -0,0 +1,51 @@ +const { Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); +const { By, until } = require('selenium-webdriver'); +const assert = require('assert'); + +// Increase timeout for Appium commands +setDefaultTimeout(60 * 1000); + + +Given('I tap on the search bar for moduleC', async function() { + const searchSelector = await this.driver.wait( + until.elementLocated(By.xpath("//*[@content-desc='Search Wikipedia']")), + 30000 + ); + await searchSelector.click(); +}); + +When('flaky test in moduleC', async function() { + assert(Math.random() > 0.5, 'Flaky test failed'); +}); + +// This will fail naturally without try/catch +When('I try to click a non-existent element {string} in moduleC', async function(elementName) { + const nonExistent = await this.driver.wait( + until.elementLocated(By.xpath(`//*[@content-desc='${elementName}']`)), + 3000 + ); + await nonExistent.click(); +}); + +Then('true should be true in moduleC', function() { + assert.strictEqual(true, true); +}); + +Then('1 plus 1 should be 2 in moduleC', function() { + assert.strictEqual(1 + 1, 2); +}); + +When('I run a flaky test with retry in moduleC', function() { + const randomOutcome = Math.random() > 0.7; + if (!randomOutcome) { + throw new Error("Test failed, will retry..."); + } +}); + +Then('it should eventually pass or fail in moduleC', function() { + // No-op +}); + +Then('{string} plus {string} should be {string} in moduleC', function(a, b, expected) { + assert.strictEqual(a + b, expected); +}); diff --git a/ios/features/support/hooks.js b/ios/features/support/hooks.js index 3c949b5..26fc896 100644 --- a/ios/features/support/hooks.js +++ b/ios/features/support/hooks.js @@ -1,7 +1,9 @@ 'use strict'; -const { Builder, Capabilities } = require("selenium-webdriver"); -const { Before, After } = require("@cucumber/cucumber"); +const { Builder, Capabilities, By, until } = require("selenium-webdriver"); +const { BeforeAll, Before, After, AfterAll } = require("@cucumber/cucumber"); + +let driver; var createBrowserStackSession = function(){ return new Builder(). @@ -9,14 +11,84 @@ var createBrowserStackSession = function(){ build(); } -Before(function (scenario, callback) { - var world = this; - world.driver = createBrowserStackSession(); - callback(); +BeforeAll(async function() { + driver = createBrowserStackSession(); + // const skipButton = await driver.wait( + // until.elementLocated(By.id('org.wikipedia.alpha:id/fragment_onboarding_skip_button')), + // 30000 + // ); + // await skipButton.click(); + + // const searchSelector = await driver.wait( + // until.elementLocated(By.xpath("//*[@content-desc='Search Wikipedia']")), + // 30000 + // ); + // await searchSelector.click(); + + + // const textButtonSelector = await driver.wait( + // until.elementLocated(By.xpath("//*[@content-desc='Text Button']")), + // 30000 + // ); + // await textButtonSelector.click(); + + // const textInputSelector = await driver.wait( + // until.elementLocated(By.xpath("//*[@content-desc='Text Input']")), + // 30000 + // ); + // await textInputSelector.click(); + // await textInputSelector.addValue("hello@browserstack.com"+"\n"); + + // const textOutputSelector = await driver.wait( + // until.elementLocated(By.xpath("//*[@content-desc='Text Output']")), + // 30000 + // ); + + + await driver.wait( + until.elementLocated( + By.xpath( + '/XCUIElementTypeApplication/XCUIElementTypeWindow/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeButton[1]' + ) + ), 30000 + ).click(); + + + var textInput = await driver.wait( + until.elementLocated( + By.xpath( + '/XCUIElementTypeApplication/XCUIElementTypeWindow[1]/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeTextField' + ), 30000 + ) + ); + await textInput.sendKeys('hello@browserstack.com\n'); + await driver.sleep(5000); + + var textOutput = await driver.findElement( + By.xpath( + '/XCUIElementTypeApplication/XCUIElementTypeWindow[1]/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeStaticText' + ) + ).getText(); }); -After(function(scenario, callback){ - this.driver.quit().then(function(){ - callback(); - }); +// No driver.quit() after each scenario +After(async function() { + // Optionally reset app state between scenarios if needed + // await this.driver.resetApp(); // Uncomment if you need to reset app state }); + +// Cleanup driver after all scenarios have run +AfterAll(async function() { + // console.log("Tearing down global driver instance..."); + if (driver) { + await driver.quit(); + } +}); + +const { setWorldConstructor } = require('@cucumber/cucumber'); +class CustomWorld { + get driver() { + return driver; + } +} +setWorldConstructor(CustomWorld); diff --git a/ios/package.json b/ios/package.json index ee08461..57847fd 100644 --- a/ios/package.json +++ b/ios/package.json @@ -8,7 +8,8 @@ "url": "git+https://github.com/browserstack/cucumber-js-appium-app-browserstack.git" }, "scripts": { - "sample-test": "browserstack-node-sdk cucumber-js features/single.feature", + "sample-test-old": "browserstack-node-sdk cucumber-js features/single.feature", + "sample-test": "browserstack-node-sdk cucumber-js features/module*/*.feature", "sample-local-test": "browserstack-node-sdk cucumber-js features/local.feature", "postinstall": "npm update browserstack-node-sdk" },