This commit is contained in:
unknown
2026-02-12 00:19:38 +05:00
parent be7aadf218
commit c348a19052
5 changed files with 391 additions and 70 deletions

View File

@@ -5,5 +5,6 @@ description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.14" requires-python = ">=3.14"
dependencies = [ dependencies = [
"pyinstaller>=6.18.0",
"selenium", "selenium",
] ]

View File

@@ -443,3 +443,193 @@ class BrowserService:
except Exception as e: except Exception as e:
logging.error(f"❌ Ошибка при выборе вкладки по индексу {tab_index}: {e}") logging.error(f"❌ Ошибка при выборе вкладки по индексу {tab_index}: {e}")
return False 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

View File

@@ -35,13 +35,14 @@ class FileProcessorService:
logging.info("🚀 ИНИЦИАЛИЗАЦИЯ ПРОЦЕССОРА ФАЙЛОВ") logging.info("🚀 ИНИЦИАЛИЗАЦИЯ ПРОЦЕССОРА ФАЙЛОВ")
logging.info("=" * 70) logging.info("=" * 70)
def start_processing(self, upload_button_texts=None, process_button_texts=None): def start_processing(self, upload_button_texts=None, process_button_texts=None, selected_tab=None):
""" """
Запускает процесс поиска и загрузки файлов. Запускает процесс поиска и загрузки файлов.
Args: Args:
upload_button_texts (list): Варианты текста кнопки загрузки файла upload_button_texts (list): Варианты текста кнопки загрузки файла
process_button_texts (list): Варианты текста кнопки обработки process_button_texts (list): Варианты текста кнопки обработки
selected_tab (str): Название вкладки на странице для выбора
Returns: Returns:
dict: Результаты обработки dict: Результаты обработки
@@ -92,12 +93,20 @@ class FileProcessorService:
logging.info("=" * 70) logging.info("=" * 70)
try: try:
# Открываем новую вкладку для каждого файла (кроме первого) # Открываем новую вкладку для КАЖДОГО файла (включая первый)
if idx > 1: logging.info(f" Открываю новую вкладку в браузере для файла...")
logging.info(f" Открываю новую вкладку для файла...") if not self.browser.open_new_tab(self.url):
if not self.browser.open_new_tab(self.url): logging.warning(f"⚠️ Не удалось открыть новую вкладку")
logging.warning(f"⚠️ Не удалось открыть новую вкладку") self.browser.wait_page_load(3)
self.browser.wait_page_load(3)
# Выбор вкладки на странице (если указана)
if selected_tab:
logging.info(f"🔍 Выбираю вкладку на странице: '{selected_tab}'")
if self.browser.select_tab_by_multiple_methods(selected_tab, timeout=15):
logging.info(f"✅ Вкладка '{selected_tab}' успешно выбрана")
self.browser.wait_page_load(2)
else:
logging.warning(f"⚠️ Не удалось выбрать вкладку '{selected_tab}'")
# Поиск и клик по кнопке загрузки # Поиск и клик по кнопке загрузки
upload_button_found = False upload_button_found = False

139
ui.py
View File

