Как использовать единый глобальный драйвер селена для экономии памяти

TLDR: я создаю несколько драйверов селена для тестирования различных функций, и это использует слишком много памяти. Есть ли способ ограничить использование памяти селеном? (Я уже бегу без головы)

Поэтому я использую селен с pytest для запуска тестов на своем веб-сайте. Для настройки правильной среды для запуска этих тестов я использую несколько функций/приспособлений, которые создают внутри них еще один драйвер и закрывают драйвер, когда они завершаются.

Но этот подход проблематичен для меня, поскольку он использует слишком много памяти и приводит к сбою моей машины каждый раз, когда я пытаюсь запустить/отладить. У меня есть несколько служебных функций в разных файлах, которые используются в моих тестах и ​​создают собственный драйвер для выполнения своей работы. Я хочу знать, смогу ли я использовать один драйвер для выполнения этих служебных функций.

Вот тесты, которые я пытаюсь запустить.

from lib.driver_options import get_driver_options
import lib.resetEnvr as resetEnvr
import lib.sayTRUST_ClientGroup as groupmanager
#... other imports
@pytest.fixture(scope = "module")
def load_config():
        file_path = os.path.dirname(__file__)
        config_file_path = os.path.join(os.path.dirname(os.path.dirname(file_path)), 'config', 'website_config.json')
        vz_config_path = os.path.join(os.path.dirname(os.path.dirname(file_path)), 'config',
                                      'cloud_testserver.json')

        with open(config_file_path) as f:
            configurations = json.load(f)
        with open(vz_config_path) as f:
            vz_config = json.load(f)

        default_config = configurations['default_config']
        keyfile = os.path.join(os.path.dirname(os.path.dirname(file_path)), 'config', 'Service_PrivateKey_RSA')

        return {
            'default_config': default_config,
            'vz_config': vz_config,
            'keyfile': keyfile
        }

class Test_NetworkAccess():
    """
    Test cases:
        Changing the network settings and making sure they all work.
    """
    NATBUDDY_PRIVIP = "1.2.3.4"
    NATBUDDY_PUBIP = "1.2.3.4"
    TCPPORT = 8360
    UDPPORT = 8361
    config =None

    @pytest.fixture(scope = "function")
    def setup_environment(self, load_config):
        print("setup method")
        options = get_driver_options()
        service = Service()
        driver = webdriver.Chrome(service=service, options=options)
        driver.implicitly_wait(2)
        vars = {}
        # env reseter cleans up the websites state after each testing.
        env_reseter = resetEnvr.dbCleaner()
        
        ifaces = load_config['vz_config']["TemplateVM_info"]["network interfaces"]
        for iface in ifaces:
            for fixed_ip in iface.get("fixed_ips", []):
                ip = fixed_ip.get("ip_address", "")
                if ip.startswith("10.236."):
                    pub_ip = ip
                else:
                    priv_ip = ip
        # try to delete previous settings.
        try:
            groupmanager.delete_testClients()
        except:
            pass
        try:
            groupmanager.delete_testGroup()
        except:
            pass
        yield {
            'driver': driver,
            'env_reseter': env_reseter,
            'pub_ip': pub_ip,
            'priv_ip': priv_ip,
            'vars': vars
        }

        # Teardown after each test function

        driver.close()
        driver.quit()
    @pytest.fixture(scope = "module", autouse=True)
    def setup_iface(self):
        print("setup interface")
        print("add_listenPort")

        groupmanager.add_listenPort()
        print("add_private_iface")
        groupmanager.add_private_iface()
        env_reseter = resetEnvr.dbCleaner()
        print("get backup")
        env_reseter.getBackup()
    @pytest.fixture(scope = "function")
    def create_test_group(self,request, setup_environment):
        print("create test group")
        params = request.param
        groupmanager.create_testGroup(**params)
        print("create test group yields")
        yield
        # Teardown 
        groupmanager.delete_testGroup()

    @pytest.fixture(scope = "function")
    def create_test_client(self, request, create_test_group):
        params = request.param
        print("create test client")
        groupmanager.create_testClient(**params)
        print("create test client yields")
        yield
        # teardown
        groupmanager.delete_testClients()

    

    @pytest.mark.parametrize("create_test_group", [
        {"NATBuddyPub": False, "TCP": True, "UDP": False, "SSH": True},
        {"NATBuddyPub": True, "TCP": True, "UDP": False, "SSH": True},
        # Add other configurations as needed
    ], indirect=True)
    @pytest.mark.parametrize("create_test_client", [
        {},
    ], indirect=True)
    @pytest.mark.usefixtures("setup_environment","create_test_group","create_test_client")
    def test_NATBuddyGroupPubSSH(self,setup_environment,create_test_group,create_test_client, record_xml_attribute ,request):

        # do some testing without using Driber
       print("do some testing")

