Who Is This Course For?
This course is for QA testers and Python learners who want to automate real websites professionally. You'll start by writing your first browser automation script and finish with a complete, structured Pytest framework covering Selenium Grid, Shadow DOM, parametrized tests, Page Object Model, and HTML reporting — skills that make your CV stand out.
- Installing Python 3 and setting up PyCharm or VS Code as your editor
- Creating a virtual environment with
python -m venv .venv— keeps packages isolated - Installing Selenium:
pip install selenium— the core automation library - WebDriver Manager:
pip install webdriver-manager— auto-handles Chrome version - Understanding what lives inside the
selenium.webdriverpackage
- Importing Selenium and understanding why
from selenium import webdriveris the entry point driver = webdriver.Chrome()— launch Chrome under full automation control- Running the script and watching Chrome open — this is the moment everything becomes real
driver.get(url)— open any URL, like typing an address into the browser bardriver.back()anddriver.forward()— simulate the browser Back/Forward buttonsdriver.refresh()— reload the page, same as pressing F5driver.quit()— close everything and end the session completelydriver.close()— close just the current window (keeps session alive)
driver.title— read the page title to confirm you landed on the right pagedriver.current_url— verify the URL after navigation or redirectdriver.page_source— get the full HTML — useful for debuggingdriver.maximize_window()— ensures consistent element positions across test runs- The lifecycle of an automated test: open → navigate → interact → assert → close
from selenium.webdriver.common.by import By— import the locator helperBy.ID— the most stable locator; every element with an ID is uniquely identifiedBy.NAME— works well for form input fields that have a name attributeBy.XPATH— the most flexible; can navigate any path through the page structureBy.CSS_SELECTOR— shorter than XPath, uses the same rules as CSS stylesheetsBy.CLASS_NAME— targets elements by their styling classBy.LINK_TEXT— find a link by exactly what it says on screenfind_elements()(plural) — returns all matching elements as a list
- Relative XPath (
//tag[@attr='value']) vs absolute XPath — why relative is better - XPath axes — moving up (
parent::), sideways (following-sibling::) in the page tree - Partial matching with
contains()andstarts-with()for dynamic elements - Elements with changing IDs — how XPath text matching keeps your locator stable
- CSS Selector patterns:
input#id,div.class > span— concise and fast - Test your locator in DevTools (F12 → Console →
$x("your-xpath")) before coding
element.click()— the most-used action in automation: buttons, links, checkboxeselement.send_keys("text")— type into any input field or text areaelement.clear()— wipe existing text before typing new contentelement.submit()— submit the parent form without clicking Submit explicitlyis_displayed(),is_enabled(),is_selected()— verify element state before acting
element.text— read the visible text inside a label, button, or paragraphelement.get_attribute("value")— read the current value of an input fieldget_attribute("href"),"class","src"— read any HTML attributeelement.tag_name— confirm which HTML tag you're dealing with
driver.execute_script("alert('hello')")— trigger browser popups through JSexecute_script("return arguments[0].value;", el)— read value when.get_attributefailsexecute_script("arguments[0].click();", el)— click hidden or overlapping elements
from selenium.webdriver.common.action_chains import ActionChainsActionChains(driver)— create a gesture builder that queues multiple actions.move_to_element(el)— hover over an element to reveal a dropdown or tooltip.double_click(el)— double-click to edit inline text or open items.context_click(el)— right-click to open a context menu.drag_and_drop(source, target)— drag one element and drop it onto another.click_and_hold(el)+.release(el)— for sliders and custom drag behaviour.perform()— execute all queued actions at once, in order
from selenium.webdriver.common.keys import Keys- Special keys:
Keys.ENTER,Keys.TAB,Keys.ESCAPE,Keys.BACKSPACE - Key combinations:
ActionChains(driver).send_keys(Keys.CONTROL, 'a').perform()
- An iFrame is a mini web page inside the main page — payment widgets, maps, rich text editors
driver.switch_to.frame("frame-name")— switch by name or ID attributedriver.switch_to.frame(0)— switch by index (0 = first frame on the page)driver.switch_to.frame(element)— switch by passing the iFrame WebElement directlydriver.switch_to.default_content()— return to the main page documentdriver.switch_to.parent_frame()— step one level out of a nested frame
- Switching into a frame inside another frame: switch to outer first, then switch to inner
- Always switch back to
default_content()when you're done — prevents test interference
- Shadow DOM — a hidden subtree attached to an element, invisible to normal DOM queries
- Why
find_element(By.CSS_SELECTOR, ...)returns nothing inside a Shadow DOM - Step 1: find the shadow host — the outer element that "contains" the hidden content
shadow_root = host_element.shadow_root— access the hidden subtreeshadow_root.find_element(By.CSS_SELECTOR, "inner-tag")— search inside the shadow- Nested shadow DOM — chaining
.shadow_rootacross multiple levels - JavaScript fallback:
execute_script("return arguments[0].shadowRoot", host)for older Chrome versions
from selenium.webdriver.support.ui import Selectselect = Select(driver.find_element(By.ID, "dropdown-id"))— wrap the dropdownselect.select_by_visible_text("Canada")— choose by what users see on screenselect.select_by_value("CA")— choose by the hidden HTML value attributeselect.select_by_index(2)— choose by position in the list (0-based)select.options— list of all available options as WebElements- Multi-select listboxes — calling
select_by_*multiple times selects multiple items
- A JavaScript alert blocks the entire page until you respond — Selenium must handle it
alert = driver.switch_to.alert— switch focus to the popupalert.accept()— click OK (also dismisses a simple alert)alert.dismiss()— click Cancel on a confirm dialog
driver.window_handles— returns a list of all currently open window handlesdriver.current_window_handle— get the handle for the current active windowdriver.switch_to.window(handle)— switch focus to another window or tabdriver.close()— close the active window, thenswitch_to.window(original)to go back- Best practice: save the original handle before any action that opens new windows
- Setting a custom download folder using
ChromeOptionspreferences prefs = {"download.default_directory": "/your/path"}— tell Chrome where to saveoptions.add_experimental_option("prefs", prefs)— apply before creating the driver- After downloading, use
os.path.exists(filepath)to assert the file arrived
from selenium.webdriver.chrome.options import Optionsoptions.add_argument("--headless")— run without a visible browser window (CI/CD friendly)options.add_argument("--start-maximized")— full screen for consistent element positionsoptions.add_experimental_option("excludeSwitches", ["enable-automation"])— remove "controlled by automation" banneroptions.add_experimental_option("useAutomationExtension", False)— improve site compatibility
- Why tests fail on fast-loading apps — your Python runs faster than the browser renders
driver.implicitly_wait(10)— global patience: retry every element find for up to 10 secondsWebDriverWait(driver, 10).until(...)— smart wait: wait for exactly one conditionEC.visibility_of_element_located— wait until element is both present AND visibleEC.element_to_be_clickable— wait until element is ready to receive a clickdriver.set_page_load_timeout(30)— don't wait forever if a page hangs- OrangeHRM live project — real login automation with proper waits applied
- Grid = one coordinator + many worker machines running tests in parallel
- Selenium Grid 4 — simplified setup, worker nodes self-register automatically
- Starting the Grid server:
java -jar selenium-server.jar standalone - Grid UI at
http://localhost:4444— see available browsers and test sessions
from selenium.webdriver import Remotedriver = Remote(command_executor="http://localhost:4444/wd/hub", options=options)- Your test code is almost identical — only the driver creation line changes
- Why Grid matters for CI/CD — tests run on a server without any display
- Installing Pytest:
pip install pytest - Test discovery — Pytest automatically finds files named
test_*.pywithout any config - Test functions must start with
test_— Pytest's naming contract - Running all tests:
pytest| Running one file:pytest test_login.py pytest -v— verbose output showing each test name and resultassertstatements — how Pytest decides pass or fail
@pytest.mark.smoke,@pytest.mark.regression— tag any test with a labelpytest -m smoke— run only the tagged tests, ignore everything elsepytest.ini— project settings file: register markers to keep the project warning-free[pytest]section withmarkers =list — one line per custom markerlib.py— shared utility functions file; keeps common logic out of test files- Class-based tests:
class Test_Login:with methods — clean grouping for related tests
@pytest.fixture— decorator that marks a function as reusable setup code- The fixture's return value is injected into the test as a parameter — no manual call needed
yieldin a fixture — code before yield runs as setup, code after runs as teardownautouse=True— apply this fixture to all tests automatically, without listing it
conftest.py— a special file Pytest always reads, no configuration required- Fixtures defined here are available to every test in the same directory and all subdirectories
- No import needed — Pytest injects them by parameter name matching (magic injection)
- This is where you put your browser setup fixture so every test file can use it
scope="function"(default) — fresh browser for every single test (safest, slowest)scope="class"— one browser shared across all tests in a classscope="module"— one browser shared across all tests in a filescope="session"— one browser for the entire test run (fastest, most efficient)- Subpackages:
__init__.pymakes a folder a Python package — organise large test suites
paramsin fixtures — pass a list of inputs, fixture runs once per item automatically@pytest.mark.parametrize("username,password", [("admin","pass"),("user","123")])- One test function → many test cases — runs for every row of data in the list
request.param— access the current data set inside a parametrized fixture
- The problem POM solves: one website change breaks dozens of test files — unmanageable
- One page class per page — locators and actions live together, tests call methods only
- Test files become readable:
login_page.enter_username("admin")not raw locators page/folder — a dedicated home for all page classes- Base page class — shared
find(),click(),type()methods inherited by all pages
pytest --html=report.html— generate a visual HTML report you can share with the team- Screenshot on failure —
driver.get_screenshot_as_file("failure.png")captured in conftest - Allure reporting — professional dashboard with pass/fail trends and step details
- Complete project structure:
conftest.py+page/+tests/+pytest.ini+reports/ - CI/CD integration — running your full framework automatically on every code push
Locator Strategy Questions
Be ready to explain which locator you choose and why. Interviewers want to hear: "I prefer By.ID when available, fall back to By.XPATH with contains() for dynamic elements." Know when CSS Selector beats XPath for speed.
Test Flakiness Questions
The most common interview topic. Know the difference between implicitly_wait (global, less precise) and WebDriverWait with expected conditions (per-element, precise). Explain when each one is appropriate and why mixing them causes problems.
Frame & Alert Questions
Explain that Selenium has one active context at a time. You must switch_to.frame() before finding elements inside it, and switch_to.default_content() to return. For alerts, the entire page is blocked until you accept() or dismiss().
Pytest & conftest Questions
Explain conftest.py as the shared fixture file that Pytest reads automatically. Describe fixture scopes with a real example: scope="session" opens the browser once for 200 tests vs scope="function" which opens it 200 times.
POM Architecture Questions
Explain POM in plain language: "Each page class holds all locators and actions for that page. Tests only call methods like login_page.submit(). When the UI changes, I update one page class instead of every test file." This answer impresses every interviewer.
Grid & Remote Questions
Describe Grid as the system that runs tests in parallel across machines. Your code changes by just one line: webdriver.Chrome() becomes Remote("http://grid-host:4444/wd/hub", options). Everything else stays the same.