diff --git a/pyproject.toml b/pyproject.toml index 14f5fd3..7d9ebbc 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,5 +5,6 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.14" dependencies = [ + "pyinstaller>=6.18.0", "selenium", ] diff --git a/services/browser_service.py b/services/browser_service.py index b1197a8..423b61f 100644 --- a/services/browser_service.py +++ b/services/browser_service.py @@ -443,3 +443,193 @@ class BrowserService: 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 \ No newline at end of file diff --git a/services/processor.py b/services/processor.py index a4aad5c..ea68fb5 100644 --- a/services/processor.py +++ b/services/processor.py @@ -35,13 +35,14 @@ class FileProcessorService: logging.info("🚀 ИНИЦИАЛИЗАЦИЯ ПРОЦЕССОРА ФАЙЛОВ") 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: upload_button_texts (list): Варианты текста кнопки загрузки файла process_button_texts (list): Варианты текста кнопки обработки + selected_tab (str): Название вкладки на странице для выбора Returns: dict: Результаты обработки @@ -92,12 +93,20 @@ class FileProcessorService: logging.info("=" * 70) try: - # Открываем новую вкладку для каждого файла (кроме первого) - if idx > 1: - logging.info(f" Открываю новую вкладку для файла...") - if not self.browser.open_new_tab(self.url): - logging.warning(f"⚠️ Не удалось открыть новую вкладку") - self.browser.wait_page_load(3) + # Открываем новую вкладку для КАЖДОГО файла (включая первый) + logging.info(f" Открываю новую вкладку в браузере для файла...") + if not self.browser.open_new_tab(self.url): + logging.warning(f"⚠️ Не удалось открыть новую вкладку") + 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 diff --git a/ui.py b/ui.py index 6ab68c5..aa33b5a 100644 --- a/ui.py +++ b/ui.py @@ -147,6 +147,49 @@ class UIManager: ) 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("", 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( main_frame, @@ -205,19 +248,28 @@ class UIManager: # Перенаправление логов в текстовое поле self._setup_log_handler() - - # ===== ВЫБОР ВКЛАДКИ ===== - tabs_label = tk.Label( - main_frame, - text="📑 Выбор вкладки браузера:", - font=self.font_label, - bg="#2C3E50", - fg="#ECF0F1" - ) - tabs_label.pack(anchor="w", pady=(10, 5)) - - self.tab_selector = TabSelectorApp(main_frame, BrowserService(download_dir="./downloads")) - self.tab_selector.pack(fill=tk.X, pady=(0, 15)) + + def _update_tab_selector(self, event=None): + """Обновляет выпадающий список вкладок на основе введенного текста.""" + 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 _update_tab_selector(self, event=None): + """Обновляет выпадающий список вкладок на основе введенного текста.""" + 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): """Настраивает обработчик логов в текстовое поле.""" @@ -276,18 +328,21 @@ class UIManager: 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(",")] + # Получаем выбранную вкладку + selected_tab = self.tab_choice_combo.get().strip() if self.tab_choice_combo.get() else None + # Отключаем кнопку и запускаем обработку в потоке self.processing = True self.start_button.config(state="disabled", bg="#95A5A6") thread = threading.Thread( target=self._processing_thread, - args=(directory, url, upload_texts, process_texts), + args=(directory, url, upload_texts, process_texts, selected_tab), daemon=True ) 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: logging.info("\n" + "="*70) @@ -301,17 +356,19 @@ class UIManager: # Создаем процессор processor = FileProcessorService(directory, url, download_dir) - # Проверка вкладок перед началом загрузки - browser_service = BrowserService(download_dir="./downloads") - browser_service.set_tabs(["Вкладка 1", "Вкладка 2", "Вкладка 3"]) - - # Пример: выбор вкладки по индексу - if not browser_service.select_tab_from_list(0): - logging.error("❌ Не удалось выбрать вкладку. Проверьте массив вкладок.") - return + # Выбор вкладки на странице (если указана) + if selected_tab: + logging.info(f"🔍 Попытка выбрать вкладку на странице: '{selected_tab}'") + browser_service = processor.browser + if browser_service and browser_service.select_tab_by_multiple_methods(selected_tab, timeout=15): + logging.info(f"✅ Вкладка '{selected_tab}' успешно выбрана") + else: + 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) @@ -357,44 +414,6 @@ class UIManager: 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(): """Главная функция.""" root = tk.Tk() diff --git a/uv.lock b/uv.lock index 09195fc..07d2b3c 100755 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,15 @@ version = 1 revision = 3 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]] name = "attrs" 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" }, ] +[[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]] name = "outcome" 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" }, ] +[[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]] name = "pycparser" 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" }, ] +[[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]] name = "pysocks" 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" }, ] +[[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]] name = "selenium" 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" }, ] +[[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]] name = "sniffio" version = "1.3.1" @@ -125,11 +223,15 @@ name = "test-loderweb" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "pyinstaller" }, { name = "selenium" }, ] [package.metadata] -requires-dist = [{ name = "selenium" }] +requires-dist = [ + { name = "pyinstaller", specifier = ">=6.18.0" }, + { name = "selenium" }, +] [[package]] name = "trio"