E2E Testing with Selenium WebDriver for Websites
Release is delayed by three days due to manual regression. Every UI change breaks old scenarios, the team spends hours re-checking. Flaky tests undermine trust in automation: tests passed yesterday, fail today for no apparent reason, and developers waste time debugging failures. We offer to implement E2E tests with Selenium WebDriver — this reduces regression from three days to one hour and provides 99% stability.
Selenium is the oldest browser automation standard. Supports all major browsers, all programming languages, integrates with TestNG, JUnit, pytest. It is chosen when cross-browser compatibility via Selenium Grid is needed or there is already legacy infrastructure. We use Selenium 4 with headless mode, enabling tests to run on a server without a graphical environment.
Problems Solved by Selenium E2E Tests
Brittleness of tests due to dynamic content. Selenium uses waits (WebDriverWait) for stabilization. We configure explicit waits with state checks — this reduces flaky tests. In practice, the number of reruns drops by 80%, and test run stability reaches 99%. — Selenium Documentation
Lack of parallel execution. Selenium Grid allows running tests on multiple nodes simultaneously. With Docker Compose, infrastructure is deployed in 10 minutes. Using 4 nodes, regression of 100 tests completes in 25 minutes instead of 100.
High maintenance cost of tests. We apply the Page Object pattern — each screen is described by a separate class. A selector change is fixed in one place. Time to update tests after UI changes is reduced by 60%. That's 2.5 times faster compared to direct selectors.
How We Do It: Stack and Approach
We use Selenium 4 with headless Chrome/Firefox. For Python — pytest with fixtures, for Java — TestNG. We code following the Page Object Model, which gives reusability and readability. With over 8 years of Selenium experience, we guarantee test stability and offer a 3-month warranty. Testing budget savings can reach 500,000 rubles per year, and projects start from 200,000 rubles.
Example of Chrome headless configuration:
# conftest.py
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
@pytest.fixture(scope='session')
def driver():
options = Options()
options.add_argument('--headless=new')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(options=options)
driver.implicitly_wait(10)
driver.set_window_size(1280, 900)
yield driver
driver.quit()
Test for login with redirect wait:
# tests/test_login.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class TestLogin:
def test_valid_login(self, driver):
driver.get('http://localhost:8000/login')
driver.find_element(By.CSS_SELECTOR, '[data-cy="email"]').send_keys('user@example')
driver.find_element(By.CSS_SELECTOR, '[data-cy="password"]').send_keys('password123')
driver.find_element(By.CSS_SELECTOR, '[data-cy="submit"]').click()
wait = WebDriverWait(driver, 10)
wait.until(EC.url_contains('/dashboard'))
assert '/dashboard' in driver.current_url
def test_invalid_login(self, driver):
driver.get('http://localhost:8000/login')
driver.find_element(By.ID, 'email').send_keys('wrong@example')
driver.find_element(By.ID, 'password').send_keys('wrongpass')
driver.find_element(By.XPATH, '//button[@type="submit"]').click()
error = WebDriverWait(driver, 5).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, '.error-message'))
)
assert 'Неверный' in error.text
How the Page Object Pattern Simplifies Test Maintenance?
Page Object isolates selectors and page logic. If a frontend developer changes id to data-cy, the fix is made only in the page class, not in all tests. This reduces maintenance time by 60% compared to direct selectors. Moreover, the Page Object Model creates a unified API for element interaction.
# pages/login_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class LoginPage:
URL = '/login'
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
def open(self):
self.driver.get(f'http://localhost:8000{self.URL}')
return self
def enter_email(self, email: str):
self.driver.find_element(By.ID, 'email').send_keys(email)
return self
def enter_password(self, password: str):
self.driver.find_element(By.ID, 'password').send_keys(password)
return self
def submit(self):
self.driver.find_element(By.CSS_SELECTOR, 'button[type=submit]').click()
return self
def get_error_message(self) -> str:
element = self.wait.until(EC.visibility_of_element_located((By.CLASS_NAME, 'error')))
return element.text
How to Set Up Parallel Execution with Selenium Grid?
To speed up regression, we deploy Selenium Grid in Docker. The file docker-compose-grid.yml starts a hub and 3 Chrome nodes + Firefox node.
Example docker-compose-grid.yml
# docker-compose-grid.yml
services:
selenium-hub:
image: selenium/hub:4.18
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
chrome-node:
image: selenium/node-chrome:4.18
environment:
SE_EVENT_BUS_HOST: selenium-hub
depends_on: [selenium-hub]
deploy:
replicas: 3
firefox-node:
image: selenium/node-firefox:4.18
environment:
SE_EVENT_BUS_HOST: selenium-hub
Connecting to Grid: RemoteWebDriver with hub address.
from selenium.webdriver.remote.webdriver import RemoteWebDriver
driver = RemoteWebDriver(
command_executor='http://selenium-hub:4444/wd/hub',
options=ChromeOptions()
)
Comparison of wait types in Selenium:
| Wait Type | Description | When to Use |
|---|---|---|
| Implicit (implicitly_wait) | Waits for element to appear in DOM (global) | For simple pages with predictable loading |
| Explicit (WebDriverWait + EC) | Waits for specific condition (visibility, clickability) | For dynamic content, AJAX requests |
| FluentWait | Explicit wait with polling and exception ignoring | For complex scenarios with changing conditions |
Why Selenium is Better Than Other Tools?
| Tool | Cross-browser | Parallel Execution | Languages | Execution Speed |
|---|---|---|---|---|
| Selenium | All browsers | Selenium Grid | Java, Python, C#, JS | Medium |
| Cypress | Only Chrome | No | JS | High |
| Playwright | Chrome, Firefox, Safari | Built-in | JS, Python, C# | High |
Selenium is indispensable when support for Internet Explorer, Edge Legacy, or distributed execution on a farm of 10+ machines is required. For example, in one project we parallelized 200 tests on 8 nodes — regression completed in 18 minutes instead of 4 hours. This saved the client over 400,000 rubles per year. QA cost reduction up to 80%.
To combat flaky tests, we use FluentWait with polling every 500 ms and ignoring StaleElementReferenceException. This gives 99% stability on dynamic pages.
What's Included in the Work: Deliverables
- Setting up test infrastructure (Selenium Grid, Docker, CI agents).
- Developing Page Object classes for key pages (up to 20 classes).
- Writing 20–50 test cases (positive/negative, edge cases).
- CI/CD integration (Jenkins, GitLab CI, GitHub Actions).
- Report generation (Allure, pytest-html with coverage metrics).
- Documentation on execution and maintenance.
- Team training for 1–2 days.
- Access to all test artifacts and ongoing support.
Work Process
- Analysis — study the application, identify critical scenarios, estimate effort.
- Design — define Page Object structure, test data, Grid configuration.
- Implementation — write Page Object classes and tests, configure waits.
- Test — run on staging, fix flaky tests, measure stability.
- Deploy — integrate into CI, set up reports, demonstrate to the team.
Timelines (Approximate)
- From 5 business days (30 tests, basic set, no Grid).
- Up to 15 business days (50+ tests, Grid, full documentation, training).
We provide an accurate estimate after project audit. Our automation experience: over 50 projects, 8 years working with Selenium. Contact us — and we'll show how E2E tests can cut regression from three days to one hour. Order E2E test development now, and your team will stop wasting time on manual rechecks. Testing budget savings can reach 500,000 rubles per year. Get a consultation — we'll answer all your questions.