Вот пример того, как другие служебные функции используют драйвер селена. И настройки я использую для драйвера. Все они используют драйвер одинаково.


def get_driver_options():
    options = webdriver.ChromeOptions()
    options.add_argument("--headless=new")
    options.add_argument("--window-size=1440, 900")
    options.add_argument('--ignore-certificate-errors')
    options.add_argument('--allow-running-insecure-content')
    options.add_argument("--disable-extensions")
    options.add_argument("--proxy-server='direct://'")
    options.add_argument("--proxy-bypass-list=*")
    options.add_argument("--start-maximized")
    options.add_argument('--disable-gpu')
    options.add_argument('--disable-dev-shm-usage')
    options.add_argument("--FontRenderHinting[none]")
    options.add_argument('--no-sandbox')
    options.add_argument('log-level=3')
    options.add_argument('--ignore-ssl-errors=yes')
    options.add_argument('--allow-insecure-localhost')

    return options

def add_private_iface():

    options = get_driver_options()
    service = Service()
    driver = webdriver.Chrome(service=service, options=options)
    driver.implicitly_wait(15)
    website_url = default_config["website_url"]
    driver.get(website_url)
    # do some stuff
    # add some private interface to website
    driver.find_element(By.LINK_TEXT, "Logout").click()
    driver.close()
    driver.quit()

И когда я запускаю эти тесты, я наблюдаю, что Google Chrome начинает использовать сумасшедшую нагрузку на процессор, и моя машина зависает.

Ранее я пытался запустить эти функции с передачей драйвера в качестве параметра, но, похоже, это не сработало и выдало кучу странных ошибок. Попробовав заставить это работать, я решил сделать это таким образом, но теперь у меня есть сомнения. Можно ли сделать для всего этого один драйвер селена?

Или, может быть, мне чего-то не хватает, возможно, в драйвере есть что-то, что я забываю отключить между вызовами функций. Я уже использую драйвер Chrome без головы, поэтому не знаю других настроек для этого.

Я не думаю, что проблема связана с другой частью pyton, потому что я заметил, что несколько экземпляров Chrome запускаются в диспетчере задач во время работы программы, и это причина, которая ограничивает использование моего процессора.

ОБНОВЛЕНИЕ 1: я успешно реализовал синглтон. Но теперь проблема изменилась. Когда драйвер Singleton вызывается самим pytest, Selenium не может подключиться. На данный момент мое главное подозрение заключается в том, что pytest каким-то образом блокирует себя через многопоточность, хотя я пытался реализовать механизм блокировки для моего одноэлементного драйвера. Вот драйвер Singleton, который я сделал.

class SdriverMeta(type):
    _instances  = {}
    _lock: Lock = Lock()

    def __call__(cls, *args, **kwargs):
        
        with cls._lock:
          
            if cls not in cls._instances:
                instance = super().__call__(*args, **kwargs)
                cls._instances[cls] = instance
        return cls._instances[cls]
class SDriver:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(SDriver, cls).__new__(cls)
            options = get_driver_options()
            service = Service()
            cls._instance.driver =webdriver.Chrome(service=service, options=options)
            cls._instance.driver.implicitly_wait(30)
        return cls._instance
    def get_driver(self):
        return self.driver
    def close_driver(self):
        if self.driver:
            self.driver.close()
            self.driver.quit()
            SDriver._instance = None

