Skip to content

Commit

Permalink
Show migration stages (raw Selenium to SeleniumBase)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdmintz committed Jun 20, 2022
1 parent d1f1547 commit 480accc
Show file tree
Hide file tree
Showing 8 changed files with 436 additions and 0 deletions.
65 changes: 65 additions & 0 deletions examples/migration/raw_selenium/ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
## ✅ Support for migrating from raw Selenium to SeleniumBase


### 🔵 Here are some examples that can help you understand how to migrate from raw Selenium to SeleniumBase

The five main examples in the [examples/migration/raw_selenium](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium) folder are:

* [flaky_messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/flaky_messy_raw.py)
* [long_messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/long_messy_raw.py)
* [messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/messy_raw.py)
* [refined_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/refined_raw.py)
* [simple_sbase.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/simple_sbase.py)

Each of these examples is structured as a test that can be run with ``pytest``. They all inherit ``unittest.TestCase`` either directly, or via ``seleniumbase.BaseCase``, which extends it. This provides automatically-called ``setUp()`` and ``tearDown()`` methods before and after each test.

> These examples show the evolution of tests from raw Selenium to SeleniumBase. By understanding common progressions of Selenium engineers, you can avoid making the same mistakes as they did, and learn to write good tests efficiently without the long learning curve.
* [flaky_messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/flaky_messy_raw.py)

