<div class="selenium-notes">
<h1>PyTest Basics</h1>
<p><strong>PyTest</strong> is a popular Python testing framework used to write, organize, execute, and maintain automated tests. It provides a simple syntax for creating test cases and includes powerful features such as fixtures, assertions, parameterization, markers, test discovery, plugins, and detailed test reporting.</p>
<p>PyTest is widely used for unit testing, integration testing, API testing, web automation, and Selenium-based browser automation. Its simple structure allows testers and developers to create readable and maintainable automated test suites with less boilerplate code.</p>
<p>In Selenium automation, PyTest can be combined with Selenium WebDriver to automate browsers, validate web application functionality, execute multiple test scenarios, manage browser setup and cleanup, and generate test execution reports.</p>
<p><strong>Course Resource:</strong> <a href="https://www.justacademy.co/course-detail/selenium-training" target="_blank" rel="noopener noreferrer">Selenium Training</a> | <a href="https://www.justacademy.co/register-for-course-demo" target="_blank" rel="noopener noreferrer">Register for Course Demo</a></p>
<hr>
<h2>1. What is PyTest?</h2>
<p>PyTest is an open-source testing framework for Python. It is designed to make writing and running tests simple while also supporting advanced testing requirements.</p>
<p>PyTest can be used to test individual functions, classes, modules, APIs, databases, web applications, and complete application workflows.</p>
<p>One of the major advantages of PyTest is that a basic test can be written using a normal Python function and a simple <strong>assert</strong> statement.</p>
<pre><code>def test_addition():
result = 10 + 20
assert result == 30</code></pre>
<p>If the assertion is true, the test passes. If the assertion is false, PyTest reports the test as failed.</p>
<hr>
<h2>2. Why is PyTest Important?</h2>
<p>PyTest is important because it provides a flexible and scalable way to automate software testing using Python.</p>
<ul>
<li>Simple and readable test syntax.</li>
<li>Powerful assertion support.</li>
<li>Automatic test discovery.</li>
<li>Fixtures for setup and cleanup.</li>
<li>Parameterized testing.</li>
<li>Test grouping using markers.</li>
<li>Detailed failure information.</li>
<li>Large plugin ecosystem.</li>
<li>Easy integration with Selenium.</li>
<li>Easy integration with API testing tools.</li>
<li>Supports parallel test execution through plugins.</li>
<li>Can generate different types of test reports.</li>
<li>Works well with CI/CD systems.</li>
</ul>
<hr>
<h2>3. PyTest Installation</h2>
<p>PyTest can be installed using Python's package manager, <strong>pip</strong>.</p>
<pre><code>pip install pytest</code></pre>
<p>After installation, verify the PyTest version:</p>
<pre><code>pytest --version</code></pre>
<p>A successful installation displays the installed PyTest version and related information.</p>
<hr>
<h2>4. Checking Python Installation</h2>
<p>Before installing PyTest, Python should be installed on the system.</p>
<pre><code>python --version</code></pre>
<p>On some systems, the following command may be required:</p>
<pre><code>python3 --version</code></pre>
<p>The Python version can also be checked from an IDE terminal such as PyCharm or Visual Studio Code.</p>
<hr>
<h2>5. Creating a Basic PyTest Test</h2>
<p>A PyTest test can be created using a Python function whose name starts with <strong>test_</strong>.</p>
<pre><code>def test_login():
username = "admin"
password = "admin123"
assert username == "admin"
assert password == "admin123"</code></pre>
<p>The function can then be executed by PyTest.</p>
<hr>
<h2>6. PyTest Test Naming Convention</h2>
<p>PyTest uses naming conventions to automatically discover tests.</p>
<p>Common test file naming patterns include:</p>
<pre><code>test_login.py
test_search.py
test_checkout.py</code></pre>
<p>Test functions commonly follow this pattern:</p>
<pre><code>def test_login():
pass
def test_search():
pass
def test_checkout():
pass</code></pre>
<p>Following the standard naming convention makes automatic test discovery easier.</p>
<hr>
<h2>7. PyTest Test Discovery</h2>
<p><strong>Test discovery</strong> is the process by which PyTest automatically identifies test files, test functions, and test classes.</p>
<p>By default, PyTest commonly discovers files beginning with <strong>test_</strong> or ending with <strong>_test.py</strong>.</p>
<p>For example:</p>
<pre><code>project/
|-- test_login.py
|-- test_search.py
|-- test_checkout.py</code></pre>
<p>Running:</p>
<pre><code>pytest</code></pre>
<p>allows PyTest to search the current directory and execute discovered tests.</p>
<hr>
<h2>8. Running PyTest Tests</h2>
<p>The basic command for executing PyTest tests is:</p>
<pre><code>pytest</code></pre>
<p>To run a specific test file:</p>
<pre><code>pytest test_login.py</code></pre>
<p>To run a specific test function:</p>
<pre><code>pytest test_login.py::test_login</code></pre>
<p>To display more detailed output:</p>
<pre><code>pytest -v</code></pre>
<p>The <strong>-v</strong> option means verbose mode.</p>
<hr>
<h2>9. Basic PyTest Execution Flow</h2>
<pre><code>PyTest Command
|
v
Test Discovery
|
v
Collect Test Files
|
v
Collect Test Functions
|
v
Execute Tests
|
v
Assertions
|
v
Pass / Fail / Skip
|
v
Test Result
|
v
Report</code></pre>
<hr>
<h2>10. Assertions in PyTest</h2>
<p>Assertions are used to verify whether the actual result matches the expected result.</p>
<pre><code>def test_addition():
result = 5 + 5
assert result == 10</code></pre>
<p>If the condition is true, the test passes.</p>
<p>If the condition is false:</p>
<pre><code>def test_addition():
result = 5 + 5
assert result == 20</code></pre>
<p>PyTest marks the test as failed and provides information about the assertion failure.</p>
<hr>
<h2>11. Common Assertion Examples</h2>
<pre><code>def test_assertions():
assert 10 == 10
assert "Python" == "Python"
assert 10 > 5
assert 5 < 10
assert "PyTest" in "PyTest Automation"
assert True</code></pre>
<p>Assertions can be used to validate values, strings, collections, Boolean conditions, and application results.</p>
<hr>
<h2>12. Assertion Failure Example</h2>
<pre><code>def test_failure():
expected = "Login Successful"
actual = "Login Failed"
assert actual == expected</code></pre>
<p>The test fails because the actual value does not match the expected value.</p>
<hr>
<h2>13. PyTest Test Class</h2>
<p>Multiple related test methods can be organized inside a class.</p>
<pre><code>class TestLogin:
def test_valid_login(self):
assert True
def test_invalid_login(self):
assert True
def test_logout(self):
assert True</code></pre>
<p>PyTest can discover test classes whose names follow its test-class naming conventions, such as classes beginning with <strong>Test</strong>.</p>
<hr>
<h2>14. Test Class Example</h2>
<pre><code>class TestCalculator:
def test_addition(self):
assert 10 + 20 == 30
def test_subtraction(self):
assert 20 - 10 == 10
def test_multiplication(self):
assert 5 * 5 == 25
def test_division(self):
assert 20 / 5 == 4</code></pre>
<p>Grouping related tests into classes can improve test organization.</p>
<hr>
<h2>15. PyTest Fixtures</h2>
<p><strong>Fixtures</strong> are one of the most important features of PyTest. They provide reusable setup and preparation logic for tests.</p>
<p>Fixtures can be used for:</p>
<ul>
<li>Browser initialization.</li>
<li>Database connections.</li>
<li>API clients.</li>
<li>Test data preparation.</li>
<li>Login setup.</li>
<li>File preparation.</li>
<li>Environment configuration.</li>
<li>Cleanup activities.</li>
</ul>
<pre><code>import pytest
@pytest.fixture
def sample_data():
return 10
def test_value(sample_data):
assert sample_data == 10</code></pre>
<hr>
<h2>16. Fixture Execution Flow</h2>
<pre><code>Test Starts
|
v
Fixture Executes
|
v
Fixture Provides Data / Resource
|
v
Test Method Executes
|
v
Assertion
|
v
Test Completes
|
v
Fixture Cleanup</code></pre>
<hr>
<h2>17. Fixture with Selenium</h2>
<p>Fixtures are commonly used to create and close Selenium WebDriver instances.</p>
<pre><code>import pytest
from selenium import webdriver
@pytest.fixture
def driver():
driver = webdriver.Chrome()
driver.maximize_window()
yield driver
driver.quit()
def test_google_title(driver):
driver.get("https://www.google.com")
assert "Google" in driver.title</code></pre>
<p>Here, the fixture creates the browser before the test and closes the browser after the test.</p>
<hr>
<h2>18. Understanding yield in Fixtures</h2>
<p>The <strong>yield</strong> statement is commonly used in PyTest fixtures to separate setup and teardown logic.</p>
<pre><code>@pytest.fixture
def browser():
driver = webdriver.Chrome()
yield driver
driver.quit()</code></pre>
<p>The code before <strong>yield</strong> runs during setup. The code after <strong>yield</strong> runs during cleanup.</p>
<hr>
<h2>19. Fixture Scope</h2>
<p>PyTest fixtures support different scopes that control how frequently the fixture is created.</p>
<table border="1" cellpadding="8" cellspacing="0">
<tr><th>Scope</th><th>Meaning</th></tr>
<tr><td>function</td><td>Runs for each test function.</td></tr>
<tr><td>class</td><td>Runs once for each test class.</td></tr>
<tr><td>module</td><td>Runs once for each module.</td></tr>
<tr><td>package</td><td>Runs once for a package.</td></tr>
<tr><td>session</td><td>Runs once for the complete test session.</td></tr>
</table>
<hr>
<h2>20. Function Scope Fixture</h2>
<pre><code>@pytest.fixture(scope="function")
def browser():
driver = webdriver.Chrome()
yield driver
driver.quit()</code></pre>
<p>A function-scoped fixture is created separately for each test function that requests it.</p>
<hr>
<h2>21. Module Scope Fixture</h2>
<pre><code>@pytest.fixture(scope="module")
def browser():
driver = webdriver.Chrome()
yield driver
driver.quit()</code></pre>
<p>A module-scoped fixture can be shared by tests within the same Python module.</p>
<hr>
<h2>22. Session Scope Fixture</h2>
<pre><code>@pytest.fixture(scope="session")
def environment():
print("Initialize test environment")
yield
print("Cleanup test environment")</code></pre>
<p>A session-scoped fixture can be useful for resources that should be initialized once for the entire test session.</p>
<hr>
<h2>23. PyTest Parameterization</h2>
<p><strong>Parameterization</strong> allows the same test to execute with different input values.</p>
<pre><code>import pytest
@pytest.mark.parametrize(
"a,b,expected",
[
(10, 20, 30),
(5, 5, 10),
(100, 50, 150)
]
)
def test_addition(a, b, expected):
assert a + b == expected</code></pre>
<p>The same test executes once for every data set.</p>
<hr>
<h2>24. Parameterization Flow</h2>
<pre><code>Test Function
|
v
@pytest.mark.parametrize
|
+---- Data Set 1
|
+---- Data Set 2
|
+---- Data Set 3
|
v
Multiple Test Executions</code></pre>
<hr>
<h2>25. Parameterized Selenium Test</h2>
<p>Parameterization is very useful in Selenium automation.</p>
<pre><code>import pytest
from selenium import webdriver
@pytest.mark.parametrize(
"url",
[
"https://www.google.com",
"https://www.example.com"
]
)
def test_title(url):
driver = webdriver.Chrome()
driver.get(url)
assert driver.title != ""
driver.quit()</code></pre>
<hr>
<h2>26. PyTest Markers</h2>
<p><strong>Markers</strong> are used to categorize and selectively execute tests.</p>
<pre><code>import pytest
@pytest.mark.smoke
def test_login():
assert True
@pytest.mark.regression
def test_search():
assert True</code></pre>
<p>Markers make it easier to organize large test suites.</p>
<hr>
<h2>27. Registering Custom Markers</h2>
<p>Custom markers can be registered in the <strong>pytest.ini</strong> file.</p>
<pre><code>[pytest]
markers =
smoke: smoke test cases
regression: regression test cases
login: login related tests</code></pre>
<p>This provides a clear definition for custom markers used in the project.</p>
<hr>
<h2>28. Running Tests by Marker</h2>
<p>To run only smoke tests:</p>
<pre><code>pytest -m smoke</code></pre>
<p>To run regression tests:</p>
<pre><code>pytest -m regression</code></pre>
<p>To run login tests:</p>
<pre><code>pytest -m login</code></pre>
<hr>
<h2>29. Skipping Tests</h2>
<p>PyTest allows tests to be skipped when they should not execute under a particular condition.</p>
<pre><code>import pytest
@pytest.mark.skip(reason="Feature is not ready")
def test_new_feature():
assert True</code></pre>
<p>The test appears as skipped instead of failed.</p>
<hr>
<h2>30. Conditional Skipping</h2>
<p>A test can be skipped based on a condition.</p>
<pre><code>import pytest
import sys
@pytest.mark.skipif(
sys.platform == "win32",
reason="Not supported on Windows"
)
def test_linux_feature():
assert True</code></pre>
<p>This is useful when tests depend on a specific operating system, Python version, browser, or environment.</p>
<hr>
<h2>31. Expected Failures</h2>
<p>The <strong>xfail</strong> marker can be used for tests that are currently expected to fail.</p>
<pre><code>import pytest
@pytest.mark.xfail(reason="Known application defect")
def test_known_issue():
assert 1 == 2</code></pre>
<p>This allows known issues to be documented without treating them as unexpected failures.</p>
<hr>
<h2>32. PyTest Configuration File</h2>
<p>PyTest can be configured using files such as <strong>pytest.ini</strong>.</p>
<pre><code>[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v</code></pre>
<p>Configuration files help standardize test execution across the project.</p>
<hr>
<h2>33. PyTest Project Structure</h2>
<p>A basic PyTest automation project can be organized as follows:</p>
<pre><code>project/
|-- tests/
| |-- test_login.py
| |-- test_search.py
| |-- test_checkout.py
|
|-- pages/
| |-- login_page.py
| |-- search_page.py
| |-- checkout_page.py
|
|-- utilities/
| |-- driver_factory.py
| |-- config_reader.py
|
|-- conftest.py
|-- pytest.ini
|-- requirements.txt</code></pre>
<hr>
<h2>34. Understanding conftest.py</h2>
<p><strong>conftest.py</strong> is a special PyTest file commonly used to define shared fixtures and test configuration.</p>
<p>Fixtures defined in an appropriate conftest.py can be automatically discovered and used by test files without importing them manually.</p>
<pre><code>import pytest
from selenium import webdriver
@pytest.fixture
def driver():
driver = webdriver.Chrome()
yield driver
driver.quit()</code></pre>
<p>This fixture can then be used by tests in the relevant test directory.</p>
<hr>
<h2>35. Selenium Fixture Using conftest.py</h2>
<p>A common Selenium project design is to place browser setup inside <strong>conftest.py</strong>.</p>
<pre><code>import pytest
from selenium import webdriver
@pytest.fixture
def driver():
driver = webdriver.Chrome()
driver.maximize_window()
yield driver
driver.quit()</code></pre>
<p>Test file:</p>
<pre><code>def test_google(driver):
driver.get("https://www.google.com")
assert "Google" in driver.title</code></pre>
<hr>
<h2>36. PyTest with Selenium WebDriver</h2>
<p>PyTest and Selenium WebDriver can be combined to build browser automation frameworks.</p>
<pre><code>from selenium import webdriver
def test_google_search():
driver = webdriver.Chrome()
driver.get("https://www.google.com")
assert "Google" in driver.title
driver.quit()</code></pre>
<p>In production frameworks, browser creation and cleanup are usually moved into fixtures.</p>
<hr>
<h2>37. Selenium Login Test with PyTest</h2>
<pre><code>def test_login(driver):
driver.get("https://example.com/login")
driver.find_element("id", "username").send_keys("admin")
driver.find_element("id", "password").send_keys("admin123")
driver.find_element("id", "loginButton").click()
assert "Dashboard" in driver.title</code></pre>
<p>This example demonstrates how a PyTest test can control a Selenium browser and validate the result.</p>
<hr>
<h2>38. PyTest with Page Object Model</h2>
<p>The <strong>Page Object Model (POM)</strong> separates web-page interaction logic from test logic.</p>
<p>Example page object:</p>
<pre><code>from selenium.webdriver.common.by import By
class LoginPage:
def __init__(self, driver):
self.driver = driver
self.username = (By.ID, "username")
self.password = (By.ID, "password")
self.login_button = (By.ID, "loginButton")
def login(self, username, password):
self.driver.find_element(
*self.username
).send_keys(username)
self.driver.find_element(
*self.password
).send_keys(password)
self.driver.find_element(
*self.login_button
).click()</code></pre>
<hr>
<h2>39. PyTest POM Test Example</h2>
<pre><code>def test_login(driver):
driver.get("https://example.com/login")
login_page = LoginPage(driver)
login_page.login(
"admin",
"admin123"
)
assert "Dashboard" in driver.title</code></pre>
<p>This approach keeps test logic cleaner and makes page interactions reusable.</p>
<hr>
<h2>40. PyTest Test Dependencies</h2>
<p>PyTest itself has a relatively simple dependency model, but Selenium automation projects normally require additional packages.</p>
<p>Example <strong>requirements.txt</strong>:</p>
<pre><code>pytest
selenium</code></pre>
<p>Additional packages can be added based on project requirements.</p>
<hr>
<h2>41. Installing Selenium with PyTest</h2>
<pre><code>pip install selenium pytest</code></pre>
<p>Verify PyTest:</p>
<pre><code>pytest --version</code></pre>
<p>Verify Selenium:</p>
<pre><code>pip show selenium</code></pre>
<hr>
<h2>42. PyTest Verbose Mode</h2>
<p>Verbose mode provides more information about each test during execution.</p>
<pre><code>pytest -v</code></pre>
<p>Example conceptual output:</p>
<pre><code>test_login.py::test_valid_login PASSED
test_login.py::test_invalid_login PASSED
test_search.py::test_search PASSED</code></pre>
<hr>
<h2>43. Running Specific Test Files</h2>
<p>A specific test file can be executed directly.</p>
<pre><code>pytest tests/test_login.py</code></pre>
<p>Multiple files can also be supplied:</p>
<pre><code>pytest tests/test_login.py tests/test_search.py</code></pre>
<hr>
<h2>44. Running a Specific Test Function</h2>
<pre><code>pytest tests/test_login.py::test_valid_login</code></pre>
<p>This is useful when debugging a specific test instead of executing the complete test suite.</p>
<hr>
<h2>45. Running Tests by Keyword</h2>
<p>The <strong>-k</strong> option can be used to select tests based on their names.</p>
<pre><code>pytest -k login</code></pre>
<p>This can execute tests whose names match the keyword.</p>
<hr>
<h2>46. Stopping After the First Failure</h2>
<p>The <strong>-x</strong> option stops execution after the first failed test.</p>
<pre><code>pytest -x</code></pre>
<p>This can be useful while debugging a failing test suite.</p>
<hr>
<h2>47. Running a Limited Number of Failures</h2>
<p>PyTest also supports stopping after a specified number of failures.</p>
<pre><code>pytest --maxfail=2</code></pre>
<p>This stops execution after two test failures.</p>
<hr>
<h2>48. PyTest Output and Test Status</h2>
<p>PyTest commonly reports statuses such as:</p>
<table border="1" cellpadding="8" cellspacing="0">
<tr><th>Status</th><th>Meaning</th></tr>
<tr><td>PASSED</td><td>Test executed successfully.</td></tr>
<tr><td>FAILED</td><td>Assertion or test execution failed.</td></tr>
<tr><td>SKIPPED</td><td>Test was intentionally skipped.</td></tr>
<tr><td>XFAIL</td><td>Test was expected to fail.</td></tr>
<tr><td>XPASS</td><td>Expected-failure test unexpectedly passed.</td></tr>
<tr><td>ERROR</td><td>Setup, fixture, collection, or another execution error occurred.</td></tr>
</table>
<hr>
<h2>49. PyTest Test Reports</h2>
<p>PyTest can generate different types of reports using built-in options and plugins.</p>
<p>For example, a basic report can be generated in JUnit XML format:</p>
<pre><code>pytest --junitxml=report.xml</code></pre>
<p>HTML reports can also be generated using the appropriate PyTest reporting plugin.</p>
<pre><code>pip install pytest-html</code></pre>
<p>Then:</p>
<pre><code>pytest --html=report.html</code></pre>
<p>Reports are useful for reviewing test results and integrating automated testing into CI/CD pipelines.</p>
<hr>
<h2>50. PyTest HTML Reports</h2>
<p>The <strong>pytest-html</strong> plugin can generate an HTML report containing test execution information.</p>
<pre><code>pip install pytest-html</code></pre>
<p>Run tests with:</p>
<pre><code>pytest --html=report.html</code></pre>
<p>The generated HTML report can contain information about passed, failed, skipped, and other test outcomes.</p>
<hr>
<h2>51. PyTest JUnit XML Reports</h2>
<p>JUnit XML reports are commonly used by CI/CD and test management systems.</p>
<pre><code>pytest --junitxml=results.xml</code></pre>
<p>These reports can be consumed by various continuous integration systems and reporting tools.</p>
<hr>
<h2>52. PyTest Logging</h2>
<p>Logging can help identify what happened during test execution.</p>
<pre><code>import logging
def test_login():
logging.info("Starting login test")
result = True
assert result is True</code></pre>
<p>For larger frameworks, logging configuration can be centralized so that test execution information is stored consistently.</p>
<hr>
<h2>53. PyTest Setup and Teardown</h2>
<p>Setup logic prepares the environment before a test, while teardown logic cleans resources after execution.</p>
<p>Fixtures are the preferred mechanism for many setup and teardown requirements in PyTest.</p>
<pre><code>@pytest.fixture
def setup():
print("Setup")
yield
print("Teardown")</code></pre>
<hr>
<h2>54. Autouse Fixtures</h2>
<p>An <strong>autouse</strong> fixture executes automatically without needing to explicitly request the fixture in every test.</p>
<pre><code>@pytest.fixture(autouse=True)
def setup():
print("Setup before test")
yield
print("Cleanup after test")</code></pre>
<p>Autouse fixtures should be used carefully because they can affect many tests implicitly.</p>
<hr>
<h2>55. Fixture Dependency</h2>
<p>One fixture can use another fixture as a dependency.</p>
<pre><code>@pytest.fixture
def username():
return "admin"
@pytest.fixture
def login_data(username):
return {
"username": username,
"password": "admin123"
}
def test_login(login_data):
assert login_data["username"] == "admin"</code></pre>
<p>This makes complex test setup easier to organize.</p>
<hr>
<h2>56. PyTest Parameterization with Multiple Values</h2>
<pre><code>import pytest
@pytest.mark.parametrize(
"username,password",
[
("admin", "admin123"),
("manager", "manager123"),
("employee", "employee123")
]
)
def test_login(username, password):
assert username != ""
assert password != ""</code></pre>
<p>The same test executes for every username and password combination.</p>
<hr>
<h2>57. Parameterization with Expected Results</h2>
<pre><code>import pytest
@pytest.mark.parametrize(
"username,password,expected",
[
("admin", "admin123", True),
("invalid", "wrong", False),
("manager", "manager123", True)
]
)
def test_login_validation(username, password, expected):
result = username != "invalid"
assert result == expected</code></pre>
<p>Expected results can be stored along with the test inputs.</p>
<hr>
<h2>58. Parameterized Selenium Search Test</h2>
<pre><code>import pytest
@pytest.mark.parametrize(
"search_text",
[
"Laptop",
"Mobile",
"Headphones",
"Keyboard"
]
)
def test_search(driver, search_text):
driver.get("https://example.com")
search_box = driver.find_element(
"id",
"search"
)
search_box.send_keys(search_text)
assert search_text != ""</code></pre>
<hr>
<h2>59. PyTest Fixtures and Parameterization Together</h2>
<p>Fixtures and parameterization can be combined to build reusable Selenium tests.</p>
<pre><code>@pytest.fixture
def driver():
driver = webdriver.Chrome()
yield driver
driver.quit()
@pytest.mark.parametrize(
"search_text",
["Laptop", "Mobile", "Tablet"]
)
def test_search(driver, search_text):
driver.get("https://example.com")
driver.find_element(
"id",
"search"
).send_keys(search_text)</code></pre>
<hr>
<h2>60. PyTest with Multiple Browsers</h2>
<p>Browser parameterization can be used to run similar Selenium tests across different browsers.</p>
<pre><code>import pytest
from selenium import webdriver
@pytest.fixture
def driver(request):
browser = request.param
if browser == "chrome":
driver = webdriver.Chrome()
elif browser == "firefox":
driver = webdriver.Firefox()
else:
raise ValueError(
f"Unsupported browser: {browser}"
)
yield driver
driver.quit()
@pytest.mark.parametrize(
"driver",
["chrome", "firefox"],
indirect=True
)
def test_browser(driver):
driver.get("https://www.google.com")
assert driver.title != ""</code></pre>
<hr>
<h2>61. PyTest request Fixture</h2>
<p>The built-in <strong>request</strong> fixture provides information about the current test context and can be useful for advanced fixture designs.</p>
<pre><code>@pytest.fixture
def browser(request):
browser_name = request.param
print("Browser:", browser_name)</code></pre>
<p>This is especially useful when creating configurable browser fixtures.</p>
<hr>
<h2>62. PyTest Skip Based on Browser</h2>
<p>Tests can be conditionally skipped depending on the browser or environment.</p>
<pre><code>@pytest.mark.skip(reason="Feature unavailable in this browser")
def test_browser_specific_feature():
assert True</code></pre>
<p>Conditional logic can also be implemented through fixtures or configuration when browser-specific behavior is required.</p>
<hr>
<h2>63. PyTest Plugins</h2>
<p>PyTest has a large ecosystem of plugins that extend its functionality.</p>
<p>Common examples include:</p>
<ul>
<li><strong>pytest-html:</strong> HTML test reports.</li>
<li><strong>pytest-xdist:</strong> Parallel test execution.</li>
<li><strong>pytest-cov:</strong> Code coverage reporting.</li>
<li><strong>pytest-mock:</strong> Mocking support.</li>
<li><strong>pytest-json-report:</strong> JSON-based reporting.</li>
</ul>
<p>Plugins allow teams to customize PyTest for different automation requirements.</p>
<hr>
<h2>64. PyTest Parallel Execution</h2>
<p>Parallel execution can reduce overall test execution time. The <strong>pytest-xdist</strong> plugin is commonly used for parallel execution.</p>
<pre><code>pip install pytest-xdist</code></pre>
<p>Run tests using multiple workers:</p>
<pre><code>pytest -n 4</code></pre>
<p>This requests four workers for parallel test execution.</p>
<p>When using Selenium, each parallel test should have an isolated browser session and thread-safe framework resources.</p>
<hr>
<h2>65. PyTest and Selenium Thread Safety</h2>
<p>When Selenium tests run in parallel, WebDriver instances should generally not be shared between independent concurrent tests.</p>
<pre><code>Test 1 ----> WebDriver 1 ----> Browser 1
Test 2 ----> WebDriver 2 ----> Browser 2
Test 3 ----> WebDriver 3 ----> Browser 3</code></pre>
<p>Using separate browser sessions helps prevent one test from interfering with another.</p>
<hr>
<h2>66. PyTest and API Testing</h2>
<p>Although PyTest is commonly associated with unit and Selenium testing, it can also be used for API automation.</p>
<pre><code>import requests
def test_get_users():
response = requests.get(
"https://api.example.com/users"
)
assert response.status_code == 200</code></pre>
<p>The same PyTest concepts such as fixtures, parameterization, markers, and assertions can be applied to API tests.</p>
<hr>
<h2>67. PyTest for Data-Driven API Testing</h2>
<pre><code>import pytest
import requests
@pytest.mark.parametrize(
"user_id",
[1, 2, 3]
)
def test_user_api(user_id):
response = requests.get(
f"https://api.example.com/users/{user_id}"
)
assert response.status_code == 200</code></pre>
<p>This demonstrates how one test can validate multiple API inputs.</p>
<hr>
<h2>68. PyTest Environment Configuration</h2>
<p>Test frameworks often need different environments such as QA, staging, and production-like environments.</p>
<pre><code>def test_environment():
environment = "QA"
base_url = "https://qa.example.com"
assert environment == "QA"
assert base_url.startswith("https://")</code></pre>
<p>In production frameworks, environment values are generally maintained through configuration rather than hard-coded throughout test files.</p>
<hr>
<h2>69. PyTest Configuration Using Environment Variables</h2>
<p>Environment variables can be used to provide configuration values without modifying test code.</p>
<pre><code>import os
def test_environment():
base_url = os.getenv(
"BASE_URL",
"https://example.com"
)
assert base_url.startswith("https://")</code></pre>
<p>This approach is useful in CI/CD environments where configuration changes between environments.</p>
<hr>
<h2>70. PyTest with Page Objects and Fixtures</h2>
<p>A scalable Selenium framework can combine fixtures, Page Object Model, parameterization, and assertions.</p>
<pre><code>@pytest.fixture
def login_page(driver):
return LoginPage(driver)
def test_login(login_page):
login_page.login(
"admin",
"admin123"
)
assert "Dashboard" in login_page.driver.title</code></pre>
<p>This structure keeps browser management, page interactions, and test validation separated.</p>
<hr>
<h2>71. PyTest Test Organization</h2>
<p>Large automation projects should organize tests logically.</p>
<pre><code>tests/
|-- smoke/
| |-- test_login.py
| |-- test_home.py
|
|-- regression/
| |-- test_search.py
| |-- test_checkout.py
|
|-- api/
| |-- test_users.py
| |-- test_products.py</code></pre>
<p>Markers and directory structure can be combined to improve test suite organization.</p>
<hr>
<h2>72. PyTest Smoke Testing</h2>
<p>Smoke tests validate critical application functionality quickly after a new build or deployment.</p>
<pre><code>import pytest
@pytest.mark.smoke
def test_login():
assert True
@pytest.mark.smoke
def test_home_page():
assert True</code></pre>
<p>Smoke tests can be executed using:</p>
<pre><code>pytest -m smoke</code></pre>
<hr>
<h2>73. PyTest Regression Testing</h2>
<p>Regression tests validate existing functionality after application changes.</p>
<pre><code>import pytest
@pytest.mark.regression
def test_search():
assert True
@pytest.mark.regression
def test_checkout():
assert True</code></pre>
<p>Regression tests can be executed using:</p>
<pre><code>pytest -m regression</code></pre>
<hr>
<h2>74. PyTest Common Mistakes</h2>
<ul>
<li>Incorrect test file naming.</li>
<li>Incorrect test function naming.</li>
<li>Forgetting to install PyTest.</li>
<li>Using incorrect fixture names.</li>
<li>Not closing Selenium browsers.</li>
<li>Sharing WebDriver instances unsafely between parallel tests.</li>
<li>Using excessive hard-coded test data.</li>
<li>Not using assertions properly.</li>
<li>Creating overly complicated fixtures.</li>
<li>Using too many implicit dependencies between tests.</li>
<li>Not organizing tests into logical modules.</li>
<li>Not configuring markers correctly.</li>
<li>Ignoring test failures in CI/CD.</li>
<li>Storing sensitive credentials directly in source code.</li>
</ul>
<hr>
<h2>75. Common Selenium + PyTest Mistakes</h2>
<table border="1" cellpadding="8" cellspacing="0">
<tr><th>Mistake</th><th>Better Approach</th></tr>
<tr><td>Creating browser in every test manually</td><td>Use reusable fixtures.</td></tr>
<tr><td>Forgetting driver.quit()</td><td>Use fixture teardown.</td></tr>
<tr><td>Sharing one driver in parallel tests</td><td>Use isolated WebDriver instances.</td></tr>
<tr><td>Hard-coding URLs everywhere</td><td>Use configuration.</td></tr>
<tr><td>Putting locators directly in every test</td><td>Use Page Object Model.</td></tr>
<tr><td>Duplicating test data</td><td>Use parameterization or external data.</td></tr>
</table>
<hr>
<h2>76. Best Practices for PyTest</h2>
<ul>
<li>Follow standard PyTest naming conventions.</li>
<li>Keep test cases small and focused.</li>
<li>Use meaningful test names.</li>
<li>Use fixtures for reusable setup and cleanup.</li>
<li>Use parameterization for data-driven tests.</li>
<li>Use markers to categorize tests.</li>
<li>Keep Selenium page interactions inside Page Objects.</li>
<li>Keep configuration separate from test logic.</li>
<li>Generate useful test reports.</li>
<li>Use logging for debugging.</li>
<li>Avoid unnecessary test dependencies.</li>
<li>Use isolated browser sessions for parallel execution.</li>
<li>Protect passwords, tokens, and other secrets.</li>
<li>Integrate automated tests into CI/CD pipelines.</li>
</ul>
<hr>
<h2>77. PyTest vs unittest</h2>
<table border="1" cellpadding="8" cellspacing="0">
<tr><th>Feature</th><th>PyTest</th><th>unittest</th></tr>
<tr><td>Syntax</td><td>Simple</td><td>More structured</td></tr>
<tr><td>Assertions</td><td>Python assert</td><td>unittest assertion methods</td></tr>
<tr><td>Fixtures</td><td>Powerful fixture system</td><td>setUp/tearDown mechanisms</td></tr>
<tr><td>Parameterization</td><td>Built-in through markers</td><td>Requires additional approaches</td></tr>
<tr><td>Plugins</td><td>Large ecosystem</td><td>Smaller ecosystem</td></tr>
<tr><td>Selenium Integration</td><td>Commonly used</td><td>Supported</td></tr>
</table>
<hr>
<h2>78. PyTest vs TestNG</h2>
<table border="1" cellpadding="8" cellspacing="0">
<tr><th>Feature</th><th>PyTest</th><th>TestNG</th></tr>
<tr><td>Language</td><td>Python</td><td>Java</td></tr>
<tr><td>Basic Assertion</td><td>assert</td><td>Assert methods</td></tr>
<tr><td>Fixtures/Setup</td><td>pytest.fixture</td><td>Configuration annotations</td></tr>
<tr><td>Parameterization</td><td>parametrize</td><td>DataProvider / Parameters</td></tr>
<tr><td>Markers</td><td>Yes</td><td>Groups and other annotations</td></tr>
<tr><td>Web Automation</td><td>Selenium Python</td><td>Selenium Java</td></tr>
<tr><td>Reports</td><td>Plugins and formats</td><td>Built-in/plugin-based reporting options</td></tr>
</table>
<hr>
<h2>79. PyTest with CI/CD</h2>
<p>PyTest can be integrated into continuous integration and continuous delivery pipelines.</p>
<pre><code>Developer Commit
|
v
CI/CD Pipeline
|
v
Install Dependencies
|
v
Run PyTest
|
v
Selenium / API Tests
|
v
Generate Reports
|
v
Build Result</code></pre>
<p>This allows automated tests to execute whenever new application code is committed or deployed.</p>
<hr>
<h2>80. PyTest with Jenkins</h2>
<p>Jenkins can execute PyTest commands as part of an automated pipeline.</p>
<pre><code>pytest -v
pytest --html=report.html
pytest --junitxml=results.xml</code></pre>
<p>Test reports can then be archived or processed by the CI system according to the project's reporting configuration.</p>
<hr>
<h2>81. PyTest Automation Framework Architecture</h2>
<pre><code> Test Data
|
v
Parameterization
|
v
PyTest Test Case
|
+----------+----------+
| |
v v
Fixtures Page Objects
| |
+----------+----------+
|
v
Selenium WebDriver
|
v
Web Browser
|
v
Application
|
v
Assertions
|
v
Reports</code></pre>
<hr>
<h2>82. Complete Practical Selenium PyTest Example</h2>
<p>The following example demonstrates a simple reusable Selenium PyTest framework using a fixture, Selenium WebDriver, and assertions.</p>
<pre><code>import pytest
from selenium import webdriver
@pytest.fixture
def driver():
driver = webdriver.Chrome()
driver.maximize_window()
yield driver
driver.quit()
def test_google_title(driver):
driver.get("https://www.google.com")
assert "Google" in driver.title
def test_google_url(driver):
driver.get("https://www.google.com")
assert "google" in driver.current_url.lower()</code></pre>
<hr>
<h2>83. Practical Login Automation Example</h2>
<pre><code>import pytest
from selenium import webdriver
@pytest.fixture
def driver():
driver = webdriver.Chrome()
driver.maximize_window()
yield driver
driver.quit()
@pytest.mark.parametrize(
"username,password",
[
("admin", "admin123"),
("manager", "manager123"),
("employee", "employee123")
]
)
def test_login(driver, username, password):
driver.get("https://example.com/login")
driver.find_element(
"id",
"username"
).send_keys(username)
driver.find_element(
"id",
"password"
).send_keys(password)
driver.find_element(
"id",
"loginButton"
).click()
assert "Dashboard" in driver.title</code></pre>
<p>This example demonstrates how fixtures and parameterization can reduce duplicate Selenium test code.</p>
<hr>
<h2>84. Practical Search Testing Example</h2>
<pre><code>import pytest
@pytest.mark.parametrize(
"keyword",
[
"Laptop",
"Mobile",
"Tablet",
"Headphones",
"Keyboard"
]
)
def test_product_search(driver, keyword):
driver.get("https://example.com")
search = driver.find_element(
"id",
"search"
)
search.clear()
search.send_keys(keyword)
driver.find_element(
"id",
"searchButton"
).click()
assert keyword != ""</code></pre>
<hr>
<h2>85. Practical Project Structure</h2>
<pre><code>selenium_pytest_project/
|-- tests/
| |-- test_login.py
| |-- test_search.py
| |-- test_checkout.py
|
|-- pages/
| |-- login_page.py
| |-- search_page.py
| |-- checkout_page.py
|
|-- utilities/
| |-- config_reader.py
| |-- driver_factory.py
|
|-- test_data/
| |-- login_data.json
| |-- search_data.json
|
|-- reports/
| |-- report.html
|
|-- conftest.py
|-- pytest.ini
|-- requirements.txt</code></pre>
<hr>
<h2>86. Recommended PyTest Framework Flow</h2>
<pre><code>Test Request
|
v
Test Data
|
v
PyTest Parameterization
|
v
Fixture
|
v
WebDriver
|
v
Page Object
|
v
Application
|
v
Assertion
|
v
Test Result
|
v
Report</code></pre>
<hr>
<h2>87. Interview Questions on PyTest Basics</h2>
<h3>1. What is PyTest?</h3>
<p>PyTest is a Python testing framework used to write and execute automated tests.</p>
<h3>2. How do you install PyTest?</h3>
<p>PyTest can be installed using <strong>pip install pytest</strong>.</p>
<h3>3. How do you run PyTest?</h3>
<p>The basic command is <strong>pytest</strong>.</p>
<h3>4. What is a PyTest fixture?</h3>
<p>A fixture provides reusable setup, test resources, data, and cleanup functionality.</p>
<h3>5. What is conftest.py?</h3>
<p>conftest.py is a special PyTest configuration file commonly used to define reusable fixtures and shared test configuration.</p>
<h3>6. What is @pytest.mark.parametrize?</h3>
<p>It allows the same test to execute with multiple sets of input data.</p>
<h3>7. What are PyTest markers?</h3>
<p>Markers categorize tests and allow specific groups of tests to be executed selectively.</p>
<h3>8. How do you skip a test?</h3>
<p>The <strong>@pytest.mark.skip</strong> marker can be used to skip a test.</p>
<h3>9. What is xfail?</h3>
<p>xfail marks a test as expected to fail because of a known or expected condition.</p>
<h3>10. How do you run a specific test?</h3>
<p>You can use a command such as <strong>pytest test_login.py::test_login</strong>.</p>
<h3>11. How do you run tests in verbose mode?</h3>
<p>Use <strong>pytest -v</strong>.</p>
<h3>12. How can PyTest be used with Selenium?</h3>
<p>Selenium WebDriver can be initialized through PyTest fixtures and used inside test functions to automate browsers.</p>
<h3>13. Why are fixtures useful in Selenium automation?</h3>
<p>Fixtures provide reusable browser setup and cleanup logic and help prevent duplicate WebDriver code.</p>
<h3>14. What is parameterization?</h3>
<p>Parameterization allows the same test logic to execute using different input values.</p>
<h3>15. How can PyTest generate HTML reports?</h3>
<p>The pytest-html plugin can be installed and used to generate HTML reports.</p>
<h3>16. How can PyTest generate JUnit reports?</h3>
<p>PyTest supports JUnit XML output using the <strong>--junitxml</strong> option.</p>
<h3>17. What is pytest.ini?</h3>
<p>pytest.ini is a configuration file that can define test paths, naming patterns, markers, and default options.</p>
<h3>18. How can PyTest tests run in parallel?</h3>
<p>The pytest-xdist plugin can be used for parallel execution.</p>
<h3>19. What is the difference between fixture scope and test parameterization?</h3>
<p>Fixture scope controls how often a fixture resource is created, while parameterization controls how many different data combinations a test receives.</p>
<h3>20. What are important PyTest best practices?</h3>
<p>Use reusable fixtures, clear test names, parameterization, Page Object Model for Selenium, meaningful assertions, organized test suites, secure configuration, and useful reporting.</p>
<hr>
<h2>88. Quick Reference Table</h2>
<table border="1" cellpadding="8" cellspacing="0">
<tr><th>Concept</th><th>Purpose</th></tr>
<tr><td>pytest</td><td>Runs the PyTest test suite.</td></tr>
<tr><td>assert</td><td>Validates expected conditions.</td></tr>
<tr><td>@pytest.fixture</td><td>Creates reusable setup and cleanup logic.</td></tr>
<tr><td>conftest.py</td><td>Stores shared fixtures and configuration.</td></tr>
<tr><td>@pytest.mark.parametrize</td><td>Runs tests with multiple data sets.</td></tr>
<tr><td>@pytest.mark.smoke</td><td>Categorizes smoke tests.</td></tr>
<tr><td>@pytest.mark.regression</td><td>Categorizes regression tests.</td></tr>
<tr><td>@pytest.mark.skip</td><td>Skips a test.</td></tr>
<tr><td>@pytest.mark.xfail</td><td>Marks an expected failure.</td></tr>
<tr><td>pytest -v</td><td>Runs tests in verbose mode.</td></tr>
<tr><td>pytest -k</td><td>Selects tests using keywords.</td></tr>
<tr><td>pytest -x</td><td>Stops after the first failure.</td></tr>
<tr><td>--maxfail</td><td>Limits the number of failures before stopping.</td></tr>
<tr><td>--html</td><td>Generates an HTML report with pytest-html.</td></tr>
<tr><td>--junitxml</td><td>Generates JUnit XML output.</td></tr>
<tr><td>pytest-xdist</td><td>Provides parallel test execution.</td></tr>
</table>
<hr>
<h2>89. Learning Roadmap for PyTest</h2>
<ol>
<li>Learn Python fundamentals.</li>
<li>Understand basic software testing concepts.</li>
<li>Install and configure PyTest.</li>
<li>Create basic test functions.</li>
<li>Learn assertions.</li>
<li>Understand test discovery.</li>
<li>Learn test classes.</li>
<li>Learn PyTest fixtures.</li>
<li>Understand fixture scopes.</li>
<li>Learn conftest.py.</li>
<li>Learn parameterization.</li>
<li>Learn markers.</li>
<li>Learn skip and xfail.</li>
<li>Learn PyTest configuration.</li>
<li>Integrate PyTest with Selenium.</li>
<li>Learn Page Object Model.</li>
<li>Build reusable Selenium fixtures.</li>
<li>Learn reporting.</li>
<li>Learn parallel execution.</li>
<li>Integrate PyTest with CI/CD.</li>
<li>Build a complete Selenium PyTest framework.</li>
</ol>
<hr>
<h2>90. Practical Exercises</h2>
<ol>
<li>Create a simple addition test using PyTest.</li>
<li>Create a calculator test with multiple assertions.</li>
<li>Create a login test using a Selenium fixture.</li>
<li>Create a parameterized login test.</li>
<li>Create a parameterized search test.</li>
<li>Create smoke and regression markers.</li>
<li>Create a reusable browser fixture in conftest.py.</li>
<li>Create a Page Object Model framework.</li>
<li>Generate an HTML test report.</li>
<li>Generate a JUnit XML report.</li>
<li>Run selected tests using markers.</li>
<li>Execute tests in parallel using pytest-xdist.</li>
<li>Integrate the test suite with a CI/CD pipeline.</li>
</ol>
<hr>
<h2>91. Real-World Selenium PyTest Architecture</h2>
<pre><code> Configuration
|
v
conftest.py
|
+-----------+-----------+
| |
v v
Fixtures Test Data
| |
+-----------+-----------+
|
v
PyTest Tests
|
v
Page Object Model
|
v
Selenium WebDriver
|
v
Browser
|
v
Web Application
|
v
Assertions
|
v
Reports
|
v
CI/CD</code></pre>
<hr>
<h2>92. Summary</h2>
<p>PyTest is a powerful and flexible Python testing framework used for creating automated tests. Its simple syntax makes it easy to write basic tests, while features such as fixtures, parameterization, markers, configuration, plugins, and reporting support large automation frameworks.</p>
<p>For Selenium automation, PyTest can manage browser setup and cleanup through fixtures, execute tests with multiple data sets using parameterization, organize tests using markers, and separate application interaction logic through the Page Object Model.</p>
<p>PyTest can also be integrated with HTML and JUnit reporting, parallel execution, API testing, CI/CD systems, and other automation tools.</p>
<p>The combination of <strong>PyTest + Selenium + Fixtures + Parameterization + Page Object Model + Reporting</strong> provides a strong foundation for building maintainable Python-based web automation frameworks.</p>
<hr>
<h2>93. Course Resources</h2>
<p>Learn more about Selenium automation and related testing concepts:</p>
<ul>
<li><a href="https://www.justacademy.co/course-detail/selenium-training" target="_blank" rel="noopener noreferrer">Selenium Training</a></li>
<li><a href="https://www.justacademy.co/register-for-course-demo" target="_blank" rel="noopener noreferrer">Register for Selenium Course Demo</a></li>
</ul>
<p><strong>Final Takeaway:</strong> PyTest provides a simple yet powerful foundation for Python automation testing. When combined with Selenium WebDriver, fixtures, parameterization, Page Object Model, markers, reporting, and CI/CD, it can be used to build structured, reusable, scalable, and maintainable web automation frameworks.</p>
</div>