@@ -147,6 +147,49 @@ class UIManager:
) )
process_label.pack(anchor="w", pady=(0, 15)) process_label.pack(anchor="w", pady=(0, 15))
# ===== ПОЛЕ 4: ВЫБОР ВКЛАДКИ НА СТРАНИЦЕ =====
tab_select_label = tk.Label(
main_frame,
text="📑 Вкладка на странице для обработки:",
font=self.font_label,
bg="#2C3E50",
fg="#ECF0F1"
)
tab_select_label.pack(anchor="w", pady=(10, 5))
# Фрейм для списка вкладок и выпадающего списка
tab_config_frame = tk.Frame(main_frame, bg="#2C3E50")
tab_config_frame.pack(fill=tk.X, pady=(0, 5))
# Поле для ввода доступных вкладок
tab_list_label = tk.Label(
main_frame,
text="Доступные вкладки (через запятую):",
font=("Arial", 9),
bg="#2C3E50",
fg="#BDC3C7"
)
tab_list_label.pack(anchor="w", pady=(0, 2))
self.tabs_list_entry = tk.Entry(main_frame, font=self.font_label)
self.tabs_list_entry.pack(anchor="w", fill=tk.X, pady=(0, 5))
self.tabs_list_entry.insert(0, "Внесение ФС, Корректировка ОН, Внесение КС")
self.tabs_list_entry.bind("<KeyRelease>", self._update_tab_selector)
# Выпадающий список для выбора вкладки
tab_choice_label = tk.Label(
main_frame,
text="Выберите вкладку:",
font=("Arial", 9),
bg="#2C3E50",
fg="#BDC3C7"
)
tab_choice_label.pack(anchor="w", pady=(5, 2))
self.tab_choice_combo = ttk.Combobox(main_frame, state="readonly", font=self.font_label)
self.tab_choice_combo.pack(anchor="w", fill=tk.X, pady=(0, 15))
self._update_tab_selector() # Инициализация списка вкладок
# ===== ЛОГИРОВАНИЕ ===== # ===== ЛОГИРОВАНИЕ =====
log_label = tk.Label( log_label = tk.Label(
main_frame, main_frame,
@@ -206,18 +249,27 @@ class UIManager:
# Перенаправление логов в текстовое поле # Перенаправление логов в текстовое поле
self._setup_log_handler() self._setup_log_handler()
# ===== ВЫБОР ВКЛАДКИ ===== def _update_tab_selector(self, event=None):
tabs_label = tk.Label( """Обновляет выпадающий список вкладок на основе введенного текста."""
main_frame, tabs_text = self.tabs_list_entry.get().strip()
text="📑 Выбор вкладки браузера:", if tabs_text:
font=self.font_label, tabs = [t.strip() for t in tabs_text.split(",") if t.strip()]
bg="#2C3E50", self.tab_choice_combo["values"] = tabs
fg="#ECF0F1" if tabs and self.tab_choice_combo.current() == -1:
) self.tab_choice_combo.current(0) # Выбираем первую по умолчанию
tabs_label.pack(anchor="w", pady=(10, 5)) else:
self.tab_choice_combo["values"] = []
self.tab_selector = TabSelectorApp(main_frame, BrowserService(download_dir="./downloads")) def _update_tab_selector(self, event=None):
self.tab_selector.pack(fill=tk.X, pady=(0, 15)) """Обновляет выпадающий список вкладок на основе введенного текста."""
tabs_text = self.tabs_list_entry.get().strip()
if tabs_text:
tabs = [t.strip() for t in tabs_text.split(",") if t.strip()]
self.tab_choice_combo["values"] = tabs
if tabs and self.tab_choice_combo.current() == -1:
self.tab_choice_combo.current(0) # Выбираем первую по умолчанию
else:
self.tab_choice_combo["values"] = []
def _setup_log_handler(self): def _setup_log_handler(self):
"""Настраивает обработчик логов в текстовое поле.""" """Настраивает обработчик логов в текстовое поле."""
@@ -276,18 +328,21 @@ class UIManager:
upload_texts = [t.strip() for t in self.upload_buttons_entry.get().split(",")] upload_texts = [t.strip() for t in self.upload_buttons_entry.get().split(",")]
process_texts = [t.strip() for t in self.process_buttons_entry.get().split(",")] process_texts = [t.strip() for t in self.process_buttons_entry.get().split(",")]
# Получаем выбранную вкладку
selected_tab = self.tab_choice_combo.get().strip() if self.tab_choice_combo.get() else None
# Отключаем кнопку и запускаем обработку в потоке # Отключаем кнопку и запускаем обработку в потоке
self.processing = True self.processing = True
self.start_button.config(state="disabled", bg="#95A5A6") self.start_button.config(state="disabled", bg="#95A5A6")
thread = threading.Thread( thread = threading.Thread(
target=self._processing_thread, target=self._processing_thread,
args=(directory, url, upload_texts, process_texts), args=(directory, url, upload_texts, process_texts, selected_tab),
daemon=True daemon=True
) )
thread.start() thread.start()
def _processing_thread(self, directory, url, upload_texts, process_texts): def _processing_thread(self, directory, url, upload_texts, process_texts, selected_tab=None):
"""Потоковая функция обработки.""" """Потоковая функция обработки."""
try: try:
logging.info("\n" + "="*70) logging.info("\n" + "="*70)
@@ -301,17 +356,19 @@ class UIManager:
# Создаем процессор # Создаем процессор
processor = FileProcessorService(directory, url, download_dir) processor = FileProcessorService(directory, url, download_dir)
# Проверка вкладок перед началом загрузки # Выбор вкладки на странице (если указана)
browser_service = BrowserService(download_dir="./downloads") if selected_tab:
browser_service.set_tabs(["Вкладка 1", "Вкладка 2", "Вкладка 3"]) logging.info(f"🔍 Попытка выбрать вкладку на странице: '{selected_tab}'")
browser_service = processor.browser
# Пример: выбор вкладки по индексу if browser_service and browser_service.select_tab_by_multiple_methods(selected_tab, timeout=15):
if not browser_service.select_tab_from_list(0): logging.info(f"✅ Вкладка '{selected_tab}' успешно выбрана")
logging.error("Не удалось выбрать вкладку. Проверьте массив вкладок.") else:
return logging.warning(f"⚠️ Не удалось выбрать вкладку '{selected_tab}', продолжаю без выбора вкладки")
else:
logging.info(" Вкладка не указана, пропускаю выбор вкладки")
# Запускаем обработку # Запускаем обработку
results = processor.start_processing(upload_texts, process_texts) results = processor.start_processing(upload_texts, process_texts, selected_tab)
# Показываем результаты # Показываем результаты
self._show_results(results) self._show_results(results)
@@ -357,44 +414,6 @@ class UIManager:
messagebox.showerror("Ошибка", "Буфер обмена пуст или недоступен.") messagebox.showerror("Ошибка", "Буфер обмена пуст или недоступен.")
class TabSelectorApp:
def __init__(self, parent, browser_service):
self.browser_service = browser_service
self.frame = tk.Frame(parent, bg="#2C3E50")
# Выпадающий список
self.combo = ttk.Combobox(self.frame, state="readonly")
self.combo.pack(pady=10)
# Кнопка для выбора вкладки
self.select_button = tk.Button(self.frame, text="Выбрать вкладку", command=self.select_tab)
self.select_button.pack(pady=10)
def pack(self, **kwargs):
"""Упрощает упаковку фрейма."""
self.frame.pack(**kwargs)
def set_tabs(self, tabs):
"""Устанавливает массив вкладок и обновляет выпадающий список."""
self.tabs = tabs
self.combo["values"] = self.tabs
if self.tabs:
self.combo.current(0) # Устанавливаем первый элемент по умолчанию
def select_tab(self):
"""Выбирает вкладку из выпадающего списка."""
selected_index = self.combo.current()
if selected_index == -1:
print("❌ Вкладка не выбрана.")
return
success = self.browser_service.select_tab_from_list(selected_index)
if success:
print(f"✅ Вкладка '{self.tabs[selected_index]}' выбрана.")
else:
print(f"Не удалось выбрать вкладку '{self.tabs[selected_index]}'." )
def main(): def main():
"""Главная функция.""" """Главная функция."""
root = tk.Tk() root = tk.Tk()

104
uv.lock generated
View File

@@ -2,6 +2,15 @@ version = 1
revision = 3 revision = 3
requires-python = ">=3.14" requires-python = ">=3.14"
[[package]]
name = "altgraph"
version = "0.17.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" },
]
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "25.4.0" version = "25.4.0"
@@ -55,6 +64,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
] ]
[[package]]
name = "macholib"
version = "1.16.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altgraph" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" },
]
[[package]] [[package]]
name = "outcome" name = "outcome"
version = "1.3.0.post0" version = "1.3.0.post0"
@@ -67,6 +88,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" }, { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" },
] ]
[[package]]
name = "packaging"
version = "26.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
]
[[package]]
name = "pefile"
version = "2024.8.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" },
]
[[package]] [[package]]
name = "pycparser" name = "pycparser"
version = "2.23" version = "2.23"
@@ -76,6 +115,47 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
] ]
[[package]]
name = "pyinstaller"
version = "6.18.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altgraph" },
{ name = "macholib", marker = "sys_platform == 'darwin'" },
{ name = "packaging" },
{ name = "pefile", marker = "sys_platform == 'win32'" },
{ name = "pyinstaller-hooks-contrib" },
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9f/b8/0fe3359920b0a4e7008e0e93ff383003763e3eee3eb31a07c52868722960/pyinstaller-6.18.0.tar.gz", hash = "sha256:cdc507542783511cad4856fce582fdc37e9f29665ca596889c663c83ec8c6ec9", size = 4034976, upload-time = "2026-01-13T03:13:23.886Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/73/e6/51b0146a1a3eec619e58f5d69fb4e3d0f65a31cbddbeef557c9bb83eeed9/pyinstaller-6.18.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:cb7aa5a71bfa7c0af17a4a4e21855663c89e4bd7c40f1d337c8370636d8847c3", size = 1040056, upload-time = "2026-01-13T03:12:15.397Z" },
{ url = "https://files.pythonhosted.org/packages/4c/9c/a3634c0ec8e1ed31b373b548848b5c0b39b56edc191cf737e697d484ec23/pyinstaller-6.18.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:07785459b3bf8a48889eac0b4d0667ade84aef8930ce030bc7cbb32f41283b33", size = 734971, upload-time = "2026-01-13T03:12:20.912Z" },
{ url = "https://files.pythonhosted.org/packages/2c/04/6756442078ccfcd552ccce636be1574035e62f827ffa1f5d8a0382682546/pyinstaller-6.18.0-py3-none-manylinux2014_i686.whl", hash = "sha256:f998675b7ccb2dabbb1dc2d6f18af61d55428ad6d38e6c4d700417411b697d37", size = 746637, upload-time = "2026-01-13T03:12:29.302Z" },
{ url = "https://files.pythonhosted.org/packages/54/39/fbc56519000cdbf450f472692a7b9b55d42077ce8529f1be631db7b75a36/pyinstaller-6.18.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:779817a0cf69604cddcdb5be1fd4959dc2ce048d6355c73e5da97884df2f3387", size = 744343, upload-time = "2026-01-13T03:12:33.369Z" },
{ url = "https://files.pythonhosted.org/packages/36/f2/50887badf282fee776e83d1e4feab74c026f50a1ea16e109ed939e32aa28/pyinstaller-6.18.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:31b5d109f8405be0b7cddcede43e7b074792bc9a5bbd54ec000a3e779183c2af", size = 741084, upload-time = "2026-01-13T03:12:37.528Z" },
{ url = "https://files.pythonhosted.org/packages/1c/08/3a1419183e4713ef77d912ecbdd6ef858689ed9deb34d547133f724ca745/pyinstaller-6.18.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4328c9837f1aef4fe1a127d4ff1b09a12ce53c827ce87c94117628b0e1fd098b", size = 740943, upload-time = "2026-01-13T03:12:41.589Z" },
{ url = "https://files.pythonhosted.org/packages/c2/47/309305e36d116f1434b42d91c420ff951fa79b2c398bbd59930c830450be/pyinstaller-6.18.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:3638fc81eb948e5e5eab1d4ad8f216e3fec6d4a350648304f0adb227b746ee5e", size = 740107, upload-time = "2026-01-13T03:12:45.694Z" },
{ url = "https://files.pythonhosted.org/packages/83/0f/a59a95cd1df59ddbc9e74d5a663387551333bcf19a5dd3086f5c81a2e83c/pyinstaller-6.18.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe59da34269e637f97fd3c43024f764586fc319141d245ff1a2e9af1036aa3", size = 739843, upload-time = "2026-01-13T03:12:49.728Z" },
{ url = "https://files.pythonhosted.org/packages/9a/09/e7a870e7205cdbd2f8785010a5d3fe48a9df2591156ee34a8b29b774fa14/pyinstaller-6.18.0-py3-none-win32.whl", hash = "sha256:496205e4fa92ec944f9696eb597962a83aef4d4c3479abfab83d730e1edf016b", size = 1323811, upload-time = "2026-01-13T03:12:55.717Z" },
{ url = "https://files.pythonhosted.org/packages/fb/d5/48eef2002b6d3937ceac2717fe17e9ca3a43a4c9826bafee367dfc75ba85/pyinstaller-6.18.0-py3-none-win_amd64.whl", hash = "sha256:976fabd90ecfbda47571c87055ad73413ec615ff7dea35e12a4304174de78de9", size = 1384389, upload-time = "2026-01-13T03:13:01.993Z" },
{ url = "https://files.pythonhosted.org/packages/1b/8d/1a88e6e94107de3ea1c842fd59c3aa132d344ad8e52ea458ffa9a748726e/pyinstaller-6.18.0-py3-none-win_arm64.whl", hash = "sha256:dba4b70e3c9ba09aab51152c72a08e58a751851548f77ad35944d32a300c8381", size = 1324869, upload-time = "2026-01-13T03:13:08.192Z" },
]
[[package]]
name = "pyinstaller-hooks-contrib"
version = "2026.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/31/8f/8052ff65067697ee80fde45b9731842e160751c41ac5690ba232c22030e8/pyinstaller_hooks_contrib-2026.0.tar.gz", hash = "sha256:0120893de491a000845470ca9c0b39284731ac6bace26f6849dea9627aaed48e", size = 170311, upload-time = "2026-01-20T00:15:23.922Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/b1/9da6ec3e88696018ee7bb9dc4a7310c2cfaebf32923a19598cd342767c10/pyinstaller_hooks_contrib-2026.0-py3-none-any.whl", hash = "sha256:0590db8edeba3e6c30c8474937021f5cd39c0602b4d10f74a064c73911efaca5", size = 452318, upload-time = "2026-01-20T00:15:21.88Z" },
]
[[package]] [[package]]
name = "pysocks" name = "pysocks"
version = "1.7.1" version = "1.7.1"
@@ -85,6 +165,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" },
] ]
[[package]]
name = "pywin32-ctypes"
version = "0.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" },
]
[[package]] [[package]]
name = "selenium" name = "selenium"
version = "4.39.0" version = "4.39.0"
@@ -102,6 +191,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/58/d0/55a6b7c6f35aad4c8a54be0eb7a52c1ff29a59542fc3e655f0ecbb14456d/selenium-4.39.0-py3-none-any.whl", hash = "sha256:c85f65d5610642ca0f47dae9d5cc117cd9e831f74038bc09fe1af126288200f9", size = 9655249, upload-time = "2025-12-06T23:12:33.085Z" }, { url = "https://files.pythonhosted.org/packages/58/d0/55a6b7c6f35aad4c8a54be0eb7a52c1ff29a59542fc3e655f0ecbb14456d/selenium-4.39.0-py3-none-any.whl", hash = "sha256:c85f65d5610642ca0f47dae9d5cc117cd9e831f74038bc09fe1af126288200f9", size = 9655249, upload-time = "2025-12-06T23:12:33.085Z" },
] ]
[[package]]
name = "setuptools"
version = "82.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" },
]
[[package]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"
@@ -125,11 +223,15 @@ name = "test-loderweb"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "pyinstaller" },
{ name = "selenium" }, { name = "selenium" },
] ]
[package.metadata] [package.metadata]
requires-dist = [{ name = "selenium" }] requires-dist = [
{ name = "pyinstaller", specifier = ">=6.18.0" },
{ name = "selenium" },
]
[[package]] [[package]]
name = "trio" name = "trio"