Selenium مع Python: أتمتة المتصفح
تعلّم كيفية أتمتة المتصفح باستخدام Selenium 4 وPython: التثبيت، تحديد العناصر، الانتظار الصريح، وضع headless ونمط Page Object.
InSkillCoach
Selenium مع Python: أتمتة المتصفح
Selenium هي المكتبة المرجعية للتحكم في متصفح الويب من خلال برنامج. سواء أردت أتمتة اختبارات الواجهة، أو ملء نماذج متكررة، أو استخراج بيانات من صفحات يتم توليدها بواسطة JavaScript، فإن Selenium تمنحك تحكمًا كاملًا في Chrome أو Firefox أو Edge. يغطي هذا الدليل أساسيات Selenium 4 مع Python: من التثبيت وحتى نمط Page Object، مرورًا بالانتظار الصريح الذي يصنع الفرق بين سكربت هشّ وسكربت موثوق.
التثبيت
منذ الإصدار Selenium 4.6، أصبحت إدارة الـ drivers تلقائية بفضل Selenium Manager المدمج في المكتبة. لم تعد هناك حاجة إلى تنزيل chromedriver أو geckodriver يدويًا: تكتشف Selenium المتصفح المثبّت لديك وتجلب الـ driver المناسب له.
# Installation de Selenium 4
pip install selenium
# Vérification de la version installée
pip show selenium
تأكد فقط من وجود متصفح مثبّت على الجهاز (Chrome أو Firefox أو Edge).
السكربت الأول
إليك سكربتًا بسيطًا يفتح صفحة، ويقرأ عنوانها، ثم يغلق المتصفح:
from selenium import webdriver
# Selenium Manager télécharge automatiquement le driver adapté
driver = webdriver.Chrome()
# Navigation vers une page
driver.get("https://www.python.org")
# Lecture du titre de la page
print(driver.title)
# Welcome to Python.org
# Lecture de l'URL courante
print(driver.current_url)
# https://www.python.org/
# Fermeture propre du navigateur
driver.quit()
توجد طريقتان للإغلاق: تغلق driver.close() التبويب الحالي، بينما تغلق driver.quit() جميع النوافذ وتنهي عملية الـ driver. يُفضَّل دائمًا استخدام quit() في نهاية السكربت.
تحديد العناصر
يبدأ كل تفاعل بتحديد عنصر في الـ DOM. تستخدم Selenium 4 الفئة By مع find_element (أول عنصر يتم العثور عليه) أو find_elements (قائمة بجميع العناصر).
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://www.python.org")
# Par identifiant
barre_recherche = driver.find_element(By.ID, "id-search-field")
# Par sélecteur CSS
bouton = driver.find_element(By.CSS_SELECTOR, "button.search-button")
# Par XPath
lien = driver.find_element(By.XPATH, "//a[contains(text(), 'Downloads')]")
# Tous les liens de la page
liens = driver.find_elements(By.TAG_NAME, "a")
print(f"Nombre de liens : {len(liens)}")
driver.quit()
جدول استراتيجيات التحديد
| الاستراتيجية | مثال | متى تُستخدم |
|---|---|---|
By.ID | By.ID, "email" | دائمًا في المقام الأول إذا وُجد id ثابت |
By.NAME | By.NAME, "username" | النماذج الكلاسيكية |
By.CSS_SELECTOR | By.CSS_SELECTOR, "div.card > a" | متعددة الاستخدامات، سريعة، مقروءة |
By.XPATH | By.XPATH, "//button[text()='OK']" | البحث بالنص أو الصعود في الـ DOM |
By.CLASS_NAME | By.CLASS_NAME, "btn-primary" | فئة واحدة فقط، دون مسافات |
By.TAG_NAME | By.TAG_NAME, "table" | مجموعات العناصر |
By.LINK_TEXT | By.LINK_TEXT, "Contact" | الروابط التي يُعرف نصها الدقيق |
By.PARTIAL_LINK_TEXT | By.PARTIAL_LINK_TEXT, "Cont" | الروابط التي يتغير نصها قليلًا |
كقاعدة عامة: فضّل By.ID أولًا، ثم محددات CSS. واحتفظ بـ XPath للحالات التي لا تغطيها CSS (التحديد بالنص، الانتقال إلى العنصر الأب).
التفاعل مع العناصر
النقر وإدخال النص
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
driver.get("https://www.python.org")
champ = driver.find_element(By.ID, "id-search-field")
# Saisie de texte
champ.send_keys("selenium")
# Validation avec la touche Entrée
champ.send_keys(Keys.RETURN)
# Effacer un champ avant une nouvelle saisie
champ = driver.find_element(By.ID, "id-search-field")
champ.clear()
champ.send_keys("nouvelle recherche")
driver.quit()
القوائم المنسدلة
بالنسبة إلى عناصر <select>، توفر Selenium الفئة المساعدة Select:
from selenium.webdriver.support.ui import Select
element_select = driver.find_element(By.ID, "pays")
select = Select(element_select)
# Trois façons de sélectionner une option
select.select_by_visible_text("France")
select.select_by_value("fr")
select.select_by_index(2)
# Lire l'option sélectionnée
print(select.first_selected_option.text)
قراءة خصائص عنصر
element = driver.find_element(By.CSS_SELECTOR, "h1")
print(element.text) # Texte visible
print(element.get_attribute("class")) # Valeur d'un attribut
print(element.is_displayed()) # True si visible
print(element.is_enabled()) # True si actif
الانتظار: الضمني مقابل الصريح
هذه أهم نقطة في هذا الدليل. تحمّل الصفحات الحديثة محتواها عبر JavaScript بشكل غير متزامن: قد لا يكون العنصر موجودًا بعد في اللحظة التي يبحث عنه فيها السكربت. وبدون انتظار، ستحصل على أخطاء NoSuchElementException عشوائية.
الانتظار الضمني
يُطبَّق الانتظار الضمني بشكل عام: عند كل find_element، تعيد Selenium المحاولة طوال المدة المحددة قبل رفع استثناء.
driver = webdriver.Chrome()
# Attendre jusqu'à 10 secondes pour chaque recherche d'élément
driver.implicitly_wait(10)
هذا بسيط لكنه غير كافٍ: فالانتظار الضمني يتحقق فقط من الوجود في الـ DOM، لا من الظهور ولا من قابلية النقر. فقد يكون العنصر موجودًا لكنه مخفي أو معطّل.
الانتظار الصريح (موصى به)
يستهدف الانتظار الصريح شرطًا محددًا على عنصر محدد، باستخدام WebDriverWait والوحدة expected_conditions:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://www.python.org")
# Attendre jusqu'à 10 secondes que l'élément soit cliquable
wait = WebDriverWait(driver, 10)
bouton = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "button.search-button"))
)
bouton.click()
driver.quit()
أكثر الشروط فائدة:
| الشرط | يتحقق من أن |
|---|---|
presence_of_element_located | العنصر موجود في الـ DOM |
visibility_of_element_located | العنصر موجود ومرئي |
element_to_be_clickable | العنصر مرئي ونشط |
text_to_be_present_in_element | نصًا معينًا معروض |
invisibility_of_element_located | عنصرًا (spinner) قد اختفى |
title_contains | عنوان الصفحة يحتوي على سلسلة نصية |
القاعدة الذهبية: لا تستخدم time.sleep() أبدًا لانتظار عنصر. فـ sleep قصير جدًا يجعل السكربت غير مستقر، وsleep طويل جدًا يبطئه دون داعٍ. أما الانتظار الصريح فيتوقف بمجرد تحقق الشرط. تجنب أيضًا الخلط بين الانتظار الضمني والصريح: فقد تتراكم أزمنة الانتظار بشكل لا يمكن التنبؤ به.
وضع headless
ينفّذ وضع headless المتصفح دون واجهة رسومية. وهو ضروري على خادم التكامل المستمر:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless=new") # Nouveau mode headless de Chrome
options.add_argument("--window-size=1920,1080") # Taille de fenêtre fixe
driver = webdriver.Chrome(options=options)
driver.get("https://www.python.org")
print(driver.title)
# Welcome to Python.org
driver.quit()
حدّد دائمًا حجم النافذة في وضع headless: فهو صغير افتراضيًا، وقد تختفي بعض العناصر بسبب العرض المتجاوب.
لقطات الشاشة
اللقطات ثمينة لتشخيص إخفاقات الاختبارات:
# Capture de la page visible
driver.save_screenshot("page.png")
# Capture d'un élément précis
element = driver.find_element(By.ID, "content")
element.screenshot("element.png")
من الممارسات الجيدة: التقط صورة بشكل منهجي داخل كتلة except في اختباراتك لترى حالة الصفحة لحظة الإخفاق.
الـ iframes والنوافذ المتعددة
الـ iframes
يكون العنصر الموجود داخل <iframe> غير مرئي بالنسبة إلى Selenium ما لم تنقل السياق إليه:
# Basculer vers l'iframe (par élément, nom ou index)
iframe = driver.find_element(By.CSS_SELECTOR, "iframe#editeur")
driver.switch_to.frame(iframe)
# Interagir avec le contenu de l'iframe
driver.find_element(By.TAG_NAME, "body").send_keys("Bonjour")
# Revenir au document principal
driver.switch_to.default_content()
النوافذ والتبويبات
لكل نافذة معرّف (handle). لمتابعة رابط يفتح تبويبًا جديدًا:
fenetre_principale = driver.current_window_handle
# Le clic ouvre un nouvel onglet
driver.find_element(By.LINK_TEXT, "Documentation").click()
# Basculer vers le nouvel onglet
for handle in driver.window_handles:
if handle != fenetre_principale:
driver.switch_to.window(handle)
break
print(driver.title) # Titre du nouvel onglet
# Fermer l'onglet et revenir à la fenêtre principale
driver.close()
driver.switch_to.window(fenetre_principale)
نمط Page Object
عندما تكبر السكربتات، تتكرر المحددات في كل مكان، وكل تغيير في الواجهة يُفسد عشرات الأسطر. يحل نمط Page Object هذه المشكلة: تُمثَّل كل صفحة من الموقع بفئة تغلّف محدداتها وإجراءاتها.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class PageConnexion:
"""Encapsule la page de connexion : sélecteurs et actions."""
URL = "https://exemple.com/connexion"
CHAMP_EMAIL = (By.ID, "email")
CHAMP_MOT_DE_PASSE = (By.ID, "password")
BOUTON_CONNEXION = (By.CSS_SELECTOR, "button[type='submit']")
MESSAGE_ERREUR = (By.CSS_SELECTOR, ".alert-danger")
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
def ouvrir(self):
self.driver.get(self.URL)
return self
def se_connecter(self, email, mot_de_passe):
self.wait.until(
EC.visibility_of_element_located(self.CHAMP_EMAIL)
).send_keys(email)
self.driver.find_element(*self.CHAMP_MOT_DE_PASSE).send_keys(mot_de_passe)
self.driver.find_element(*self.BOUTON_CONNEXION).click()
def message_erreur(self):
return self.wait.until(
EC.visibility_of_element_located(self.MESSAGE_ERREUR)
).text
# Utilisation dans un test : le test ne connaît aucun sélecteur
driver = webdriver.Chrome()
page = PageConnexion(driver).ouvrir()
page.se_connecter("utilisateur@exemple.com", "mauvais_mot_de_passe")
assert "identifiants invalides" in page.message_erreur().lower()
driver.quit()
إذا تغيرت الواجهة، فلا تعدّل سوى الفئة PageConnexion: وتبقى جميع الاختبارات التي تستخدمها سليمة.
أفضل الممارسات لتفادي الاختبارات غير المستقرة (flaky)
الاختبار «flaky» هو الذي يفشل بشكل عشوائي دون أن يتغير الكود المختبَر. إليك كيفية تجنبه:
- امنع استخدام
time.sleep(): استبدل كلsleepبانتظار صريح على شرط يمكن ملاحظته. - انتظر الشرط الصحيح:
element_to_be_clickableقبل النقر، وinvisibility_of_element_locatedعلى spinner التحميل قبل قراءة النتيجة. - اختر محددات ثابتة: فضّل السمات المخصصة للاختبارات (
data-testid) على فئات CSS المولّدة تلقائيًا. - اعزل الاختبارات: ينشئ كل اختبار بياناته الخاصة ولا يعتمد على ترتيب تنفيذ الاختبارات الأخرى.
- حدّد حجم النافذة: فاختلاف العرض المتجاوب بين جهازك وخادم CI يغيّر ترتيب العناصر.
- التقط الشاشة عند الإخفاق: صورة مع HTML الصفحة (
driver.page_source) تسرّع التشخيص كثيرًا. - استخدم
driver.quit()داخل كتلةfinallyأو مدير سياق (context manager) حتى لا تترك أي عمليات يتيمة.
الخاتمة
يوفر Selenium 4 مع Python كل ما يلزم لأتمتة المتصفح بشكل متين: فالإدارة التلقائية للـ drivers تبسّط التثبيت، والفئة By ومحددات CSS تغطيان التحديد، والانتظار الصريح مع WebDriverWait يضمن الموثوقية أمام الصفحات غير المتزامنة. وفي المشاريع التي تتجاوز بضعة سكربتات، يهيكل نمط Page Object الكود ويحميه من تغيرات الواجهة.
لمزيد من التعمق، راجع التوثيق الرسمي لـ Selenium واستكشف التكامل مع pytest لتنظيم مجموعات اختباراتك.
نبذة عن InSkillCoach
خبير في التدريب والتقنيات
مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.
الشهادات:
- AWS Certified Solutions Architect – Professional
- شهادات Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
التعليقات
التعليقات مدعومة عبر GitHub Discussions
سجّل الدخول باستخدام GitHub للمشاركة في النقاش