Selenium With Python: Advanced Scripting Techniques
Testing websites as they get updated is important to ensure a smooth user experience. Performing these tests manually takes a lot of time and effort. This is where automated testing with Selenium comes in handy!
Selenium is a tool that acts as a virtual user, helping automate website testing. When combined with Python, it becomes easy to write scripts that can interact with web pages and validate if features work correctly.
Selenium Python has transformed how teams approach test automation, enabling rapid scripting to validate web apps cross-browser. But mastering advanced interactions is key to handling dynamic sites. In this article, we will explore some advanced techniques in Selenium to create robust automation scripts that can handle complex real-world testing scenarios.
Whether it’s conditional waits reacting to page changes, keyboard shortcuts testing robustness, or distributed setups harnessing the cloud, these Selenium Python proficiencies take your automation to the next level.
Why Advanced Scripting Techniques Matter?
It may seem easy to write simple Selenium scripts with just basic clicks and text entries. However, real-world testing requires emulating intricate user flows across complex web apps.
Some benefits of mastering advanced scripting with Selenium include:
- Comprehensive Test Coverage: Just like real users, tests can interact with dynamic page elements, pop-ups, shortcuts etc. boosting coverage.
- Robust Test Execution: Flakiness reduces as scripts can now handle delays in page loads using waits and synchronize across windows.
- Scalable Automation: Sophisticated scripts created can serve as modular building blocks for test automation across sites due to reuse.
In essence, advancing Selenium skills helps improve test reliability, efficiency and coverage – saving time and effort.
Advanced Scripting Techniques
Here are some advanced scripting techniques in Selenium Python:
- Locating Web Elements
Automating interactions with any webpage is begun by locating the various elements like text boxes, buttons, links etc. that you want to work with.
Selenium uses a technique called locators to find web elements. There are different types of locators we can use such as ID, Class Name, Tag Name, Link Text and others.
For example, to find a search box on Google homepage, we can use the name locator as:
search_box = driver.find_element_by_name(‘q’)
The more unique locators we use like ID, the faster Selenium can locate elements.
- Interacting with Web Elements
Once elements are located on a page, we will perform various actions on them to imitate real user testing.
Some common interactions with elements include:
1. Sending text: Used to fill input fields and text areas
username.send_keys(‘JohnWick99’)
2. Clicking: Used to click on buttons, links, checkboxes
submit_button.click()
3. Dropdowns: Select options from dropdown menus
from selenium.webdriver.support.select import Select
dropdown = Select(driver.find_element_by_id(‘id’))
dropdown.select_by_visible_text(‘Option’)
There are many other interactions like clear text, get element text etc. that help automate everyday user actions.
- Handling Pop-up Alerts
Websites often use pop-up alerts to show notices, confirmation messages, errors etc. Selenium provides easy ways to handle them.
1. Switch focus to the alert
alert = driver.switch_to.alert
2. Get alert text
print(alert.text)
3. Accept/Dismiss alert
alert.accept()
alert.dismiss()
This allows the script to respond to alerts automatically during execution.
- Working with Frames and Windows
Modern websites tend to have multiple frames/windows open up. Selenium needs to switch focus between them to test properly.
To handle frames, we first switch focus from default content to the frame:
driver.switch_to.frame(‘frame_name’)
We interact inside the frame and switch back to default content later:
driver.switch_to.default_content()
For new browser windows, we follow similar approach:
main_page = driver.current_window_handle
for handle in driver.window_handles:
if handle != main_page:
driver.switch_to.window(handle)
This allows easy handling of multiple frames and windows in automation.
- Advanced Interactions
Besides basic clicks and text entries, creating robust tests requires simulating complex user actions like:
Mouse Actions:
from selenium.webdriver import ActionChains
actions = ActionChains(driver)
actions.drag_and_drop(source_element, target_element).perform()
Keyboard Actions:
from selenium.webdriver.common.keys import Keys
actions.key_down(Keys.CONTROL).send_keys(‘c’).key_up(Keys.CONTROL).perform() # CTRL + C
Waits:
from selenium.webdriver.support.ui import WebDriverWait
element = WebDriverWait(driver, 5).until(expect_condition) # Wait
These advanced interactions add realism to automated testing.
- Structuring Tests with the Page Object Model
As tests grow in complexity, we need better code organization. The Page Object Model helps by separating web page interaction code from test cases.
1. Create a Python class for each webpage
class GoogleSearchPage:
def __init__(self, driver)
self.driver = driver
self.search_box = driver.find_element_by_name(‘q’)
def search(self, query)
self.search_box.send_keys(query)
2. Use the classes in test scripts
search_page = GoogleSearchPage(driver)
search_page.search(‘Automation Testing’)
This modular design promotes code reuse between tests.
- Integrating Selenium with Unittest
Unittest is Python’s test framework included in the standard library. Let’s see how to integrate Selenium tests into unittest:
1. Import unittest and selenium modules
import unittest
from selenium import webdriver
2. Create test class
class GoogleSearchTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
def test_search(self):
# Test steps
def tearDown(self):
self.driver.quit()
if __name__ == ‘__main__’:
unittest.main()
Unittest classes structure the script and provide enhanced reporting for easy debugging.
- Running Tests Across Browsers
To run Selenium tests across browsers like Chrome, Firefox and Edge which render pages differently, we create a base TestClass:
import unittest
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
class TestBase(unittest.TestCase):
def setUp(self):
# Run on each browser
if browser == “chrome”:
self.driver = webdriver.Chrome(ChromeDriverManager().install())
elif browser == “firefox”:
self.driver = webdriver.Firefox(executable_path=GeckoDriverManager().install())
def tearDown(self):
self.driver.close()
# Child class
class MyTest(TestBase):
def test_run(self):
# Test case steps
The MyTest class inherits the driver setup/teardown methods to work across browsers with no added effort!
- Parallel Test Execution
Executing automation sequentially limits speed. Parallel execution splits tests across multiple threads and machines.
To enable Thread based parallel test runs in Selenium Python:
from threading import Thread
thread1 = Thread(target = test1, args=(driver,))
thread2 = Thread(target = test2, args=(driver,))
thread1.start(); thread2.start()
For even greater scalability, we can distribute test runs using Selenium Grid across multiple systems.
- Integrating Test Frameworks
While Selenium Python natively supports writing test cases, fuller-featured frameworks enhance structure and reporting.
PyTest Fixtures
The PyTest framework enhances test reliability through setup/teardown methods and shared data:
import pytest
from selenium import webdriver
@pytest.fixture
def driver():
driver = webdriver.Chrome()
yield driver
driver.quit()
def test_admin_login(driver):
driver.get(“http://adminarea.com”)
# Use fixture driver
Fixture functions initialize resources for test cases relieving duplication.
Unittest Parameterization
To run similar test flows across different inputs, use unittest capabilities:
import unittest
urls = [“http://url1”, “http://url2”]
class MyTestCase(unittest.TestCase):
@parameterized.expand([(url,) for url in urls])
def test_page(self, url):
driver.get(url)
# Assertions
This iterates the same test logic over all supplied URLs without rewriting.
Allure Reporting
For dynamic HTML test reports visualizing execution status, failures, logs etc, use the Allure framework:
@allure.step(“Navigate to Login”)
def login():
return f”Login Successful”
def test_login():
allure.dynamic.title(“Testing Login”)
with allure.step(“Enter Credentials”):
login()
assert login() == “Login Successful”
Beautiful collateral conveys test outcomes intuitively through custom annotations.
- Porting To Cross-Browser Testing
While scripts run locally often assume Chrome, targeting multiple runtimes mandates more flexible structures accommodating differences.
Centralized Configuration
Store browser selection and timeouts in dedicated config files:
settings.json
{
“browser”: “firefox”,
“headless”: false,
“timeout”: 20
}
import json
CONFIG = json.loads(open(“settings.json”))
driver = webdriver.Chrome() if CONFIG[“browser”] == “chrome” else webdriver.Firefox()
This keeps test logic independent of runtime parameters for simplicity.
Browser Abstraction
Further abstract browser initiation by moving it into factory methods:
from selenium.webdriver import Chrome, Firefox
class BrowserFactory():
def get_browser(browser):
if browser == “chrome”:
return Chrome()
elif browser == “firefox”:
return Firefox()
Encapsulating environment handling in separate classes maintains test case brevity.
These techniques boost overall automation performance and productivity.
Shortcomings of a Local Selenium Grid
A local Selenium Grid is a setup where you host and manage the Selenium Grid infrastructure within your own environment, such as within your office or data center. While it can be helpful for automating testing, especially for checking how your website or application works across different browsers, it comes with some challenges:
- Continuous Maintenance: Running a local Selenium Grid means you need to constantly maintain and update it. This includes keeping track of different browser versions, operating systems, and ensuring compatibility with the latest Selenium Server Grid version. This can be a time-consuming task and may require dedicated resources.
- Limited Scope: Maintaining a physical infrastructure to cover all possible combinations of browsers, browser versions, and operating systems can be complex and costly. It might not be feasible for every organization to manage such a comprehensive setup due to budget or resource constraints.
- Recursive Cost: The process of maintaining and updating a local Selenium Grid can incur a recursive cost. This means that as your testing needs evolve or new browser versions are released, you’ll need to invest more time and resources into keeping the Grid up to date.
Cloud-Based Selenium Grid
On the other hand, moving to а сloud-based Selenium Grid, suсh as LambdaTest, offers several advantages that overcome the limitations of а loсal setup:
- Independent of Physiсal Infrastruсture: With а сloud-based Selenium Grid, you don’t need to worry about managing physiсal servers or infrastruсture. Everything is hosted and managed by the сloud serviсe provider, freeing you from maintenanсe tasks.
- Extensive Test Coverage: Cloud-based grids like LambdaTest provide access to а wide range of browser versions, operating systems, and real devices. This allows you to test your website or application on over 3,000 сombinations, ensuring сomprehensive test сoverage.
- Parallel Testing and Effiсienсy: Cloud grids offer parallel testing capabilities, allowing you to run tests simultaneously across multiple browser сonfigurations. This significantly reduces the overall time spent on testing and improves efficiency.
- Cost-Effeсtive Options: Cloud services typiсally offer flexible priсing plans, allowing you to choose а subsсription that fits your project’s budget and testing requirements. You сan sсale up or down as needed without the overhead of managing hardware.
Running Selenium Python Automation Tests on а Cloud-Based Selenium Grid
Here’s а steр-by-steр guide on how to perform Selenium testing with Python on а сloud-based Selenium Grid, specifically using LambdaTest as the рlatform.
- Write your test scripts: Create your test scripts based on your chosen programming language and test automation framework.
- Install dependencies: Install the necessary libraries and dependencies to run tests on LambdaTest.
- Add LambdaTest credentials: Include your LambdaTest Username, Access Key, and Grid URL in your test scripts to connect to LambdaTest Cloud Grid.
- Configure automation capabilities: Specify the browsers, browser versions, and operating systems on which you want to test your web application. LambdaTest provides an Automation Capabilities Generator to auto-generate these combinations that you can add to your test scripts.
- Run the tests: Run your test scripts on LambdaTest. Once the tests are complete, you can access the LambdaTest Web Automation Dashboard to check the test results and logs for each configuration, allowing you to identify any bugs or issues that may have occurred during testing.
Conclusion
This article provided an overview of advanced web test automation concepts using Selenium and Python, including:
- Locating elements in adaptive ways to precisely simulate user actions
- Responding to alerts/popups, frames and windows
- Building robustness through waits and smart validation
- Adding realism via advanced mouse/keyboard simulation
- Promoting reusability through page object model
- Enhancing structure with integration frameworks like unittest
- Running tests reliably cross-browser/cross-system leveraging parallelization
Mastering these scripting proficiencies is key to creating enterprise-grade test automation frameworks that are quick, resilient and efficient.
They represent must-have strategies for test teams looking to ship high quality software faster through test automation. With the proliferation of modern web apps, these Selenium Python proficiencies deliver confidence and peace of mind by enabling comprehensive, scalable testing.