diff --git a/.env b/.env deleted file mode 100644 index 0987e7b..0000000 --- a/.env +++ /dev/null @@ -1,3 +0,0 @@ -BROWSERSTACK_USERNAME="BROWSERSTACK_USERNAME" -BROWSERSTACK_ACCESS_KEY="BROWSERSTACK_ACCESS_KEY" -URL="https://hub.browserstack.com/wd/hub" diff --git a/README.md b/README.md index 2074fe1..0d1124f 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,44 @@ # python-selenium-browserstack +Run python tests on browserstack using the SDK. ## Prerequisite ``` -python3 and pip3 should be installed +python3 should be installed ``` -## Steps to run test session - -- Install packages through requirements.txt +## Setup +* Clone the repo ``` -pip3 install -r requirements.txt +git clone -b sdk https://github.com/browserstack/python-selenium-browserstack.git +``` +* Install packages through requirements.txt ``` -- Update your credentials in .env file -```dotenv -BROWSERSTACK_USERNAME="BROWSERSTACK_USERNAME" -BROWSERSTACK_ACCESS_KEY="BROWSERSTACK_ACCESS_KEY" -URL="https://hub.browserstack.com/wd/hub" +pip3 install -r requirements.txt ``` -- Run tests +## Set BrowserStack Credentials +* Add your BrowserStack username and access key in the `browserstack.yml` config fle. +* You can also export them as environment variables, `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY`: + + #### For Linux/MacOS + ``` + export BROWSERSTACK_USERNAME= + export BROWSERSTACK_ACCESS_KEY= + ``` + #### For Windows + ``` + setx BROWSERSTACK_USERNAME= + setx BROWSERSTACK_ACCESS_KEY= + ``` - a. For parallel - ``` - python3 ./scripts/parallel.py - ``` - b. For local - ``` - python3 ./scripts/local.py - ``` +## Running tests +* Run sample test: + - To run the sample test across platforms defined in the `browserstack.yml` file, run: + ``` + browserstack-sdk ./tests/test.py + ``` +* Run tests on locally hosted website: + - To run the local test across platforms defined in the `browserstack.yml` file, run: + ``` + browserstack-sdk ./tests/local-test.py + ``` diff --git a/browserstack.yml b/browserstack.yml new file mode 100644 index 0000000..d698962 --- /dev/null +++ b/browserstack.yml @@ -0,0 +1,63 @@ +# ============================= +# Set BrowserStack Credentials +# ============================= +# Add your BrowserStack userName and accessKey here or set BROWSERSTACK_USERNAME and +# BROWSERSTACK_ACCESS_KEY as env variables +userName: YOUR_USERNAME +accessKey: YOUR_ACCESS_KEY + +# ====================== +# BrowserStack Reporting +# ====================== +# The following capabilities are used to set up reporting on BrowserStack: +# Set 'projectName' to the name of your project. Example, Marketing Website +projectName: BrowserStack Samples +# Set `buildName` as the name of the job / testsuite being run +buildName: browserstack build +# `buildIdentifier` is a unique id to differentiate every execution that gets appended to +# buildName. Choose your buildIdentifier format from the available expressions: +# ${BUILD_NUMBER} (Default): Generates an incremental counter with every execution +# ${DATE_TIME}: Generates a Timestamp with every execution. Eg. 05-Nov-19:30 +# Read more about buildIdentifiers here -> https://www.browserstack.com/docs/automate/selenium/organize-tests +buildIdentifier: '#${BUILD_NUMBER}' # Supports strings along with either/both ${expression} + +# ======================================= +# Platforms (Browsers / Devices to test) +# ======================================= +# Platforms object contains all the browser / device combinations you want to test on. +# Entire list available here -> (https://www.browserstack.com/list-of-browsers-and-platforms/automate) +platforms: + - os: OS X + osVersion: Big Sur + browserName: Chrome + browserVersion: latest + - os: Windows + osVersion: 10 + browserName: Edge + browserVersion: latest + - deviceName: Samsung Galaxy S22 Ultra + browserName: chrome # Try 'samsung' for Samsung browser + osVersion: 12.0 + +# ========================================== +# BrowserStack Local +# (For localhost, staging/private websites) +# ========================================== +# Set browserStackLocal to true if your website under test is not accessible publicly over the internet +# Learn more about how BrowserStack Local works here -> https://www.browserstack.com/docs/automate/selenium/local-testing-introduction +browserstackLocal: true # (Default false) +# browserStackLocalOptions: +# Options to be passed to BrowserStack local in-case of advanced configurations + # localIdentifier: # (Default: null) Needed if you need to run multiple instances of local. + # forceLocal: true # (Default: false) Set to true if you need to resolve all your traffic via BrowserStack Local tunnel. + # Entire list of arguments available here -> https://www.browserstack.com/docs/automate/selenium/manage-incoming-connections + +source: python-browserstack:sample-sdk:v1.0 + +# =================== +# Debugging features +# =================== +debug: false # # Set to true if you need screenshots for every selenium command ran +networkLogs: false # Set to true to enable HAR logs capturing +consoleLogs: errors # Remote browser's console debug levels to be printed (Default: errors) +# Available options are `disable`, `errors`, `warnings`, `info`, `verbose` (Default: errors) diff --git a/requirements.txt b/requirements.txt index b05ee02..6bb0568 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -python-dotenv browserstack-local >= 1.2.3 -selenium == 4.1.0 +selenium +browserstack-sdk diff --git a/scripts/local.py b/scripts/local.py deleted file mode 100644 index 1f41434..0000000 --- a/scripts/local.py +++ /dev/null @@ -1,64 +0,0 @@ -from dotenv import load_dotenv -import os -import json -from selenium import webdriver -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.chrome.options import Options as ChromeOptions -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.by import By -from browserstack.local import Local -from selenium.common.exceptions import NoSuchElementException - -load_dotenv() -BROWSERSTACK_USERNAME = os.environ.get("BROWSERSTACK_USERNAME") or "BROWSERSTACK_USERNAME" -BROWSERSTACK_ACCESS_KEY = os.environ.get("BROWSERSTACK_ACCESS_KEY") or "BROWSERSTACK_ACCESS_KEY" -URL = os.environ.get("URL") or "https://hub.browserstack.com/wd/hub" - -# Creates an instance of Local -bs_local = Local() - -# You can also set an environment variable - "BROWSERSTACK_ACCESS_KEY". -bs_local_args = { "key": BROWSERSTACK_ACCESS_KEY } - -# Starts the Local instance with the required arguments -bs_local.start(**bs_local_args) - -# Check if BrowserStack local instance is running -print("Local binary connected: ", bs_local.isRunning()) - -desired_cap = { - "os" : "OS X", - "osVersion" : "Sierra", - "buildName" : "browserstack-build-1", - "sessionName" : "BStack local python", - "local" : "true", - "userName": BROWSERSTACK_USERNAME, - "accessKey": BROWSERSTACK_ACCESS_KEY -} -desired_cap["source"] = "python:sample-main:v1.0" -options = ChromeOptions() -options.set_capability('bstack:options', desired_cap) -driver = webdriver.Remote( - command_executor=URL, - options=options) -try: - driver.get("http://bs-local.com:45691/check") - body_text = WebDriverWait(driver, 10).until( - EC.visibility_of_element_located((By.CSS_SELECTOR, 'body'))).text - # check if local connected successfully - if body_text == "Up and running": - # mark test as passed if Local is accessible - driver.execute_script( - 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"passed", "reason": "Local Test ran successfully"}}') - else: - # mark test as failed if Local not accessible - driver.execute_script( - 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed", "reason": "Local test setup failed"}}') -except Exception as err: - message = "Exception: " + str(err.__class__) + str(err.msg) - driver.execute_script( - 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed", "reason": ' + json.dumps(message) + '}}') - bs_local.stop() -# Stop the driver -driver.quit() -bs_local.stop() diff --git a/scripts/parallel.py b/scripts/parallel.py deleted file mode 100644 index 5fb1b86..0000000 --- a/scripts/parallel.py +++ /dev/null @@ -1,116 +0,0 @@ -from dotenv import load_dotenv -import os -import json -from selenium import webdriver -from selenium.webdriver.chrome.options import Options as ChromeOptions -from selenium.webdriver.firefox.options import Options as FirefoxOptions -from selenium.webdriver.safari.options import Options as SafariOptions -from selenium.webdriver.edge.options import Options as EdgeOptions -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.by import By -from selenium.common.exceptions import NoSuchElementException -from threading import Thread - -load_dotenv() -BROWSERSTACK_USERNAME = os.environ.get( - "BROWSERSTACK_USERNAME") or "BROWSERSTACK_USERNAME" -BROWSERSTACK_ACCESS_KEY = os.environ.get( - "BROWSERSTACK_ACCESS_KEY") or "BROWSERSTACK_ACCESS_KEY" -URL = os.environ.get("URL") or "https://hub.browserstack.com/wd/hub" - -capabilities = [ - { - "os": "OS X", - "osVersion": "Monterey", - "buildName": "browserstack-build-1", - "sessionName": "BStack parallel python", - "browserName": "chrome", - "browserVersion": "latest" - }, - { - "os": "Windows", - "osVersion": "11", - "buildName": "browserstack-build-1", - "sessionName": "BStack parallel python", - "browserName": "firefox", - "browserVersion": "latest" - }, - { - "osVersion": "10", - "deviceName": "Samsung Galaxy S20", - "buildName": "browserstack-build-1", - "sessionName": "BStack parallel python", - "browserName": "chrome", - }, -] - - -def get_browser_option(browser): - switcher = { - "chrome": ChromeOptions(), - "firefox": FirefoxOptions(), - "edge": EdgeOptions(), - "safari": SafariOptions(), - } - return switcher.get(browser, ChromeOptions()) - - -def run_session(cap): - bstack_options = { - "osVersion": cap["osVersion"], - "buildName": cap["buildName"], - "sessionName": cap["sessionName"], - "userName": BROWSERSTACK_USERNAME, - "accessKey": BROWSERSTACK_ACCESS_KEY - } - if "os" in cap: - bstack_options["os"] = cap["os"] - if "deviceName" in cap: - bstack_options['deviceName'] = cap["deviceName"] - bstack_options["source"] = "python:sample-main:v1.1" - if cap['browserName'] in ['ios']: - cap['browserName'] = 'safari' - options = get_browser_option(cap["browserName"].lower()) - if "browserVersion" in cap: - options.browser_version = cap["browserVersion"] - options.set_capability('bstack:options', bstack_options) - if cap['browserName'].lower() == 'samsung': - options.set_capability('browserName', 'samsung') - driver = webdriver.Remote( - command_executor=URL, - options=options) - try: - driver.get("https://bstackdemo.com/") - WebDriverWait(driver, 10).until(EC.title_contains("StackDemo")) - # Get text of an product - iPhone 12 - item_on_page = WebDriverWait(driver, 10).until( - EC.visibility_of_element_located((By.XPATH, '//*[@id="1"]/p'))).text - # Click the 'Add to cart' button if it is visible - WebDriverWait(driver, 10).until(EC.visibility_of_element_located( - (By.XPATH, '//*[@id="1"]/div[4]'))).click() - # Check if the Cart pane is visible - WebDriverWait(driver, 10).until(EC.visibility_of_element_located( - (By.CLASS_NAME, "float-cart__content"))) - # Get text of product in cart - item_in_cart = WebDriverWait(driver, 10).until(EC.visibility_of_element_located( - (By.XPATH, '//*[@id="__next"]/div/div/div[2]/div[2]/div[2]/div/div[3]/p[1]'))).text - # Verify whether the product (iPhone 12) is added to cart - if item_on_page == item_in_cart: - # Set the status of test as 'passed' or 'failed' based on the condition; if item is added to cart - driver.execute_script( - 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"passed", "reason": "iPhone 12 has been successfully added to the cart!"}}') - except NoSuchElementException as err: - message = "Exception: " + str(err.__class__) + str(err.msg) - driver.execute_script( - 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed", "reason": ' + json.dumps(message) + '}}') - except Exception as err: - message = "Exception: " + str(err.__class__) + str(err.msg) - driver.execute_script( - 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed", "reason": ' + json.dumps(message) + '}}') - # Stop the driver - driver.quit() - - -for cap in capabilities: - Thread(target=run_session, args=(cap,)).start() diff --git a/tests/local-test.py b/tests/local-test.py new file mode 100644 index 0000000..2478a77 --- /dev/null +++ b/tests/local-test.py @@ -0,0 +1,33 @@ +import json +from selenium import webdriver +from selenium.webdriver.chrome.options import Options as ChromeOptions + +options = ChromeOptions() +options.set_capability('sessionName', 'BStack Local Test') + +# The webdriver management will be handled by the browserstack-sdk +# so this will be overridden and tests will run browserstack - +# without any changes to the test files! +driver = webdriver.Remote( + command_executor='http://localhost:4444/wd/hub', + options=options) + +try: + driver.get('http://bs-local.com:45454') + page_title = driver.title + # check if local connected successfully + if 'BrowserStack Local' in page_title: + # mark test as passed if Local website is accessible + driver.execute_script( + 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"passed", "reason": "Local Test ran successfully"}}') + else: + # mark test as failed if Local website is not accessible + driver.execute_script( + 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed", "reason": "Local test setup failed"}}') +except Exception as err: + message = 'Exception: ' + str(err.__class__) + str(err.msg) + driver.execute_script( + 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed", "reason": ' + json.dumps(message) + '}}') + +# Stop the driver +driver.quit() diff --git a/scripts/single.py b/tests/test.py similarity index 65% rename from scripts/single.py rename to tests/test.py index b8c4e5a..c242b14 100644 --- a/scripts/single.py +++ b/tests/test.py @@ -1,35 +1,21 @@ -from dotenv import load_dotenv -import os import json from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.chrome.options import Options as ChromeOptions from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.chrome.options import Options as ChromeOptions -load_dotenv() -BROWSERSTACK_USERNAME = os.environ.get("BROWSERSTACK_USERNAME") or "BROWSERSTACK_USERNAME" -BROWSERSTACK_ACCESS_KEY = os.environ.get("BROWSERSTACK_ACCESS_KEY") or "BROWSERSTACK_ACCESS_KEY" -URL = os.environ.get("URL") or "https://hub.browserstack.com/wd/hub" - -bstack_options = { - "os" : "OS X", - "osVersion" : "Monterey", - "buildName" : "browserstack-build-1", - "sessionName" : "BStack single python", - "userName": BROWSERSTACK_USERNAME, - "accessKey": BROWSERSTACK_ACCESS_KEY -} -bstack_options["source"] = "python:sample-main:v1.0" +# The webdriver management will be handled by the browserstack-sdk +# so this will be overridden and tests will run browserstack - +# without any changes to the test files! options = ChromeOptions() -options.set_capability('bstack:options', bstack_options) -driver = webdriver.Remote( - command_executor=URL, - options=options) +options.set_capability('sessionName', 'BStack Sample Test') +driver = webdriver.Chrome(options=options) + try: - driver.get("https://bstackdemo.com/") - WebDriverWait(driver, 10).until(EC.title_contains("StackDemo")) + driver.get('https://bstackdemo.com/') + WebDriverWait(driver, 10).until(EC.title_contains('StackDemo')) # Get text of an product - iPhone 12 item_on_page = WebDriverWait(driver, 10).until( EC.visibility_of_element_located((By.XPATH, '//*[@id="1"]/p'))).text @@ -38,8 +24,8 @@ (By.XPATH, '//*[@id="1"]/div[4]'))).click() # Check if the Cart pane is visible WebDriverWait(driver, 10).until(EC.visibility_of_element_located( - (By.CLASS_NAME, "float-cart__content"))) - ## Get text of product in cart + (By.CLASS_NAME, 'float-cart__content'))) + # Get text of product in cart item_in_cart = WebDriverWait(driver, 10).until(EC.visibility_of_element_located( (By.XPATH, '//*[@id="__next"]/div/div/div[2]/div[2]/div[2]/div/div[3]/p[1]'))).text # Verify whether the product (iPhone 12) is added to cart @@ -52,12 +38,13 @@ driver.execute_script( 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed", "reason": "iPhone 12 not added to the cart!"}}') except NoSuchElementException as err: - message = "Exception: " + str(err.__class__) + str(err.msg) + message = 'Exception: ' + str(err.__class__) + str(err.msg) driver.execute_script( 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed", "reason": ' + json.dumps(message) + '}}') except Exception as err: - message = "Exception: " + str(err.__class__) + str(err.msg) + message = 'Exception: ' + str(err.__class__) + str(err.msg) driver.execute_script( 'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed", "reason": ' + json.dumps(message) + '}}') -# Stop the driver -driver.quit() +finally: + # Stop the driver + driver.quit()