0%
Selenium مع Python: أتمتة المتصفح

Selenium مع Python: أتمتة المتصفح

تعلّم كيفية أتمتة المتصفح باستخدام Selenium 4 وPython: التثبيت، تحديد العناصر، الانتظار الصريح، وضع headless ونمط Page Object.

I

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.IDBy.ID, "email"دائمًا في المقام الأول إذا وُجد id ثابت
By.NAMEBy.NAME, "username"النماذج الكلاسيكية
By.CSS_SELECTORBy.CSS_SELECTOR, "div.card > a"متعددة الاستخدامات، سريعة، مقروءة
By.XPATHBy.XPATH, "//button[text()='OK']"البحث بالنص أو الصعود في الـ DOM
By.CLASS_NAMEBy.CLASS_NAME, "btn-primary"فئة واحدة فقط، دون مسافات
By.TAG_NAMEBy.TAG_NAME, "table"مجموعات العناصر
By.LINK_TEXTBy.LINK_TEXT, "Contact"الروابط التي يُعرف نصها الدقيق
By.PARTIAL_LINK_TEXTBy.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

نبذة عن InSkillCoach

خبير في التدريب والتقنيات

مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.

الشهادات:

  • AWS Certified Solutions Architect – Professional
  • شهادات Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
873
234

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!