איך אני בונה תשתית אוטומציה ב-Python ל-Selenium Chrome
מסיבות של אבטחת מידע אני לא יכול להציג כאן את כל הפרויקט האמיתי שלי,
אבל אני כן יכול להראות את הכיוון והמבנה הכללי שבו אני משתמש כדי לכתוב בדיקות אוטומציה בצורה מהירה,
מסודרת וניתנת להעתקה לפרויקטים אחרים.
הרעיון המרכזי הוא לבנות קובץ בדיקה אחד שנראה כמו תבנית עבודה:
בתחילת הקובץ יש נתוני בדיקה, אחר כך רשימת לוקייטורים, אחר כך פונקציות עזר,
ובסוף ה-flow העסקי עצמו.
בפרויקט עצמו ההסברים בתוך הקוד כתובים באנגלית, כי זה מקובל יותר בפרויקטי אוטומציה
ומקל על עבודה עם צוותים, ספריות וכלים חיצוניים.
ייבוא ספריות וכלים
# Enable modern type-hint behavior for this file.
from __future__ import annotations
# Import Allure decorators for report grouping.
import allure
# Import pytest to mark this test with environment labels.
import pytest
# Import Select for working with real HTML select dropdowns.
from selenium.webdriver.support.select import Select
# Import shared URLs and environment settings.
from wulfauto.config.settings import settings
# Import Allure helpers for readable steps and text attachments.
from wulfauto.core.allure_utils import allure_step, attach_text
# Import explicit wait helpers used before actions.
from wulfauto.core.waits import clickable, text_present, visible
נתוני בדיקה במקום אחד
את נתוני הבדיקה אני שומר בתחילת הקובץ, כדי שלא יהיו מפוזרים בתוך ה-flow עצמו.
# Keep editable test data near the top so it is easy to reuse this file for another site.
TEST_USER = {
"name": "Desktop Chrome",
"email": "[email protected]",
"plan": "Pro",
}
לוקייטורים במקום אחד
את כל ה-selectors אני שומר במילון אחד בשם SEL.
אם האתר משתנה, בדרך כלל צריך לעדכן רק את החלק הזה.
# Keep selectors in one place so changing a page locator does not require reading the whole test.
SEL = {
"hero_cta": "[data-testid='hero-cta']",
"practice_status": "[data-testid='practice-status']",
"name_input": "[data-testid='name-input']",
"email_input": "[data-testid='email-input']",
"plan_select": "[data-testid='plan-select']",
"terms_checkbox": "[data-testid='terms-checkbox']",
"submit_order": "[data-testid='submit-order']",
"form_result": "[data-testid='form-result']",
}
כאשר אפשר, אני מעדיף להשתמש ב-data-testid,
כי זה selector יציב יותר מטקסט, class עיצובי או XPath ארוך.
פונקציות עזר
פונקציות עזר הן לא פקודות מובנות של Selenium.
אלה פונקציות שאני כותב כדי שהבדיקה תהיה קצרה, קריאה ובטוחה יותר.
css
הפונקציה css מקבלת שם לוגי מתוך SEL ומחזירה locator מוכן ל-Selenium.
def css(by, key: str) -> tuple[str, str]:
"""Return a CSS locator from the selector dictionary."""
# A tiny helper keeps test actions short while selectors stay centralized.
return (by.CSS_SELECTOR, SEL[key])
open_page
הפונקציה open_page פותחת URL ומצרפת את הכתובת הסופית לדוח Allure.
זה שימושי כאשר יש redirects או מעבר למסך login.
def open_page(driver, url: str) -> None:
"""Open a page and attach the final URL to Allure."""
# get() is the standard Selenium command for opening a page.
driver.get(url)
# The current URL is useful when redirects or login pages are involved.
attach_text("Opened URL", driver.current_url)
click
הפונקציה click ממתינה עד שהאלמנט באמת לחיץ ורק אז לוחצת עליו.
כך הבדיקה לא רצה מהר מדי לפני שה-UI מוכן.
def click(driver, by, key: str, timeout: int = 10) -> None:
"""Click an element by selector key after waiting for it to be clickable."""
# Always wait before clicking so the test does not race the UI.
clickable(driver, css(by, key), timeout=timeout).click()
type_text
הפונקציה type_text ממתינה שהשדה יהיה visible, מנקה אותו במידת הצורך,
ואז מקלידה לתוכו טקסט.
def type_text(driver, by, key: str, value: str, clear_first: bool = True, timeout: int = 10) -> None:
"""Type text into an input by selector key."""
# Wait until the input is visible before interacting with it.
field = visible(driver, css(by, key), timeout=timeout)
# Clear old text when the same profile or browser state is reused.
if clear_first:
field.clear()
# send_keys is the standard Selenium command for typing.
field.send_keys(value)
select_visible_text
הפונקציה select_visible_text מיועדת ל-dropdown אמיתי של HTML מסוג <select>.
def select_visible_text(driver, by, key: str, text: str) -> None:
"""Select an option from a real HTML select element."""
# Selenium Select should be used only for real HTML select tags.
Select(visible(driver, css(by, key))).select_by_visible_text(text)
set_checkbox
הפונקציה set_checkbox הופכת עבודה עם checkbox לבטוחה יותר.
היא לא לוחצת סתם, אלא קודם בודקת האם checkbox כבר נמצא במצב הרצוי.
def set_checkbox(driver, by, key: str, expected_checked: bool) -> None:
"""Set a checkbox to the requested state without double-clicking it."""
# Find the checkbox after the page is ready.
checkbox = visible(driver, css(by, key))
# Compare current state to desired state so the action is idempotent.
if checkbox.is_selected() != expected_checked:
checkbox.click()
scroll_to
הפונקציה scroll_to גוללת לאלמנט מסוים.
זה שימושי כאשר כפתור או אזור בדף נמצא מחוץ למסך.
def scroll_to(driver, element) -> None:
"""Scroll an element into the center of the viewport."""
# JavaScript scroll is useful when normal clicks are blocked by viewport position.
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)
scroll_to_key
הפונקציה scroll_to_key מוצאת אלמנט לפי key מתוך SEL,
גוללת אליו ומחזירה אותו להמשך שימוש.
def scroll_to_key(driver, by, key: str):
"""Find an element by selector key, scroll to it, and return it."""
# Wait for the element before scrolling to avoid stale or missing elements.
element = visible(driver, css(by, key))
# Move the element into a stable visible position.
scroll_to(driver, element)
# Return the element so the caller can assert text or click it.
return element
assert_text
הפונקציה assert_text ממתינה עד שאלמנט מכיל טקסט מסוים.
בפועל זו בדיקה שהתוצאה שרצינו באמת קרתה.
def assert_text(driver, by, key: str, expected_text: str, timeout: int = 10) -> None:
"""Wait until an element contains expected text."""
# This is a readable wrapper around an explicit text wait.
text_present(driver, css(by, key), expected_text, timeout=timeout)
דוגמה ל-flow עסקי
עכשיו הבדיקה עצמה נראית כמו סיפור עסקי.
שימו לב שבהערות בתוך הקוד אני מציין מתי משתמשים בפונקציית עזר.
@allure.epic("Desktop Chrome")
@allure.feature("Website")
@allure.story("Complex website flow template")
@allure.title("Desktop Chrome user can complete a complex demo website flow")
@pytest.mark.desktop_chrome
@pytest.mark.selenium
def test_desktop_chrome_core_actions(desktop_chrome, by):
"""Run a readable Desktop Chrome business flow."""
# Use the open_page helper to open the configured website URL.
open_page(desktop_chrome, settings.base_url)
# Start an Allure step for the first user action.
with allure_step("Start practice with a button click"):
# Use the click helper to wait for the button and click it.
click(desktop_chrome, by, "hero_cta")
# Use text_present directly to wait until the click result appears.
text_present(
desktop_chrome,
css(by, "practice_status"),
"Practice started"
)
# Start an Allure step for filling and submitting the form.
with allure_step("Fill form fields and submit order"):
# Use the type_text helper to clear and type the user's display name.
type_text(desktop_chrome, by, "name_input", TEST_USER["name"])
# Use the type_text helper to clear and type the user's email.
type_text(desktop_chrome, by, "email_input", TEST_USER["email"])
# Use the select_visible_text helper for a real HTML select dropdown.
select_visible_text(desktop_chrome, by, "plan_select", TEST_USER["plan"])
# Use the set_checkbox helper to set the checkbox safely.
set_checkbox(desktop_chrome, by, "terms_checkbox", True)
# Use the click helper to submit the form.
click(desktop_chrome, by, "submit_order")
# Use text_present directly to verify the final result text.
text_present(
desktop_chrome,
css(by, "form_result"),
"Order saved"
)
מה משנים כשעוברים לפרויקט אחר?
- משנים את
BASE_URLבקובץ ההגדרות. - מעדכנים את
TEST_USER. - מחליפים את הלוקייטורים בתוך
SEL. - משנים את שמות ה-
allure_step. - מסדרים את סדר הפעולות לפי ה-flow החדש.
למה המבנה הזה טוב?
- הנתונים נמצאים במקום אחד.
- הלוקייטורים נמצאים במקום אחד.
- ה-flow העסקי נשאר קריא.
- פונקציות העזר מקטינות כפילויות.
- Allure עוזר להבין באיזה שלב הבדיקה נפלה.
- קל לקחת את הקובץ כתבנית לפרויקט הבא.
סיכום
המטרה היא לא לכתוב בדיקה חד-פעמית, אלא לבנות תבנית שאפשר לקחת לפרויקט הבא.
כאשר מפרידים בין נתונים, לוקייטורים, פונקציות עזר וה-flow העסקי,
Selenium הופך להרבה יותר נוח לתחזוקה.