This is common example of how newcomers to Selenium write tests (assuming they've already learned how to break out reusuable code into ``setUp()`` and ``tearDown()`` methods). It uses ``find_element()`` calls, which can lead to flaky tests because those calls fail if a page element is slow to load.

* [long_messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/long_messy_raw.py)

At the next stage of learning, newcomers to Selenium realize that their tests are flaky, so they start replacing existing ``find_element()`` calls with ``WebDriverWait`` and internal Selenium ``expected_conditions`` methods, such as ``visibility_of_element_located`` and ``element_to_be_clickable``. This can result in long/messy tests that are unmaintainable if not written carefully.

* [messy_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/messy_raw.py)

By this stage, newcomers to Selenium have evolved into legitimate test automation engineers. They have become better at writing reusable code, so they've broken down the long ``WebDriverWait`` and ``expected_conditions`` calls into shorter method calls, which are easier to read, but could still be improved on for better maintainability. Here, individual page actions are still written out as multiple lines of code, (or multiple method calls per line), which isn't efficient.

* [refined_raw.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/refined_raw.py)

By now, the test automation engineer has become an expert in breaking out code into reusable methods, and the test itself has been simplified down to a single page action per line. The code is easy to read and easy to maintain. The journey of writing a complete test automation framework for Selenium has begun.

* [simple_sbase.py](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/migration/raw_selenium/simple_sbase.py)

With a complete test automation framework built, most of the hard work is already done for you. By importing ``BaseCase`` into your test classes, your tests gain access to all SeleniumBase methods, which can simplify your code. SeleniumBase also provides a lot of additional functionality that isn't included with raw Selenium.


### 🔵 How is SeleniumBase different from raw Selenium?

<div>
<p>💡 With raw Selenium, you need to set up your own options-parser for configuring tests from the command-line. SeleniumBase has its own options-parser, which expands on the <code>pytest</code>, <code>nosetests</code>, and <code>behave</code> test runners, allowing you to set the browser, enable the dashboard, and do many more things.</p>

<p>💡 With raw Selenium, you have to manually download drivers (<i>eg. chromedriver</i>) before running tests. With SeleniumBase's driver manager, that's done automatically for you if the required driver isn't already on your PATH. There are also console scripts available for more control (eg. <code>sbase install chromedriver latest</code> to download the latest version of chromedriver to a local SeleniumBase directory).</p>

<p>💡 With raw Selenium, commands that use selectors need to specify the type of selector (eg. <code>"css selector", "button#myButton"</code>). With SeleniumBase, there's auto-detection between CSS Selectors and XPath, which means you don't need to specify the type of selector in your commands (<i>but optionally you could</i>).</p>

<p>💡 SeleniumBase methods often perform multiple actions in a single method call. For example, <code>self.type(selector,text)</code> does the following:<br />1. Waits for the element to be visible.<br />2. Waits for the element to be interactive.<br />3. Clears the text field.<br />4. Types in the new text.<br />5. Presses Enter/Submit if the text ends in "\n".<br />With raw Selenium, those actions require multiple method calls.</p>

<p>💡 SeleniumBase uses default timeout values when not set, which means that methods automatically wait for elements to appear (<i>up to the timeout limit</i>) before failing:<br />✅<code>self.click("button")</code><br />With raw Selenium, methods would fail instantly (<i>by default</i>) if an element needed more time to load:<br />❌<code>self.driver.find_element(by="css selector", value="button").click()</code><br />(Reliable code is better than unreliable code.)</p>

<p>💡 SeleniumBase lets you change the explicit timeout values of methods:<br />✅<code>self.click("button",timeout=10)</code><br />With raw Selenium, that requires more code:<br />❌<code>WebDriverWait(driver,10).until(EC.element_to_be_clickable("css selector", "button")).click()</code><br />(Simple code is better than complex code.)</p>

<p>💡 SeleniumBase gives you clean error output when a test fails. With raw Selenium, error messages can get very messy.</p>

<p>💡 SeleniumBase gives you the option to generate a dashboard and reports for tests. It also saves screenshots from failing tests to the <code>./latest_logs/</code> folder. Raw Selenium does not have these options out-of-the-box.</p>

<p>💡 SeleniumBase includes desktop GUI apps for running tests, such as <b>SeleniumBase Commander</b> for <code>pytest</code>, and <b>SeleniumBase Behave GUI.</b></p>

<p>💡 SeleniumBase has its own Recorder & Test Generator that can create tests from manual browser actions. SeleniumBase also has many other useful tools and console scripts for getting things done quickly. (<i>See the documentation for more details!</i>)</p>
</div>

--------

[<img src="https://seleniumbase.io/cdn/img/fancy_logo_14.png" title="SeleniumBase" width="290">](https://github.com/seleniumbase/SeleniumBase)
Empty file.
66 changes: 66 additions & 0 deletions examples/migration/raw_selenium/flaky_messy_raw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Flaky Raw Selenium Example - (This test does NOT use SeleniumBase)"""
import sys
from selenium import webdriver
from selenium.webdriver.common.by import By
from unittest import TestCase


class FlakyMessyRawSelenium(TestCase):
def setUp(self):
self.driver = None
options = webdriver.ChromeOptions()
options.add_argument("--disable-notifications")
if "linux" in sys.platform:
options.add_argument("--headless")
options.add_experimental_option(
"excludeSwitches", ["enable-automation"],
)
prefs = {
"credentials_enable_service": False,
"profile.password_manager_enabled": False,
}
options.add_experimental_option("prefs", prefs)
options.add_experimental_option("w3c", True)
self.driver = webdriver.Chrome(options=options)

def tearDown(self):
if self.driver:
try:
if self.driver.service.process:
self.driver.quit()
except Exception:
pass

def is_element_visible(self, selector, by="css selector"):
try:
element = self.driver.find_element(by, selector)
if element.is_displayed():
return True
except Exception:
pass
return False

def test_add_item_to_cart(self):
self.driver.get("https://www.saucedemo.com")
by_css = By.CSS_SELECTOR # "css selector"
element = self.driver.find_element(by_css, "#user-name")
element.clear()
element.send_keys("standard_user")
element = self.driver.find_element(by_css, "#password")
element.clear()
element.send_keys("secret_sauce")
element.submit()
self.driver.find_element(by_css, "div.inventory_list")
element = self.driver.find_element(by_css, "span.title")
self.assertEqual(element.text, "PRODUCTS")
self.driver.find_element(by_css, 'button[name*="backpack"]').click()
self.driver.find_element(by_css, "#shopping_cart_container a").click()
element = self.driver.find_element(by_css, "span.title")
self.assertEqual(element.text, "YOUR CART")
element = self.driver.find_element(by_css, "div.cart_item")
self.assertIn("Backpack", element.text)
self.driver.find_element(by_css, "#remove-sauce-labs-backpack").click()
self.assertFalse(self.is_element_visible("div.cart_item"))
self.driver.find_element(by_css, "#react-burger-menu-btn").click()
self.driver.find_element(by_css, "a#logout_sidebar_link").click()
self.driver.find_element(by_css, "input#login-button")
90 changes: 90 additions & 0 deletions examples/migration/raw_selenium/long_messy_raw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Long & Messy Raw Selenium Example - (This test does NOT use SeleniumBase)"""
import sys
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from unittest import TestCase


class LongMessyRawSelenium(TestCase):
def setUp(self):
self.driver = None
options = webdriver.ChromeOptions()
options.add_argument("--disable-notifications")
if "linux" in sys.platform:
options.add_argument("--headless")
options.add_experimental_option(
"excludeSwitches", ["enable-automation"],
)
prefs = {
"credentials_enable_service": False,
"profile.password_manager_enabled": False,
}
options.add_experimental_option("prefs", prefs)
options.add_experimental_option("w3c", True)
self.driver = webdriver.Chrome(options=options)

def tearDown(self):
if self.driver:
try:
if self.driver.service.process:
self.driver.quit()
except Exception:
pass

def test_add_item_to_cart(self):
self.driver.get("https://www.saucedemo.com")
by_css = By.CSS_SELECTOR # "css selector"
element = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((by_css, "#user-name"))
)
element.clear()
element.send_keys("standard_user")
element = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((by_css, "#password"))
)
element.clear()
element.send_keys("secret_sauce")
element.submit()
WebDriverWait(self.driver, 10).until(
EC.visibility_of_element_located((by_css, "div.inventory_list"))
)
element = WebDriverWait(self.driver, 10).until(
EC.visibility_of_element_located((by_css, "span.title"))
)
self.assertEqual(element.text, "PRODUCTS")
element = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((by_css, 'button[name*="backpack"]'))
)
element.click()
element = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((by_css, "#shopping_cart_container a"))
)
element.click()
element = WebDriverWait(self.driver, 10).until(
EC.visibility_of_element_located((by_css, "span.title"))
)
self.assertEqual(element.text, "YOUR CART")
element = WebDriverWait(self.driver, 10).until(
EC.visibility_of_element_located((by_css, "div.cart_item"))
)
self.assertIn("Backpack", element.text)
element = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((by_css, "#remove-sauce-labs-backpack"))
)
element.click()
WebDriverWait(self.driver, 10).until(
EC.invisibility_of_element((by_css, "div.cart_item"))
)
element = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((by_css, "#react-burger-menu-btn"))
)
element.click()
element = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((by_css, "a#logout_sidebar_link"))
)
element.click()
WebDriverWait(self.driver, 10).until(
EC.visibility_of_element_located((by_css, "input#login-button"))
)
78 changes: 78 additions & 0 deletions examples/migration/raw_selenium/messy_raw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Messy Raw Selenium Example - (This test does NOT use SeleniumBase)"""
import sys
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from unittest import TestCase


class MessyRawSelenium(TestCase):
def setUp(self):
self.driver = None
options = webdriver.ChromeOptions()
options.add_argument("--disable-notifications")
if "linux" in sys.platform:
options.add_argument("--headless")
options.add_experimental_option(
"excludeSwitches", ["enable-automation"],
)
prefs = {
"credentials_enable_service": False,
"profile.password_manager_enabled": False,
}
options.add_experimental_option("prefs", prefs)
options.add_experimental_option("w3c", True)
self.driver = webdriver.Chrome(options=options)

def tearDown(self):
if self.driver:
try:
if self.driver.service.process:
self.driver.quit()
except Exception:
pass

def wait_for_element_visible(
self, selector, by="css selector", timeout=10
):
return WebDriverWait(self.driver, timeout).until(
EC.visibility_of_element_located((by, selector))
)

def wait_for_element_clickable(
self, selector, by="css selector", timeout=10
):
return WebDriverWait(self.driver, timeout).until(
EC.element_to_be_clickable((by, selector))
)

def wait_for_element_not_visible(
self, selector, by="css selector", timeout=10
):
return WebDriverWait(self.driver, timeout).until(
EC.invisibility_of_element((by, selector))
)

def test_add_item_to_cart(self):
self.driver.get("https://www.saucedemo.com")
element = self.wait_for_element_clickable("#user-name")
element.clear()
element.send_keys("standard_user")
element = self.wait_for_element_clickable("#password")
element.clear()
element.send_keys("secret_sauce")
element.submit()
self.wait_for_element_visible("div.inventory_list")
element = self.wait_for_element_visible("span.title")
self.assertEqual(element.text, "PRODUCTS")
self.wait_for_element_clickable('button[name*="backpack"]').click()
self.wait_for_element_clickable("#shopping_cart_container a").click()
element = self.wait_for_element_visible("span.title")
self.assertEqual(element.text, "YOUR CART")
element = self.wait_for_element_visible("div.cart_item")
self.assertIn("Backpack", element.text)
self.wait_for_element_clickable("#remove-sauce-labs-backpack").click()
self.wait_for_element_not_visible("div.cart_item")
self.wait_for_element_clickable("#react-burger-menu-btn").click()
self.wait_for_element_clickable("a#logout_sidebar_link").click()
self.wait_for_element_visible("input#login-button")
15 changes: 15 additions & 0 deletions examples/migration/raw_selenium/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[pytest]

# Display console output, disable cacheprovider, and have the ipdb debugger replace pdb:
addopts = --capture=no -p no:cacheprovider --pdbcls=IPython.terminal.debugger:TerminalPdb

# Ignore warnings such as DeprecationWarning and PytestUnknownMarkWarning
filterwarnings =
ignore::pytest.PytestWarning
ignore:.*U.*mode is deprecated:DeprecationWarning

# Set pytest discovery rules:
# (Most of the rules here are similar to the default rules.)
# (unittest.TestCase rules override the rules here for classes and functions.)
python_files = *.py
python_functions = test_*

0 comments on commit 480accc

Please sign in to comment.