انتقل إلى المحتوى
في هذه الصفحة

Webdrivers مخصصة لـ Selenium باستخدام المُزخرفات

السؤال الرئيسي:

كيفية إنشاء فئات webdriver selenium مخصصة بشكل ديناميكي مع مسار ثنائي للمتصفح مُعامَل؟

الدافع:

العمل على اختبارات الوظائف المواقع باستخدام selenium webdriver يؤدي إلى الرغبة في تشغيل الاختبارات باستخدام إصدارات مختلفة من المتصفحات الأكثر استخداماً اليوم، بشكل متوازٍ على نفس الجهاز.

يمكن للاختبارات استخدام parameterized حيث يمكن أن يكون المعامل هو فئة السائق نفسها، وهنا تظهر الحاجة إلى إنشاء فئات السواقي ديناميكياً وتمريرها كمعاملات للاختبارات.

الفكرة:

تمكين استخدام أكثر من إصدار واحد من المتصفح، أولاً يجب تثبيت هذه الإصدارات محلياً، ثم إنشاء مولد فئات webdriver مخصص مع مسار ثنائي مخصص واستخدام هذه الفئات المولّدة في الاختبارات دون تهيئتها.

الإجراء:

سأشرح الخطوات التي اتبعتها فيما يتعلق بالمتصفحات الأكثر شعبية اليوم: Mozilla Firefox و Google Chrome.

  • قم بتنزيل إصدارات Firefox التي تريد استخدامها في الاختبارات من أرشيف إصدارات Mozilla.

  • بالنسبة لـ Chrome قد يتطلب بعض الجهد، ولكن التعليمات واضحة على موقع مشروع Chromium، سأدرجها هنا في حال فقدانها:

    • ابحث في googlechromereleases.blogspot.com عن المرة الأخيرة التي تم فيها ذكر الإصدار المطلوب.
    • ابحث في سجل الإصدارات ذلك (مثلاً: “44.0.2403.157”) في Position Lookup
    • في هذه الحالة يُرجع أساساً من “330231”. هذا هو الالتزام حيث تم فرع إصدار 44، في مايو 2015.*
    • افتح أرشيف البناء المستمر
    • انقر على منصتك (Linux/Mac/Win)
    • الصق “330231” في حقل التصفية وانتظر حتى يتم تحميل جميع النتائج عبر Xhr.
    • في النهاية أحصل على نتيجة مثالية: commondatastorage.googleapis.com/chromium-browser-ssnapshots
    • أحياناً قد تحتاج إلى تخفيض رقم الالتزام حتى تجد واحداً.
    • قم بالتنزيل والتشغيل!
  • الآن لديك جميع إصداراتك جاهزة وقابلة للتشغيل، هناك عدة خيارات حيث يمكنك حفظها بحيث يمكن الوصول إليها في جميع أنحاء النظام. أحد الخيارات هو حفظها في متغيرات البيئة.

  • اكتب مولّد فئات webdriver مخصص، حيث يكون المسار الثنائي معاماً ويمكن تغييره ديناميكياً في اختبارات selenium. لكن المشكلة الأولى التي واجهتها هنا كانت إمكانية تغيير طريقة __init__ لفئتنا المخصصة وتمرير المسار الثنائي المخصص. اكتشفت أنه يمكن فعل ذلك بالاشتقاق من الفئة type:

binary = FirefoxBinary(bin_path)
def __init__(self, firefox_binary=binary):
    webdriver.Firefox.__init__(self, firefox_binary=firefox_binary)

Firefox = type('Firefox', (webdriver.Firefox, ), {'__init__': __init__})
  • بالنسبة لـ Google Chrome، يتم تغيير المسار الثنائي بشكل مختلف قليلاً:
chrome_options = Options()
chrome_options.binary_location = bin_path

def __init__(self, chrome_options=chrome_options):
    webdriver.Chrome.__init__(self, chrome_options=chrome_options)

Chrome = type('Chrome', (webdriver.Chrome, ), {'__init__': __init__})
  • سيكون عملياً أكثر لوضع هذه الوظائف في مُزخرفات. لهذا الغرض كتبت مُزخرفتين لإنشاء كائنات Firefox webdriver :
class CustomFirefox:

    def __init__(self, bin_path):
        self.bin_path = bin_path

    def __call__(self, cls):
        binary = FirefoxBinary(self.bin_path)

        def __init__(self, firefox_binary=binary):
            webdriver.Firefox.__init__(self, firefox_binary=firefox_binary)

        Firefox = type('Firefox', (webdriver.Firefox, ), {'__init__': __init__})
        return Firefox

