Implement Keyword-Driven Testing
When to Use
- Reducing test code duplication
- Enabling non-technical team members to create tests
- Implementing high-level business operations
- Creating reusable test keywords
- Building domain-specific languages for tests
- Large test suites with similar operations
Procedure
1. Create Base Keyword Class
keywords/base_keyword.py
from utils.logger import get_logger
import inspect
logger = get_logger(__name__)
class BaseKeyword:
"""Base class for all keywords."""
def log_keyword_call(self, *args, **kwargs):
logger.info(f"Executing keyword: {self.__class__.__name__}")
logger.debug(f"Args: {args}, Kwargs: {kwargs}")
def __call__(self, *args, **kwargs):
self.log_keyword_call(*args, **kwargs)
return self.execute(*args, **kwargs)
def execute(self, *args, **kwargs):
raise NotImplementedError("execute() must be implemented")
2. Create UI Keywords for Playwright
keywords/ui_keywords.py
from keywords.base_keyword import BaseKeyword
from pages.login_page import LoginPage
from pages.dashboard_page import DashboardPage
from utils.logger import get_logger
logger = get_logger(__name__)
class OpenPage(BaseKeyword):
"""Open a URL in the browser."""
def __init__(self, page):
self.page = page
def execute(self, url: str) -> bool:
self.page.goto(url)
logger.info(f"Opened: {url}")
return True
class LoginAsUser(BaseKeyword):
"""Login with username and password."""
def __init__(self, page):
self.page = page
def execute(self, username: str, password: str) -> bool:
login = LoginPage(self.page)
login.login(username, password)
logger.info(f"Logged in as {username}")
return True
class VerifyDashboardLoaded(BaseKeyword):
"""Verify user is on the dashboard."""
def __init__(self, page):
self.page = page
def execute(self) -> bool:
dashboard = DashboardPage(self.page)
result = dashboard.is_user_logged_in()
logger.info(f"Dashboard loaded: {result}")
return result
class ClickElement(BaseKeyword):
"""Click element by CSS selector."""
def __init__(self, page):
self.page = page
def execute(self, selector: str) -> bool:
self.page.click(selector)
logger.info(f"Clicked: {selector}")
return True
class FillField(BaseKeyword):
"""Fill input field with text."""
def __init__(self, page):
self.page = page
def execute(self, selector: str, text: str) -> bool:
self.page.fill(selector, text)
logger.info(f"Filled {selector}")
return True
class VerifyTextVisible(BaseKeyword):
"""Verify text is visible on page."""
def __init__(self, page):
self.page = page
def execute(self, text: str) -> bool:
found = self.page.is_visible(f"text={text}")
logger.info(f"Text '{text}' visible: {found}")
return found
class TakeScreenshot(BaseKeyword):
"""Take screenshot with given name."""
def __init__(self, page):
self.page = page
def execute(self, name: str) -> bool:
self.page.screenshot(path=f"reports/screenshots/{name}.png")
logger.info(f"Screenshot saved: {name}")
return True
class WaitForElement(BaseKeyword):
"""Wait for element to be visible."""
def __init__(self, page):
self.page = page
def execute(self, selector: str, timeout: int = 10000) -> bool:
try:
self.page.wait_for_selector(selector, timeout=timeout)
logger.info(f"Element visible: {selector}")
return True
except Exception:
logger.warning(f"Element not found: {selector}")
return False
3. Use Keywords in Tests
tests/ui/test_login_keywords.py
import pytest
from keywords.ui_keywords import (
OpenPage,
LoginAsUser,
VerifyDashboardLoaded,
TakeScreenshot
)
class TestLoginKeywords:
@pytest.fixture(autouse=True)
def setup(self, page):
self.pw_page = page.page
@pytest.mark.smoke
def test_login_flow(self):
# Plain-English keyword calls — readable by anyone
OpenPage(self.pw_page).execute("https://app.example.com/login")
LoginAsUser(self.pw_page).execute("admin@example.com", "pass123")
assert VerifyDashboardLoaded(self.pw_page).execute()
TakeScreenshot(self.pw_page).execute("post_login")
@pytest.mark.regression
def test_failed_login(self):
from keywords.ui_keywords import VerifyTextVisible
OpenPage(self.pw_page).execute("https://app.example.com/login")
LoginAsUser(self.pw_page).execute("user@example.com", "wrongpass")
assert VerifyTextVisible(self.pw_page).execute("Invalid credentials")
Best Practices
- Keywords map to business actions, not technical steps
- Each keyword does ONE thing
- Non-technical team members can read and write tests
- Reuse keywords across dozens of test cases
- Keywords abstract away Playwright API details