И вот в чем проблема. В одном из приспособлений для создания клиентской группы тестирования для надлежащего сертификационного тестирования я пытаюсь заставить селен открыть мой веб-сайт, но он выдает *MaxRetryError, превышено максимальное количество повторов с URL-адресом: /session/2369e6d74474ac5c8ab85e652b356e0c/url (вызвано NewConnectionError('< Объект urllib3.connection.HTTPConnection по адресу 0x0000027B36908B50>: Не удалось установить новое соединение: [WinError 10061] *

@pytest.fixture(scope = "function")
    def create_test_group(self,request, setup_environment):
        print("create test group")
        params = request.param
        groupmanager.create_testGroup(**params)# here we have the problem.
        print("create test group yields")
        yield
        # Teardown steps if necessary (e.g., deleting the test group)
        groupmanager.delete_testGroup()

def create_testGroup(NATBuddyPub=False, SSH=False, TCP=False, UDP=False):
    '''
    options = get_driver_options()
    service = Service()
    driver = webdriver.Chrome(service=service, options=options)
    '''
    sdriver=SDriver()
    driver = sdriver.get_driver()
    #driver.implicitly_wait(60)

    website_url = default_config["website_url"]
    driver.get(website_url) # this is where it actually messes up 
    # do some other stuff...

Итак, моя проблема с памятью, похоже, решена. Но на этот раз оно заменено на maxRetryError.

ОБНОВЛЕНИЕ 2: Эта ошибка была вызвана тем, что я неправильно вызвал драйвер синглтона класса и вместо этого вызвал self.

def get_driver(cls):
     if cls._instance.driver is None:
            options = get_driver_options()
            service = Service()
            cls._instance.driver = webdriver.Chrome(service=service, options=options)
            cls._instance.driver.implicitly_wait(30)
     return cls._instance.driver

это решило проблему выше. И все же я вернулся прямо к исходной точке. Если я запускаю код построчно, кажется, он работает нормально. Однако как только я запускаю его, он перегружает процессор и зависает мой компьютер.

ОБНОВЛЕНИЕ 3: В комментарии @Techrookie89 я добавил к своей машине дополнительные ядра, и это, похоже, сработало для части замораживания и разрушения. В некоторых тестах код содержал некоторую логику создания подпроцессов. Думаю, в этом была проблема.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я не эксперт в питоне, но проблему, которую вы излагаете, можно решить с помощью паттерна проектирования Singleton. По сути, вы проверяете, является ли объект нулевым или нет, если он равен нулю? вы его инициализируете, а если это не так, вы возвращаете ранее инициализированный экземпляр.

Это должно быть что-то похожее на фрагмент ниже:

class WebBrowser:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(WebBrowser, cls).__new__(cls)
            options = get_driver_options()
            service = Service()
            driver = webdriver.Chrome(service=service, options=options)
            driver.implicitly_wait(15)
            cls._instance.driver = webdriver.Chrome()
        return cls._instance

    def get_driver(self):
        return self.driver

    def close_driver(self):
        if self.driver:
            self.driver.quit()
            WebBrowser._instance = None

Затем это можно использовать в наших тестах, как показано ниже:

browser = WebBrowser().get_driver()
browser.get("https://duckduckgo.com")

Примечание. Если вы хотите, чтобы тип веб-драйвера также был динамическим, я бы посоветовал также прочитать шаблон проектирования Factory.

Хорошо, я пытался использовать этот шаблон проектирования Singleton для своего драйвера, но, похоже, он нарушает работу селена. Когда я пытаюсь получить URL-адрес с помощью add_private_iface(): или любой другой функции, например, он выдает мне MaxRetryError для подключения к локальному хосту для некоторого порта x.

ajax 09.07.2024 17:27

Можете ли вы поделиться сутью обновленного кода, который вы пробовали?

Techrookie89 10.07.2024 02:06

Действителен ли URL-адрес, который вы используете? Опубликованная вами ошибка, по-видимому, связана с сетевой ошибкой на вашей стороне, т. е. у браузера нет доступа к Интернету.

Techrookie89 10.07.2024 13:44

Какую конфигурацию машины вы используете? А параллелизм, которого вы пытаетесь достичь?

Techrookie89 16.07.2024 08:17

Это машина с ОС Windows 11, 4-ядерным процессором с частотой 5,6 ГГц и оперативной памятью 7,9 ГБ. Я использую Python 3.10 с Selenium версии 4.18.1, pythest 8.0.2 и драйвером Chrome 2.24.1. Также я использую последнюю версию pycharm.

ajax 16.07.2024 09:06

Какое количество параллельных тестов вы выполняете? Вы пробовали это с 4 параллелизмом или более?

Techrookie89 16.07.2024 10:14

Они должны выполняться последовательно один за другим. Я ничего не делаю для параллельного запуска этих тестов

ajax 16.07.2024 10:49

Другие вопросы по теме