وأخرى لكائنات Chrome webdriver :

class CustomChrome:

    def __init__(self, bin_path):
        self.bin_path = bin_path

    def __call__(self, cls):
        chrome_options = Options()
        chrome_options.binary_location = self.bin_path

        def __init__(self, chrome_options=chrome_options):
            webdriver.Chrome.__init__(self, chrome_options=chrome_options)

        Chrome = type('Chrome', (webdriver.Chrome, ), {'__init__': __init__})
        return Chrome
  • الآن كل ما تحتاجه هو تطبيق المُزخرف المناسب على فئة وتمرير المسار إلى الثنائي المخصص. على سبيل المثال:
@CustomFirefox('<path_to_firefox_binary>')
class Driver: pass
    print(Driver)

سيبدو المخرجات هكذا:

<class '__main__.Firefox'>

لفتح المتصفح، فقط استدعِ كائن webdriver newly أنشأته:

Driver()
  • هذا يعمل بشكل جيد، باستثناء أن البيانات الأصلية من فئة webdriver قد اختفت. للحفاظ على البيانات من الفئة الأصل webdriver.Firefox أو webdriver.Chrome، نحتاج إلى حفظ هذه البيانات أولاً قبل تغيير طريقة __init__، ثم استعادتها بعد ذلك. على سبيل المثال لمُزخرف Firefox :
meta_data = dict(webdriver.Firefox.__dict__)
meta_data['__init__'] = __init__
Firefox = type('Firefox', (webdriver.Firefox, ), meta_data)

الآن عندما نتحقق من الفئة المُعادة، نحصل على:

<class 'selenium.webdriver.firefox.webdriver.WebDriver'>
``¨

- قد يكون تكييفاً صغيراً مفيداً لتضمين الحالة الافتراضية حيث لا يتم تمرير أي مسار كمعامل، بحيث يُنشئ المُزخرف webdriver الافتراضي مع إصدار المتصفح المثبت على النظام.

- الآن من الممكن إنشاء مصنع السواقي لإنشاء webdrivers مخصصة من قائمة مسارات ثنائية، حيث تكون معاملات تلك المصنع المُزخرف وقائمة المسارات الثنائية:

```python
def driver_factory(decorator, bin_paths):
    drivers = []
    for bin_path in bin_paths:
        @decorator(bin_path)
        class CustomDriver: pass
        drivers.append(CustomDriver)
return drivers
  • Voilà! الـ webdriver جاهز للاستخدام في الاختبارات!

  • سيبدو السكربت الكامل هكذا:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary


class CustomFirefox:
    '''
    Decorator to generate Firefox webdriver
    the parameter passed to this decorator is the path to
    the executable firefox browser
    '''
    
    def __init__(self, bin_path):
        self.bin_path = bin_path
    
    def __call__(self, cls):
        if self.bin_path:
            binary = FirefoxBinary(self.bin_path)
            
            def __init__(self, firefox_binary=binary):
                webdriver.Firefox.__init__(self, firefox_binary=firefox_binary)
            
            meta_data = dict(webdriver.Firefox.__dict__)
            meta_data['__init__'] = __init__
            Firefox = type('Firefox', (webdriver.Firefox, ), meta_data)
            return Firefox
        else:
            return webdriver.Firefox

        
class CustomChrome:
    '''
    Decorator to generate Firefox webdriver
    the parameter passed to this decorator is the path to
    the executable chrome browser
    '''
    
    def __init__(self, bin_path):
        self.bin_path = bin_path
    
    def __call__(self, cls):
        if self.bin_path:
            chrome_options = Options()
            chrome_options.binary_location = self.bin_path

            def __init__(self, chrome_options=chrome_options):
                webdriver.Chrome.__init__(self, chrome_options=chrome_options)

            meta_data = dict(webdriver.Chrome.__dict__)
            meta_data['__init__'] = __init__
            Chrome = type('Chrome', (webdriver.Chrome, ), meta_data)
            return Chrome
        else:
            return webdriver.Chrome


def driver_factory(decorator, bin_paths):
    drivers = []
    for bin_path in bin_paths:
        @decorator(bin_path)
        class CustomDriver: pass 
        drivers.append(CustomDriver)
    return drivers

قد يكون من الممكن تحقيق نفس الوظيفة باستخدام فئة meta. سأتحقق من ذلك لاحقاً. إذا كان لديك أي تعليقات أو اقتراحات، يرجى مشاركتها معي.

العودة للأعلى