├── .gitignore ├── Workflows └── example_workflows.py ├── conftest.py ├── pages ├── ican_navigation.py └── main_page.py ├── pytest.ini ├── readme.md ├── requirements.txt └── tests ├── test_with_bdd.py ├── test_with_bdd_and_using_workflows.py ├── tests.feature └── without_bdd ├── test_without_bdd.py └── test_without_bdd_workflow.py /.gitignore: -------------------------------------------------------------------------------- 1 | /venv/ 2 | /.playwright-screenshots/ 3 | /tests/report.html 4 | -------------------------------------------------------------------------------- /Workflows/example_workflows.py: -------------------------------------------------------------------------------- 1 | from pages.main_page import MainPage 2 | from pages.ican_navigation import IcanNavigationMenu 3 | 4 | 5 | def open_example_site_verify_page_load(page): 6 | main_page = MainPage(page) 7 | page.goto("https://example.com") 8 | main_page.verify_header_text() 9 | 10 | def navigate_to_more_information_page_verify_menus(page): 11 | main_page = MainPage(page) 12 | ican_navigation = IcanNavigationMenu(page) 13 | main_page.select_more_information_link() 14 | ican_navigation.verify_menu_links() 15 | 16 | def launch_and_navigate_to_ican_page(ican_navigation, page): 17 | open_example_site_verify_page_load(page) 18 | navigate_to_more_information_page_verify_menus(page) 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pathlib import Path 4 | 5 | def pytest_runtest_makereport(item, call) -> None: 6 | if call.when == "call": 7 | if call.excinfo is not None and "page" in item.funcargs: 8 | page = item.funcargs["page"] 9 | screenshot_dir = Path(".playwright-screenshots") 10 | screenshot_dir.mkdir(exist_ok=True) 11 | page.screenshot(path=str(screenshot_dir / f"screenshot.png")) 12 | 13 | def pytest_addoption(parser): 14 | parser.addoption('--menu-item', default='domains') 15 | parser.addoption('--expected-url', default='https://www.iana.org/domains') 16 | 17 | 18 | @pytest.fixture 19 | def menu_item(request): 20 | return request.config.getoption('--menu-item') 21 | 22 | 23 | @pytest.fixture 24 | def expected_url(request): 25 | return request.config.getoption('--expected-url') -------------------------------------------------------------------------------- /pages/ican_navigation.py: -------------------------------------------------------------------------------- 1 | from playwright.sync_api import Page 2 | 3 | 4 | class IcanNavigationMenu: 5 | DOMAIN_LINK = 'text=Domains' 6 | NUMBERS_LINK = 'text=Numbers' 7 | PROTOCOLS_LINK = 'text=Protocols' 8 | ABOUT_US_LINK = 'text=About Us' 9 | 10 | def __init__(self, page: Page): 11 | self.page = page 12 | 13 | def verify_menu_links(self): 14 | self.page.wait_for_selector(self.DOMAIN_LINK) 15 | self.page.wait_for_selector(self.NUMBERS_LINK) 16 | self.page.wait_for_selector(self.PROTOCOLS_LINK) 17 | self.page.wait_for_selector(self.ABOUT_US_LINK) 18 | 19 | def click_domain_link(self): 20 | with self.page.expect_navigation(): 21 | self.page.click(self.DOMAIN_LINK) 22 | 23 | def click_numbers_link(self): 24 | with self.page.expect_navigation(): 25 | self.page.click(self.NUMBERS_LINK) 26 | 27 | def click_protocols_link(self): 28 | with self.page.expect_navigation(): 29 | self.page.click(self.PROTOCOLS_LINK) 30 | 31 | def click_about_us_link(self): 32 | with self.page.expect_navigation(): 33 | self.page.click(self.ABOUT_US_LINK) 34 | 35 | -------------------------------------------------------------------------------- /pages/main_page.py: -------------------------------------------------------------------------------- 1 | from playwright.sync_api import Page 2 | 3 | 4 | class MainPage: 5 | HEADER: str = 'h1' 6 | MORE_INFO_LINK: str = 'text=More information' 7 | 8 | def __init__(self, page: Page): 9 | self.page = page 10 | 11 | def verify_header_text(self): 12 | assert self.page.inner_text(self.HEADER) == 'Example Domain' 13 | 14 | def select_more_information_link(self): 15 | with self.page.expect_navigation(): 16 | self.page.click(self.MORE_INFO_LINK) 17 | 18 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | # pytest.ini 2 | [pytest] 3 | minversion = 6.0 4 | addopts = -ra -q --headed 5 | testpaths = 6 | tests -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Playwright Python pytest_bdd example 2 | - Depends on [pytest-playwright](https://github.com/microsoft/playwright-pytest) 3 | - pip install pytest-playwright 4 | - pip install pytest-bdd 5 | - playwright install 6 | 7 | ## Command Line Example 8 | Use "pytest" to run all the tests 9 | 10 | Other examples 11 | Use "pytest tests/test_with_bdd.py" to run all the tests in the test_without_bdd module 12 | Use "pytest tests/without_bdd/test_without_bdd.py::test_select_numbers_link" to run the test_select_numbers_link test 13 | 14 | ## Playwright Documentation 15 | 16 | [https://playwright.dev/python/docs/intro](https://playwright.dev/python/docs/intro) 17 | 18 | ## pytest-bdd Documentation 19 | 20 | See [https://pypi.org/project/pytest-bdd/](https://pypi.org/project/pytest-bdd/) for reporting options. 21 | 22 | 23 | 24 | ## Page Object example 25 | This test is using a standard page object model, where the selectors 26 | and functions are group inside a class. 27 | 28 | Example is given with and without the bdd layer 29 | 30 | Alternative format that uses files instead of objects to group the pages can be found 31 | [here](https://github.com/cmoir/playwright-pytest-pagefile-example) 32 | 33 | ## Recommended plugins: 34 | pytest-xdist - used to run multiple tests at the same time 35 | pytest-html - for nice simple html report 36 | 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | playwright>=1.25.1 2 | pytest>=7.1.2 3 | pytest-playwright>=0.3.0 4 | pytest-bdd>=6.0.1 -------------------------------------------------------------------------------- /tests/test_with_bdd.py: -------------------------------------------------------------------------------- 1 | from pages.main_page import MainPage 2 | from pages.ican_navigation import IcanNavigationMenu 3 | from pytest_bdd import scenario, given, when, then 4 | 5 | 6 | @scenario('tests.feature', 'user can navigate to the ican page') 7 | def test_user_can_navigate_to_each_link(): 8 | print('starting bdd test') 9 | 10 | @scenario('tests.feature', 'user can navigate to the domains page') 11 | def test_navigate_to_domains_page(): 12 | pass 13 | 14 | 15 | @given("an example site") 16 | def goto_website(page): 17 | main_page = MainPage(page) 18 | page.goto("https://example.com") 19 | main_page.verify_header_text() 20 | 21 | 22 | @given('the user is on the Domains page') 23 | def goto_domains(page): 24 | main_page = MainPage(page) 25 | page.goto("https://example.com") 26 | main_page.verify_header_text() 27 | main_page.select_more_information_link() 28 | ican_navigation = IcanNavigationMenu(page) 29 | ican_navigation.verify_menu_links() 30 | ican_navigation.click_domain_link() 31 | 32 | 33 | @when('they click on the "More information" link') 34 | def navigate_to_ican(page): 35 | main_page = MainPage(page) 36 | main_page.select_more_information_link() 37 | ican_navigation = IcanNavigationMenu(page) 38 | ican_navigation.verify_menu_links() 39 | 40 | 41 | @then('they are able to navigate to the Domains page') 42 | def verify_domains_page(page): 43 | assert page.url == 'https://www.iana.org/domains/reserved', f"Expected URL https://www.iana.org/domains/reserved, but got {page.url}" 44 | 45 | 46 | @then('they are able to navigate to the expected web page') 47 | def check_ican_nav_menu(page, menu_item, expected_url): 48 | ican_navigation = IcanNavigationMenu(page) 49 | if menu_item == 'domains': 50 | ican_navigation.click_domain_link() 51 | elif menu_item == 'numbers': 52 | ican_navigation.click_numbers_link() 53 | elif menu_item == 'protocols': 54 | ican_navigation.click_protocols_link() 55 | elif menu_item == 'about us': 56 | ican_navigation.click_about_us_link() 57 | else: 58 | raise ValueError(f"Invalid menu item: {menu_item}") 59 | assert page.url == expected_url, f"Expected URL {expected_url}, but got {page.url}" -------------------------------------------------------------------------------- /tests/test_with_bdd_and_using_workflows.py: -------------------------------------------------------------------------------- 1 | from Workflows import example_workflows 2 | from pages.ican_navigation import IcanNavigationMenu 3 | from pytest_bdd import scenario, given, when, then 4 | 5 | 6 | @scenario('tests.feature', 'user can navigate to the ican page') 7 | def test_user_can_navigate_to_each_link(): 8 | print('starting bdd test') 9 | 10 | 11 | @given("an example site") 12 | def goto_website(page): 13 | example_workflows.open_example_site_verify_page_load(page) 14 | 15 | 16 | @when('they click on the "More information" link') 17 | def navigate_to_ican(page): 18 | example_workflows.navigate_to_more_information_page_verify_menus(page) 19 | 20 | 21 | @then('they are able to navigate to the expected web page') 22 | def check_ican_nav_menu(page, menu_item, expected_url): 23 | ican_navigation = IcanNavigationMenu(page) 24 | if menu_item == 'domains': 25 | ican_navigation.click_domain_link() 26 | elif menu_item == 'numbers': 27 | ican_navigation.click_numbers_link() 28 | elif menu_item == 'protocols': 29 | ican_navigation.click_protocols_link() 30 | elif menu_item == 'about us': 31 | ican_navigation.click_about_us_link() 32 | else: 33 | raise ValueError(f"Invalid menu item: {menu_item}") 34 | assert page.url == expected_url, f"Expected URL {expected_url}, but got {page.url}" 35 | -------------------------------------------------------------------------------- /tests/tests.feature: -------------------------------------------------------------------------------- 1 | # Updated feature file 2 | 3 | Feature: Example BDD tests 4 | 5 | Scenario: user can navigate to the domains page 6 | Given an example site 7 | When they click on the "More information" link 8 | Then they are able to navigate to the Domains page 9 | 10 | Scenario: user can navigate to the ican page 11 | Given an example site 12 | When they click on the "More information" link 13 | Then they are able to navigate to the expected web page 14 | 15 | Examples: 16 | | menu_item | expected_url | 17 | | domains | https://www.iana.org/domains | 18 | | numbers | https://www.iana.org/numbers | 19 | | protocols | https://www.iana.org/protocols | 20 | | about us | https://www.iana.org/about | 21 | -------------------------------------------------------------------------------- /tests/without_bdd/test_without_bdd.py: -------------------------------------------------------------------------------- 1 | from pages.main_page import MainPage 2 | from pages.ican_navigation import IcanNavigationMenu 3 | 4 | def test_select_domains_link(page): 5 | main_page = MainPage(page) 6 | ican_navigation = IcanNavigationMenu(page) 7 | 8 | page.goto("https://example.com") 9 | 10 | main_page.verify_header_text() 11 | main_page.select_more_information_link() 12 | ican_navigation.verify_menu_links() 13 | ican_navigation.click_domain_link() 14 | 15 | def test_select_numbers_link(page): 16 | main_page = MainPage(page) 17 | ican_navigation = IcanNavigationMenu(page) 18 | 19 | page.goto("https://example.com") 20 | 21 | main_page.verify_header_text() 22 | main_page.select_more_information_link() 23 | ican_navigation.verify_menu_links() 24 | ican_navigation.click_numbers_link() 25 | 26 | def test_select_protocols_link(page): 27 | main_page = MainPage(page) 28 | ican_navigation = IcanNavigationMenu(page) 29 | 30 | page.goto("https://example.com") 31 | 32 | main_page.verify_header_text() 33 | main_page.select_more_information_link() 34 | ican_navigation.verify_menu_links() 35 | ican_navigation.click_protocols_link() 36 | 37 | def test_select_about_us_link(page): 38 | main_page = MainPage(page) 39 | ican_navigation = IcanNavigationMenu(page) 40 | 41 | page.goto("https://example.com") 42 | 43 | main_page.verify_header_text() 44 | main_page.select_more_information_link() 45 | ican_navigation.verify_menu_links() 46 | ican_navigation.click_about_us_link() 47 | -------------------------------------------------------------------------------- /tests/without_bdd/test_without_bdd_workflow.py: -------------------------------------------------------------------------------- 1 | from Workflows import example_workflows 2 | from pages.ican_navigation import IcanNavigationMenu 3 | 4 | def test_select_domains_link(page): 5 | ican_navigation = IcanNavigationMenu(page) 6 | 7 | example_workflows.launch_and_navigate_to_ican_page(ican_navigation, page) 8 | ican_navigation.click_domain_link() 9 | 10 | 11 | def test_select_numbers_link(page): 12 | ican_navigation = IcanNavigationMenu(page) 13 | 14 | example_workflows.launch_and_navigate_to_ican_page(ican_navigation, page) 15 | ican_navigation.click_numbers_link() 16 | 17 | def test_select_protocols_link(page): 18 | ican_navigation = IcanNavigationMenu(page) 19 | 20 | example_workflows.launch_and_navigate_to_ican_page(ican_navigation, page) 21 | ican_navigation.click_protocols_link() 22 | 23 | def test_select_about_us_link(page): 24 | ican_navigation = IcanNavigationMenu(page) 25 | 26 | example_workflows.launch_and_navigate_to_ican_page(ican_navigation, page) 27 | ican_navigation.click_about_us_link() 28 | --------------------------------------------------------------------------------