635 lines
32 KiB
Python
635 lines
32 KiB
Python
"""
|
||
Модуль для автоматизации работы с браузером через Selenium
|
||
"""
|
||
import time
|
||
import logging
|
||
import os
|
||
import sys
|
||
import platform
|
||
import subprocess
|
||
from pathlib import Path
|
||
from selenium import webdriver
|
||
from selenium.webdriver.common.by import By
|
||
from selenium.webdriver.firefox.options import Options as FirefoxOptions
|
||
from selenium.webdriver.firefox.service import Service as FirefoxService
|
||
from selenium.webdriver.support.ui import WebDriverWait
|
||
from selenium.webdriver.support import expected_conditions as EC
|
||
|
||
try:
|
||
from webdriver_manager.firefox import GeckoDriverManager
|
||
HAS_WEBDRIVER_MANAGER = True
|
||
except ImportError:
|
||
HAS_WEBDRIVER_MANAGER = False
|
||
|
||
|
||
class BrowserService:
|
||
"""Сервис для автоматизации работы с браузером."""
|
||
|
||
def __init__(self, download_dir, firefox_binary_path=None):
|
||
"""Инициализирует браузер с указанными параметрами."""
|
||
self.download_dir = download_dir
|
||
self.firefox_binary_path = firefox_binary_path or self._find_firefox_path()
|
||
self.driver = None
|
||
self.wait = None
|
||
self._setup_driver()
|
||
|
||
@staticmethod
|
||
def _find_firefox_path():
|
||
"""Автоматически находит путь к Firefox для текущей ОС."""
|
||
system = platform.system()
|
||
found_path = None
|
||
|
||
if system == "Windows":
|
||
# Windows пути - разные места установки Firefox
|
||
possible_paths = [
|
||
r"C:\Program Files\Mozilla Firefox\firefox.exe",
|
||
r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe",
|
||
os.path.expandvars(r"%PROGRAMFILES%\Mozilla Firefox\firefox.exe"),
|
||
os.path.expandvars(r"%PROGRAMFILES(X86)%\Mozilla Firefox\firefox.exe"),
|
||
os.path.expandvars(r"%LOCALAPPDATA%\Mozilla Firefox\firefox.exe"),
|
||
]
|
||
|
||
# Пытаемся найти через реестр (Windows)
|
||
try:
|
||
import winreg
|
||
try:
|
||
reg_path = r"SOFTWARE\Mozilla\Mozilla Firefox"
|
||
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, reg_path) as key:
|
||
install_dir = winreg.QueryValueEx(key, "InstallDir")[0]
|
||
possible_paths.insert(0, os.path.join(install_dir, "firefox.exe"))
|
||
except:
|
||
pass
|
||
except:
|
||
pass
|
||
|
||
elif system == "Darwin":
|
||
# macOS пути
|
||
possible_paths = [
|
||
"/Applications/Firefox.app/Contents/MacOS/firefox",
|
||
"/usr/local/bin/firefox",
|
||
]
|
||
|
||
else: # Linux
|
||
# Linux пути
|
||
possible_paths = [
|
||
"/snap/bin/firefox",
|
||
"/usr/bin/firefox",
|
||
"/usr/local/bin/firefox",
|
||
"/opt/firefox/firefox",
|
||
]
|
||
|
||
# Ищем Firefox в возможных местах
|
||
for path in possible_paths:
|
||
if path and os.path.exists(path):
|
||
logging.info(f"✅ Firefox найден: {path}")
|
||
return path
|
||
|
||
# Пытаемся найти через which/where команды
|
||
try:
|
||
if system == "Windows":
|
||
result = subprocess.run(["where", "firefox.exe"], capture_output=True, text=True, timeout=5)
|
||
else:
|
||
result = subprocess.run(["which", "firefox"], capture_output=True, text=True, timeout=5)
|
||
|
||
if result.returncode == 0 and result.stdout.strip():
|
||
firefox_path = result.stdout.strip().split('\n')[0]
|
||
logging.info(f"✅ Firefox найден через {system} search: {firefox_path}")
|
||
return firefox_path
|
||
except:
|
||
pass
|
||
|
||
logging.warning(f"⚠️ Firefox не найден в стандартных местах для {system}")
|
||
return None
|
||
|
||
def _setup_driver(self):
|
||
"""Настраивает и запускает веб-драйвер."""
|
||
try:
|
||
firefox_options = FirefoxOptions()
|
||
|
||
# ===== ОПЦИИ ЗАГРУЗКИ =====
|
||
firefox_options.set_preference("browser.download.folderList", 2)
|
||
firefox_options.set_preference("browser.download.dir", self.download_dir)
|
||
firefox_options.set_preference("browser.download.useDownloadDir", True)
|
||
firefox_options.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream")
|
||
|
||
# ===== ОТКЛЮЧАЕМ HEADLESS (окно ДОЛЖНО быть видимым) =====
|
||
# Убедимся, что headless режим НЕ установлен
|
||
# (это заставит браузер открыться в нормальном режиме)
|
||
|
||
# Устанавливаем путь к Firefox только если он найден
|
||
if self.firefox_binary_path:
|
||
logging.info(f" ✅ Используется Firefox: {self.firefox_binary_path}")
|
||
firefox_options.binary_location = self.firefox_binary_path
|
||
else:
|
||
logging.info(" ⚠️ Firefox путь не определен, буду использовать системный поиск...")
|
||
|
||
logging.info("🚀 Запуск Firefox драйвера с ВИДИМЫМ окном...")
|
||
|
||
# Попытка 1: Использовать webdriver-manager (самый надежный способ)
|
||
if HAS_WEBDRIVER_MANAGER:
|
||
try:
|
||
logging.info(" Попытка 1: webdriver-manager...")
|
||
service = FirefoxService(GeckoDriverManager().install())
|
||
self.driver = webdriver.Firefox(service=service, options=firefox_options)
|
||
except Exception as e:
|
||
logging.warning(f" webdriver-manager не сработал: {e}")
|
||
self.driver = None
|
||
|
||
# Попытка 2: Прямой запуск (Selenium сам найдет geckodriver в PATH)
|
||
if not self.driver:
|
||
try:
|
||
logging.info(" Попытка 2: Прямой запуск (Selenium ищет geckodriver в PATH)...")
|
||
self.driver = webdriver.Firefox(options=firefox_options)
|
||
except Exception as e:
|
||
logging.warning(f" Прямой запуск не сработал: {e}")
|
||
self.driver = None
|
||
|
||
if not self.driver:
|
||
raise Exception("Не удалось инициализировать Firefox драйвер ни одним способом")
|
||
|
||
# Даем браузеру время открыться
|
||
time.sleep(2)
|
||
|
||
self.driver.set_page_load_timeout(300)
|
||
self.wait = WebDriverWait(self.driver, 10)
|
||
logging.info("✅ Браузер открыт и готов к работе.")
|
||
|
||
except Exception as e:
|
||
logging.error(f"❌ Ошибка при настройке драйвера: {e}")
|
||
logging.error(f"\n Решения:")
|
||
logging.error(f" 1. Убедитесь, что Firefox установлен")
|
||
logging.error(f" 2. На Windows: установите Firefox с https://mozilla.org/firefox")
|
||
|
||
raise
|
||
|
||
def open_page(self, url):
|
||
"""Открывает страницу по URL."""
|
||
try:
|
||
logging.info(f"🌐 Переход на страницу: {url}")
|
||
self.driver.get(url)
|
||
logging.info("✅ Страница загружена.")
|
||
time.sleep(2)
|
||
except Exception as e:
|
||
logging.error(f"❌ Ошибка при открытии страницы: {e}")
|
||
raise
|
||
|
||
def wait_page_load(self, seconds=5):
|
||
"""Ждет загрузки страницы."""
|
||
logging.info(f"⏳ Ожидание {seconds} сек...")
|
||
time.sleep(seconds)
|
||
|
||
def click_button_by_text(self, button_text, exact_match=False, timeout=10):
|
||
"""
|
||
Ищет и нажимает кнопку по тексту в различных формах.
|
||
|
||
Args:
|
||
button_text (str): Текст кнопки для поиска
|
||
exact_match (bool): Точное совпадение или частичное
|
||
timeout (int): Время ожидания в секундах
|
||
|
||
Returns:
|
||
bool: True если кнопка найдена и нажата, False иначе
|
||
"""
|
||
try:
|
||
logging.info(f"🔎 Ищу кнопку: '{button_text}'")
|
||
button = self._find_button_by_text(button_text, exact_match, timeout)
|
||
|
||
if button:
|
||
logging.info(f"✅ Кнопка найдена, нажимаю...")
|
||
# Скролл к кнопке если нужно
|
||
self.driver.execute_script("arguments[0].scrollIntoView(true);", button)
|
||
time.sleep(0.5)
|
||
button.click()
|
||
logging.info(f"✅ Кнопка '{button_text}' нажата успешно.")
|
||
time.sleep(1)
|
||
return True
|
||
else:
|
||
logging.warning(f"⚠️ Кнопка '{button_text}' не найдена.")
|
||
return False
|
||
except Exception as e:
|
||
logging.error(f"❌ Ошибка при нажатии кнопки '{button_text}': {e}")
|
||
return False
|
||
|
||
def _find_button_by_text(self, button_text, exact_match=False, timeout=10):
|
||
"""
|
||
Находит кнопку по тексту различными способами.
|
||
|
||
Args:
|
||
button_text (str): Текст для поиска
|
||
exact_match (bool): Точное совпадение
|
||
timeout (int): Время ожидания
|
||
|
||
Returns:
|
||
WebElement или None
|
||
"""
|
||
button_text_lower = button_text.lower().strip()
|
||
end_time = time.time() + timeout
|
||
|
||
while time.time() < end_time:
|
||
# Вариант 1: Поиск по тегу <button>
|
||
buttons = self.driver.find_elements(By.TAG_NAME, 'button')
|
||
for button in buttons:
|
||
if self._text_matches(button.text, button_text_lower, exact_match):
|
||
if self._is_clickable(button):
|
||
return button
|
||
|
||
# Вариант 2: Поиск по роли button
|
||
buttons = self.driver.find_elements(By.CSS_SELECTOR, '[role="button"]')
|
||
for button in buttons:
|
||
if self._text_matches(button.text, button_text_lower, exact_match):
|
||
if self._is_clickable(button):
|
||
return button
|
||
|
||
# Вариант 3: Поиск по содержимому текста (для div/span с текстом)
|
||
try:
|
||
elements = self.driver.find_elements(By.XPATH,
|
||
f"//*[contains(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '{button_text_lower}')][not(self::script or self::style)]")
|
||
for elem in elements:
|
||
if self._is_clickable(elem):
|
||
return elem
|
||
except:
|
||
pass
|
||
|
||
# Вариант 4: Поиск по атрибутам (aria-label, title, etc)
|
||
for attr in ['aria-label', 'title', 'data-text', 'placeholder', 'value']:
|
||
try:
|
||
elements = self.driver.find_elements(By.XPATH, f"//*[@{attr}]")
|
||
for elem in elements:
|
||
attr_value = elem.get_attribute(attr)
|
||
if attr_value and self._text_matches(attr_value, button_text_lower, exact_match):
|
||
if self._is_clickable(elem):
|
||
return elem
|
||
except:
|
||
pass
|
||
|
||
# Вариант 5: Поиск label и возврат его родителя (для div+label случаев)
|
||
try:
|
||
labels = self.driver.find_elements(By.TAG_NAME, 'label')
|
||
for label in labels:
|
||
if self._text_matches(label.text, button_text_lower, exact_match):
|
||
if self._is_clickable(label):
|
||
return label
|
||
# Если сам label не кликабелен, ищем кликабельного родителя
|
||
try:
|
||
parent = label.find_element(By.XPATH, "ancestor::div[@onclick] | ancestor::div[@role='button'] | ancestor::button")
|
||
if self._is_clickable(parent):
|
||
return parent
|
||
except:
|
||
pass
|
||
except:
|
||
pass
|
||
|
||
# Вариант 6: <input> элементы (button, submit, reset)
|
||
try:
|
||
inputs = self.driver.find_elements(By.CSS_SELECTOR, 'input[type="button"], input[type="submit"], input[type="reset"]')
|
||
for inp in inputs:
|
||
value = inp.get_attribute('value')
|
||
if value and self._text_matches(value, button_text_lower, exact_match):
|
||
if self._is_clickable(inp):
|
||
return inp
|
||
except:
|
||
pass
|
||
|
||
time.sleep(0.5)
|
||
|
||
return None
|
||
|
||
def _text_matches(self, element_text, search_text, exact_match):
|
||
"""Проверяет совпадение текста."""
|
||
element_text_clean = element_text.lower().strip()
|
||
if exact_match:
|
||
return element_text_clean == search_text
|
||
else:
|
||
return search_text in element_text_clean
|
||
|
||
def _is_clickable(self, element):
|
||
"""Проверяет, можно ли кликнуть на элемент."""
|
||
try:
|
||
return element.is_displayed() and element.is_enabled()
|
||
except:
|
||
return False
|
||
|
||
def upload_file(self, file_path, selector='input[type="file"]', by=By.CSS_SELECTOR):
|
||
"""Загружает файл через input."""
|
||
try:
|
||
logging.info(f"📤 Загружаю файл: {Path(file_path).name}")
|
||
|
||
# Попытка кликнуть на область загрузки
|
||
try:
|
||
drop_area = self.driver.find_element(By.CSS_SELECTOR, '.upload-area, .drop-zone, [data-upload]')
|
||
drop_area.click()
|
||
time.sleep(1)
|
||
logging.info(" Найдена область загрузки")
|
||
except:
|
||
pass
|
||
|
||
file_input = self.wait.until(EC.presence_of_element_located((by, selector)))
|
||
file_input.send_keys(file_path)
|
||
logging.info(f"✅ Файл загружен: {Path(file_path).name}")
|
||
time.sleep(1)
|
||
return True
|
||
except Exception as e:
|
||
logging.error(f"❌ Ошибка при загрузке файла {Path(file_path).name}: {e}")
|
||
return False
|
||
|
||
def close(self):
|
||
"""Закрывает браузер."""
|
||
if self.driver:
|
||
self.driver.quit()
|
||
logging.info("🛑 Браузер закрыт.")
|
||
|
||
def get_page_title(self):
|
||
"""Возвращает заголовок страницы."""
|
||
return self.driver.title
|
||
|
||
def open_new_tab(self, url):
|
||
"""Открывает новую вкладку с заданным URL."""
|
||
try:
|
||
logging.info(f"📑 Открываю новую вкладку: {url}")
|
||
# Открываем новую вкладку
|
||
self.driver.execute_script("window.open(arguments[0], '_blank');", url)
|
||
# Переключаемся на последнюю открытую вкладку
|
||
self.driver.switch_to.window(self.driver.window_handles[-1])
|
||
time.sleep(2)
|
||
logging.info("✅ Новая вкладка открыта и активна.")
|
||
return True
|
||
except Exception as e:
|
||
logging.error(f"❌ Ошибка при открытии новой вкладки: {e}")
|
||
return False
|
||
|
||
def close_current_tab(self):
|
||
"""Закрывает текущую вкладку и переходит на предыдущую."""
|
||
try:
|
||
logging.info("📑 Метод close_current_tab вызван, но вкладки не будут закрываться.")
|
||
# Удаляем логику закрытия вкладок
|
||
return True
|
||
except Exception as e:
|
||
logging.error(f"❌ Ошибка при попытке закрыть вкладку: {e}")
|
||
return False
|
||
|
||
def get_tabs_count(self):
|
||
"""Возвращает количество открытых вкладок."""
|
||
return len(self.driver.window_handles)
|
||
|
||
def select_tab_by_name(self, tab_name):
|
||
"""Выбирает вкладку на странице по её названию."""
|
||
try:
|
||
logging.info(f"🔍 Поиск вкладки с названием: {tab_name}")
|
||
# Ищем элемент вкладки по тексту
|
||
tab_element = self.wait.until(
|
||
EC.presence_of_element_located((By.XPATH, f"//a[text()='{tab_name}']"))
|
||
)
|
||
tab_element.click()
|
||
logging.info(f"✅ Вкладка '{tab_name}' выбрана.")
|
||
return True
|
||
except Exception as e:
|
||
logging.error(f"❌ Ошибка при выборе вкладки '{tab_name}': {e}")
|
||
return False
|
||
|
||
def select_tab_by_text_or_name(self, target_text, timeout=30):
|
||
"""Ищет и выбирает вкладку по тексту или названию, делая повторные попытки.
|
||
|
||
Args:
|
||
target_text (str): Текст или название вкладки для поиска.
|
||
timeout (int): Общее время ожидания в секундах.
|
||
|
||
Returns:
|
||
bool: True, если вкладка найдена и выбрана, иначе False.
|
||
"""
|
||
end_time = time.time() + timeout
|
||
while time.time() < end_time:
|
||
try:
|
||
logging.info(f"🔍 Поиск вкладки с текстом или названием: {target_text}")
|
||
# Ищем элемент вкладки по тексту
|
||
tab_element = self.wait.until(
|
||
EC.presence_of_element_located((By.XPATH, f"//*[text()='{target_text}']"))
|
||
)
|
||
self.driver.execute_script("arguments[0].scrollIntoView(true);", tab_element)
|
||
time.sleep(0.5)
|
||
tab_element.click()
|
||
logging.info(f"✅ Вкладка с текстом '{target_text}' выбрана.")
|
||
return True
|
||
except Exception as e:
|
||
logging.warning(f"⚠️ Вкладка с текстом '{target_text}' не найдена, повторная попытка...")
|
||
time.sleep(1) # Ждем перед повторной попыткой
|
||
logging.error(f"❌ Не удалось найти вкладку с текстом '{target_text}' за {timeout} секунд.")
|
||
return False
|
||
|
||
def set_tabs(self, tabs):
|
||
"""Устанавливает массив с названиями вкладок."""
|
||
self.tabs = tabs
|
||
|
||
def select_tab_from_list(self, tab_index):
|
||
"""Выбирает вкладку из массива по индексу.
|
||
|
||
Args:
|
||
tab_index (int): Индекс вкладки в массиве.
|
||
|
||
Returns:
|
||
bool: True, если вкладка выбрана успешно, иначе False.
|
||
"""
|
||
try:
|
||
if not hasattr(self, 'tabs') or not self.tabs:
|
||
logging.error("❌ Массив вкладок не установлен или пуст.")
|
||
return False
|
||
|
||
if tab_index < 0 or tab_index >= len(self.tabs):
|
||
logging.error("❌ Индекс вкладки выходит за пределы массива.")
|
||
return False
|
||
|
||
tab_name = self.tabs[tab_index]
|
||
logging.info(f"🔍 Выбор вкладки с индексом {tab_index}: {tab_name}")
|
||
return self.select_tab_by_name(tab_name)
|
||
except Exception as e:
|
||
logging.error(f"❌ Ошибка при выборе вкладки по индексу {tab_index}: {e}")
|
||
return False
|
||
|
||
def select_tab_by_multiple_methods(self, tab_text, timeout=15):
|
||
"""Пытается выбрать вкладку на странице перебором всех элементов.
|
||
|
||
Args:
|
||
tab_text (str): Текст вкладки для поиска
|
||
timeout (int): Общее время ожидания в секундах
|
||
|
||
Returns:
|
||
bool: True, если вкладка найдена и выбрана, иначе False
|
||
"""
|
||
end_time = time.time() + timeout
|
||
attempt = 0
|
||
|
||
# Нормализуем текст для поиска
|
||
tab_text_normalized = tab_text.strip()
|
||
tab_text_lower = tab_text_normalized.lower()
|
||
|
||
logging.info(f"🔍 Начинаю поиск вкладки: '{tab_text}'")
|
||
|
||
while time.time() < end_time:
|
||
attempt += 1
|
||
logging.info(f" Попытка {attempt}: перебираю все элементы на странице...")
|
||
|
||
try:
|
||
# Ищем и кликаем через JavaScript в одном скрипте
|
||
script = """
|
||
var searchText = arguments[0].toLowerCase();
|
||
var allElements = document.querySelectorAll('*');
|
||
var foundElements = [];
|
||
|
||
console.log('Ищу элемент с текстом:', searchText);
|
||
|
||
for (var i = 0; i < allElements.length; i++) {
|
||
var el = allElements[i];
|
||
var elementText = el.textContent || el.innerText || '';
|
||
var visibleText = elementText.trim();
|
||
|
||
// Проверяем только прямой текст элемента (не включая детей)
|
||
var directText = '';
|
||
for (var j = 0; j < el.childNodes.length; j++) {
|
||
if (el.childNodes[j].nodeType === 3) { // TEXT_NODE
|
||
directText += el.childNodes[j].nodeValue;
|
||
}
|
||
}
|
||
directText = directText.trim();
|
||
|
||
// Совпадение по прямому тексту или полному тексту
|
||
var match = false;
|
||
if (directText.toLowerCase() === searchText ||
|
||
visibleText.toLowerCase() === searchText) {
|
||
match = true;
|
||
} else if (directText.toLowerCase().includes(searchText) ||
|
||
visibleText.toLowerCase().includes(searchText)) {
|
||
match = true;
|
||
}
|
||
|
||
if (match) {
|
||
// Проверяем видимость элемента
|
||
var rect = el.getBoundingClientRect();
|
||
var style = window.getComputedStyle(el);
|
||
|
||
if (rect.width > 0 && rect.height > 0 &&
|
||
style.display !== 'none' &&
|
||
style.visibility !== 'hidden' &&
|
||
style.opacity !== '0') {
|
||
foundElements.push({
|
||
element: el,
|
||
tag: el.tagName.toLowerCase(),
|
||
text: visibleText.substring(0, 50),
|
||
directText: directText.substring(0, 50)
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('Найдено элементов:', foundElements.length);
|
||
|
||
// Пытаемся кликнуть на каждый найденный элемент
|
||
for (var k = 0; k < foundElements.length; k++) {
|
||
var item = foundElements[k];
|
||
var elem = item.element;
|
||
|
||
try {
|
||
// Прокручиваем к элементу (без анимации для скорости)
|
||
elem.scrollIntoView({block: 'center', behavior: 'auto'});
|
||
|
||
// Небольшая пауза для стабильности (выполняется синхронно)
|
||
var startWait = Date.now();
|
||
while (Date.now() - startWait < 200) {
|
||
// Ждем 200мс
|
||
}
|
||
|
||
console.log('Пробую кликнуть элемент:', item.tag, item.text);
|
||
|
||
// Сначала находим САМЫЙ кликабельный элемент в иерархии
|
||
var targetToClick = elem;
|
||
|
||
// Ищем родителя который является ссылкой или кнопкой
|
||
var clickableParent = elem.closest('a, button, [role="tab"], [role="button"], li');
|
||
if (clickableParent) {
|
||
console.log('Найден кликабельный родитель:', clickableParent.tagName, clickableParent.className);
|
||
targetToClick = clickableParent;
|
||
}
|
||
|
||
// Теперь пробуем все методы клика на найденном элементе
|
||
var methods = [
|
||
{
|
||
name: 'direct click',
|
||
fn: function(el) { el.click(); }
|
||
},
|
||
{
|
||
name: 'mousedown+mouseup',
|
||
fn: function(el) {
|
||
el.dispatchEvent(new MouseEvent('mousedown', {bubbles: true, cancelable: true}));
|
||
el.dispatchEvent(new MouseEvent('mouseup', {bubbles: true, cancelable: true}));
|
||
el.dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true}));
|
||
}
|
||
},
|
||
{
|
||
name: 'dispatchEvent click',
|
||
fn: function(el) {
|
||
el.dispatchEvent(new MouseEvent('click', {
|
||
view: window,
|
||
bubbles: true,
|
||
cancelable: true,
|
||
clientX: el.getBoundingClientRect().left + 5,
|
||
clientY: el.getBoundingClientRect().top + 5
|
||
}));
|
||
}
|
||
},
|
||
{
|
||
name: 'focus + click',
|
||
fn: function(el) {
|
||
if (el.focus) el.focus();
|
||
el.click();
|
||
}
|
||
}
|
||
];
|
||
|
||
// Пробуем каждый метод
|
||
for (var m = 0; m < methods.length; m++) {
|
||
try {
|
||
console.log('Пробую метод:', methods[m].name, 'на', targetToClick.tagName);
|
||
methods[m].fn(targetToClick);
|
||
console.log('✅ Успешно кликнул методом:', methods[m].name);
|
||
return {success: true, tag: targetToClick.tagName, text: item.text, method: methods[m].name};
|
||
} catch(err) {
|
||
console.log('✗ Метод', methods[m].name, 'не сработал:', err.message);
|
||
}
|
||
}
|
||
|
||
} catch(err) {
|
||
console.log('Общая ошибка при обработке элемента:', err.message);
|
||
}
|
||
}
|
||
|
||
return {success: false, found: foundElements.length, message: 'Элементы найдены, но ни один метод клика не сработал'};
|
||
"""
|
||
|
||
result = self.driver.execute_script(script, tab_text_lower)
|
||
|
||
if result and isinstance(result, dict):
|
||
if result.get('success'):
|
||
method = result.get('method', 'unknown')
|
||
tag = result.get('tag', 'unknown')
|
||
text = result.get('text', '')
|
||
logging.info(f" ✅ УСПЕХ! Метод: {method}, тег: <{tag}>, текст: '{text}'")
|
||
time.sleep(0.5)
|
||
return True
|
||
else:
|
||
found_count = result.get('found', 0)
|
||
message = result.get('message', 'Не найдено')
|
||
if found_count > 0:
|
||
logging.warning(f" ✗ Попытка {attempt}: найдено {found_count} элементов, но кликнуть не удалось")
|
||
else:
|
||
logging.warning(f" ✗ Попытка {attempt}: {message}")
|
||
else:
|
||
logging.warning(f" ✗ Попытка {attempt}: некорректный результат от JavaScript")
|
||
|
||
time.sleep(1)
|
||
|
||
except Exception as e:
|
||
logging.warning(f" ✗ Ошибка при попытке {attempt}: {e}")
|
||
import traceback
|
||
logging.debug(traceback.format_exc())
|
||
time.sleep(1)
|
||
|
||
logging.error(f"❌ Не удалось найти и кликнуть на вкладку '{tab_text}' за {timeout} секунд после {attempt} попыток")
|
||
return False |