그룹선택

This commit is contained in:
9700X_PC 2025-01-01 06:39:46 +09:00
parent 2f1d48de66
commit 052b037bd9
6 changed files with 325 additions and 29 deletions

View File

@ -10,6 +10,7 @@ import os, sys, random
import requests
from PIL import Image
from io import BytesIO
import psutil
from whale_new import WhaleTranslator
from clipboardImageManager import ClipboardImageManager
@ -114,6 +115,10 @@ class BrowserController(QThread):
self.detail_tab_locator = self.locator_manager.get_locator('BrowserControl', 'detail_tab_locator')
self.upload_tab_locator = self.locator_manager.get_locator('BrowserControl', 'upload_tab_locator')
self.save_button_locator = self.locator_manager.get_locator('BrowserControl', 'save_button_locator')
self.group_dropdown_locator = self.locator_manager.get_locator('BrowserControl', 'group_dropdown_locator')
self.group_index_template = self.locator_manager.get_locator('BrowserControl', 'group_index_template')
self.dropdown_openstatus_locator = self.locator_manager.get_locator('BrowserControl', 'dropdown_openstatus_locator')
self.selected_group_name_locator = self.locator_manager.get_locator('BrowserControl', 'selected_group_name_locator')
self.text_templates = self.locator_manager.selectors.get('DetailPageTextTemplates', {})
@ -183,16 +188,6 @@ class BrowserController(QThread):
# Playwright 시작 및 브라우저 실행
self.playwright = await async_playwright().start()
# cx_Freeze로 패키징된 경우와 일반 Python 실행 환경 구분하여 경로 설정
# if getattr(sys, 'frozen', False):
# browser_path = os.path.join(os.path.dirname(sys.executable), 'browsers', 'chromium-1112', 'chrome-win','chrome.exe')
# extension_path = os.path.join(os.path.dirname(sys.executable), 'browsers', 'extensions', '1.1.100_0')
# user_data_dir = os.path.join(os.path.dirname(sys.executable), 'browsers', 'user_data')
# else:
# browser_path = os.path.join(os.path.dirname(__file__), 'browsers', 'chromium-1112', 'chrome-win','chrome.exe')
# extension_path = os.path.join(os.path.dirname(__file__), 'browsers', 'extensions', '1.1.100_0')
# user_data_dir = os.path.join(os.path.dirname(__file__), 'browsers', 'user_data')
base_path = self.get_base_dir()
self.logger.log(f"base_path: {base_path}", level=logging.DEBUG)
@ -278,6 +273,11 @@ class BrowserController(QThread):
self.logger.log('신규 상품 등록 페이지로 이동 중...', level=logging.INFO)
await self.go_to_new_product_page()
group_index = self.toggle_states['group_index']
self.logger.log('선택한 그룹 인덱스로 이동', level=logging.INFO)
if group_index:
await self.select_group_index(group_index=group_index)
# 각 핸들러에 초기화된 page 객체 전달.
self.titleGenerator.update_page(self.page)
@ -546,6 +546,49 @@ class BrowserController(QThread):
self.logger.log(f"상품 수정 버튼 상태 확인 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
return False # 오류 발생 시 기본적으로 활성화된 것으로 처리
async def select_group_index(self, group_index: int):
"""그룹 드롭다운 열고 옵션 선택"""
try:
group_option_locator = self.group_index_template.format(index=group_index)
# 드롭다운 열기
await self.page.wait_for_selector(self.group_dropdown_locator, timeout=3000)
await self.page.click(self.group_dropdown_locator)
self.logger.log("드롭다운을 성공적으로 클릭했습니다.", level=logging.INFO)
# 드롭다운 열림 상태 확인
await self.page.wait_for_selector(self.dropdown_openstatus_locator, timeout=3000, state='visible')
self.logger.log("드롭다운이 열렸습니다.", level=logging.INFO)
# 옵션 선택
await self.page.wait_for_selector(group_option_locator, timeout=3000)
await self.page.click(group_option_locator)
self.logger.log(f"[{group_index}]번 그룹 선택 완료", level=logging.INFO)
selected_group_name = await self.page.inner_text(self.selected_group_name_locator)
self.logger.log(f"선택된 그룹 이릅 : [{selected_group_name}]", level=logging.INFO)
except TimeoutError:
self.logger.log("드롭다운 또는 옵션 선택 중 타임아웃이 발생했습니다.", level=logging.WARNING)
except Exception as e:
self.logger.log(f"그룹 선택 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
# async def get_selected_group_name(self):
# """선택된 그룹 이름 가져오기"""
# try:
# selected_group_name_locator = self.locator_manager.get_locator('BrowserControl', 'selected_group_name_locator')
# await self.page.wait_for_selector(selected_group_name_locator, timeout=3000, state='visible')
# selected_group_name = await self.page.inner_text(selected_group_name_locator)
# self.logger.log(f"선택된 그룹 이름: {selected_group_name}", level=logging.INFO)
# return selected_group_name
# except TimeoutError:
# self.logger.log("선택된 그룹 이름을 가져오는 중 타임아웃이 발생했습니다.", level=logging.WARNING)
# return None
# except Exception as e:
# self.logger.log(f"그룹 이름 가져오기 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
# return None
async def get_product_edit_buttons_by_templete(self):
"""현재 페이지의 세부사항 수정 및 업로드 버튼을 찾기"""
# 버튼 선택자 설정
@ -2166,9 +2209,29 @@ class BrowserController(QThread):
self.logger.log("Whale 브라우저 창 닫기 시도 중...", level=logging.INFO)
self.whale_translator.close_whale_window()
def force_terminate_browser(self):
"""Playwright 브라우저 프로세스를 강제로 종료"""
try:
if self.browser:
browser_pid = self.browser.contexts[0]._channel.owner._impl_obj._browser_pid
browser_process = psutil.Process(browser_pid)
for child in browser_process.children(recursive=True):
child.kill()
browser_process.kill()
self.logger.log("브라우저 프로세스를 강제로 종료했습니다.", level=logging.WARNING)
if self.whale_translator:
self.whale_translator.close_whale_window()
except Exception as e:
self.logger.log(f"브라우저 프로세스 강제 종료 중 오류 발생: {e}", level=logging.ERROR)
def terminate(self):
self.logger.log("크롬 스레드 종료", level=logging.INFO)
self.cleanup() # 종료 시 추가 정리 작업 호출
if self.whale_translator:
self.whale_translator.close_whale_window()
super().terminate()
def cleanup(self):

View File

@ -165,9 +165,11 @@ product_price_for_ed_template = '//*[@id="root"]/div/div/div/div/main/div/div[2]
product_image_for_ed_template = '//*[@id="root"]/div/div/div/div/main/div/div[2]/div[3]/div[2]/div/div/div/div/div[2]/table/tbody/tr[{index}]/td[2]/div/div/div/div[1]/span/div/div[1]/img'
"div#root div:nth-child(2) > div > li > div > div > div:nth-child(2) > div > div > div.ant-flex.css-1li46mu.ant-flex-align-stretch.ant-flex-justify-space-between.ant-flex-vertical > div.ant-flex.css-1li46mu.ant-flex-wrap-nowrap.ant-flex-justify-flex-end > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-default.ant-btn-icon-only"
# 그룹관련 선택자
group_dropdown_locator = 'div.ant-select-selector'
dropdown_openstatus_locator = 'div.ant-select-dropdown:not(.ant-select-dropdown-hidden)'
group_index_template = 'div.ant-select-item.ant-select-item-option:nth-child({index})'
selected_group_name_locator = 'div#root div:nth-child(4) > div > div > span.ant-select-selection-item'
# 상품 편집 및 페이지 이동 관련 선택자

119
groupTest.py Normal file
View File

@ -0,0 +1,119 @@
from playwright.sync_api import sync_playwright
import sys, os, random
import time
def change_group(selected_group_index: int):
# Playwright 시작 및 브라우저 실행
playwright = sync_playwright().start()
base_path = get_base_dir()
browser_path = os.path.join(base_path, 'browsers', 'chromium-1140', 'chrome-win','chrome.exe')
extension_path = os.path.join(base_path, 'browsers', 'extensions', '1.1.100_0')
user_data_dir = os.path.join(base_path, 'browsers', 'user_data')
# User agent 설정
user_agent = random.choice([
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 OPR/85.0.0.0",
])
# 브라우저 시작 및 설정
browser = playwright.chromium.launch_persistent_context(
user_data_dir,
headless=True,
permissions=["geolocation", "notifications"],
geolocation={"latitude": 37.5665, "longitude": 126.9780},
locale="ko-KR",
args=[
'--disable-popup-blocking',
f'--disable-extensions-except={extension_path}',
f'--load-extension={extension_path}',
'--start-maximized',
'--window-size=1920,1080'
],
executable_path=browser_path,
user_agent=user_agent
)
# 기본 페이지가 없을 수 있으므로 새로운 페이지 생성
page = browser.new_page()
# 로그인 페이지로 이동 (URL을 실제 로그인 페이지로 변경)
page.goto('https://percenty.co.kr/signin')
# 로그인 수행
admin_toggle = page.locator('button[role="switch"]')
if admin_toggle.get_attribute("aria-checked") == "true":
admin_toggle.click() # 관리자 모드에서 직원 모드로 전환
page.fill('input[placeholder="이메일 주소 입력"]', 'leensoo1nt@gmail.com')
page.fill('input[placeholder="직원 아이디 입력"]', 'test')
page.fill('input[placeholder="영문/숫자/특수문자의 조합 (6~15자리)"]', 'test')
page.click('button:has-text("직원 로그인 하기")')
page.wait_for_selector('div[role="dialog"]', timeout=3000, state='visible')
close_button = page.query_selector("div.ant-modal-footer > div > div > button[type='button'].ant-btn.css-1li46mu.ant-btn-default")
if close_button:
close_button.click()
# 상품 수정 페이지로 이동
page.wait_for_load_state("networkidle")
page.click('span.ant-menu-title-content:has-text("신규 상품 등록")')
page.evaluate("""
const dropdown = document.querySelector('input#rc_select_1');
if (dropdown) {
dropdown.scrollIntoView();
}
""")
print("해당요소 스크롤")
# 그룹 드롭박스 열기
group_dropdown_css = "div.ant-select-selector"
page.wait_for_selector(group_dropdown_css)
print("드롭박스 상위 요소 대기")
# 드롭박스 열기
page.click(group_dropdown_css)
print("드롭박스 클릭")
# 드롭다운 열림 상태 확인
dropdown_openstatus_css = "div.ant-select-dropdown:not(.ant-select-dropdown-hidden)"
page.wait_for_selector(dropdown_openstatus_css)
print("드롭다운 열림 확인")
# 옵션 선택
group_index_css = f"div.ant-select-item.ant-select-item-option:nth-child({selected_group_index + 1})"
page.wait_for_selector(group_index_css)
page.click(group_index_css)
print(f"[{selected_group_index+1}]번 그룹 선택 완료")
# 변경된 그룹 이름 가져오기
selected_group_name_css = "div#root div:nth-child(4) > div > div > span.ant-select-selection-item"
selected_group_name = page.inner_text(selected_group_name_css)
print(f"변경된 그룹 이름: {selected_group_name}")
time.sleep(1)
# 브라우저 닫기
browser.close()
def get_base_dir():
if getattr(sys, 'frozen', False): # 패키징된 경우
base_dir = os.path.dirname(sys.executable)
internal_dir = os.path.join(base_dir, '_internal')
if os.path.exists(internal_dir):
return internal_dir
else:
base_dir = os.path.dirname(os.path.abspath(__file__))
return base_dir
# 실행 예제 (2번째 그룹 선택)
change_group(selected_group_index=2)

136
gui.py
View File

@ -1,4 +1,4 @@
from PySide6.QtWidgets import QInputDialog, QWidget, QMessageBox, QSpinBox, QPushButton, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
from PySide6.QtWidgets import QApplication, QInputDialog, QWidget, QMessageBox, QSpinBox, QPushButton, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy, QComboBox
from PySide6.QtCore import Qt, Signal, Slot, QRect, QSettings, QTimer
from toggleSwitch import ToggleSwitch
from browser_control import BrowserController
@ -11,6 +11,7 @@ from loggerModule import Logger # 추가
import logging
import sys
import os, shutil, time
import asyncio
class AutoPercentyGUI(QWidget):
@ -226,6 +227,7 @@ class AutoPercentyGUI(QWidget):
'watermark_text': "", # 워터마크 텍스트 저장
'opacity_percent': 25, # 워터마크 투명도
'max_option_count': 20, # 최대 선택가능한 옵션 수
'group_index': 1, # 작업그룹 선택
}
def initUI(self):
@ -396,8 +398,18 @@ class AutoPercentyGUI(QWidget):
self.opacity_percent_input.setToolTip("워터마크 투명도 설정: 낮을수록 투명") # 툴팁 설정
self.opacity_percent_input.valueChanged.connect(self.update_opacity_percent) # 값 변경 시 update_max_option_count 메서드 호출
# 워터마크 관련 요소들을 하나의 QHBoxLayout에 추가 (비율 2:3:1)
self.watermark_layout = QHBoxLayout()
# 그룹 선택 드롭박스 및 툴팁 추가
self.group_selector_label = QLabel("그룹 선택:", self)
self.group_selector = QComboBox(self)
self.group_selector.setToolTip(
"직원계정은 3개, 관리자계정은 20개 중 선택할 수 있습니다.\n해당 그룹이 없을 경우 기본으로 1번그룹을 작업합니다."
)
self.group_selector.currentIndexChanged.connect(self.on_group_selected)
# 기본 상태는 직원 (3개 그룹)
self.update_group_items(is_admin=False)
# 초기 위치에서 배치
self.update_widget_positions(use_api_row=1)
@ -773,6 +785,39 @@ class AutoPercentyGUI(QWidget):
current_row += 1
self.toggle_layout.addWidget(self.max_option_count_label, current_row, 0)
self.toggle_layout.addWidget(self.max_option_count_input, current_row, 1)
self.toggle_layout.addWidget(self.group_selector_label, current_row, 2)
self.toggle_layout.addWidget(self.group_selector, current_row, 3)
def update_group_items(self, is_admin: bool):
"""관리자 여부에 따라 그룹 선택 항목 변경"""
self.group_selector.clear() # 기존 아이템 제거
if is_admin:
# 관리자 계정: 20개 그룹
self.group_selector.addItems([f"{i}" for i in range(1, 21)])
else:
# 직원 계정: 3개 그룹
self.group_selector.addItems(["1번그룹", "2번그룹", "3번그룹"])
self.group_selector.setCurrentIndex(0) # 기본값 설정
def on_group_selected(self):
"""그룹 선택 변경 시 호출"""
import re
try:
# 정규식으로 숫자만 추출
match = re.search(r'\d+', self.group_selector.currentText())
if match:
self.toggle_states['group_index'] = int(match.group())
print(f"선택된 그룹이 변경되었습니다: {self.toggle_states['group_index']}")
else:
# 숫자가 없을 경우 처리
print(f"선택된 그룹에 숫자가 없습니다: {self.group_selector.currentText()}")
self.toggle_states['group_index'] = None
except Exception as e:
# 기타 예외 처리
print(f"그룹 선택 처리 중 오류 발생: {e}")
self.toggle_states['group_index'] = None
def on_toggle_clicked_generic(self, key, is_checked):
"""토글 클릭 시 상태 업데이트 및 저장"""
@ -860,6 +905,8 @@ class AutoPercentyGUI(QWidget):
self.set_layout_visibility(self.admin_layout, False)
self.set_layout_visibility(self.user_layout, True)
self.update_group_items(is_admin=is_checked)
# def on_vd_mode_for_detail_imageTrans_clicked(self, is_checked):
# """상페이미지 번역여부에 따라 VD 모드 선택 필드를 표시/숨김"""
# if is_checked:
@ -1045,22 +1092,83 @@ class AutoPercentyGUI(QWidget):
"""브라우저 오류 발생 시 처리할 로직"""
self.logger.log(f"브라우저 시작 중 오류 발생: {error_message}", level=logging.ERROR)
def closeEvent(self, event):
"""창 닫기 시 스레드 종료"""
self.logger.log('프로그램을 종료합니다...', level=logging.INFO)
self.save_settings()
if self.browser_controller.isRunning():
self.browser_controller.stop() # 리소스 정리
self.browser_controller.wait() # 스레드가 종료될 때까지 대기
event.accept()
# def close(self):
# def closeEvent(self, event):
# """창 닫기 시 스레드 종료"""
# self.logger.log('프로그램을 종료합니다...', level=logging.INFO)
# self.save_settings()
# asyncio.run(self.browser_controller.close_browser()) # 브라우저 종료
# if self.browser_controller.isRunning():
# self.browser_controller.stop() # 리소스 정리
# # self.browser_controller.wait() # 스레드가 종료될 때까지 대기
# event.accept()
# super().close()
def closeEvent(self, event):
"""창 닫기 시 스레드 및 리소스 종료"""
self.logger.log('프로그램을 종료합니다...', level=logging.INFO)
# 현재 설정 저장
self.save_settings()
# Playwright 및 이벤트 루프 정리
try:
asyncio.run(self.cleanup_resources())
except Exception as e:
self.logger.log(f"Playwright 리소스 정리 중 오류 발생: {e}", level=logging.ERROR)
# 브라우저 컨트롤러 스레드 종료
if self.browser_controller.isRunning():
self.browser_controller.terminate()
self.browser_controller.wait(3000) # 3초 대기
if self.browser_controller.isRunning():
self.logger.log('스레드가 종료되지 않아 강제 종료를 시도합니다.', level=logging.WARNING)
self.browser_controller.terminate() # 강제 종료
# Qt 메인 이벤트 루프 종료
QApplication.quit()
event.accept()
super().closeEvent(event)
async def cleanup_resources(self):
"""Playwright 및 이벤트 루프 정리"""
try:
self.logger.log("Playwright 리소스 정리를 시작합니다.", level=logging.INFO)
# 모든 페이지 닫기
if self.browser_controller.browser:
self.logger.log("열린 페이지를 닫습니다...", level=logging.INFO)
for page in self.browser_controller.browser.pages:
try:
await asyncio.wait_for(page.close(), timeout=1) # 페이지 닫기에 타임아웃 적용
self.logger.log(f"페이지 {page.url} 닫기 완료.", level=logging.INFO)
except asyncio.TimeoutError:
self.logger.log(f"페이지 {page.url} 닫기 타임아웃 발생. 강제 종료를 시도합니다.", level=logging.WARNING)
# 브라우저 닫기
self.logger.log("브라우저를 닫습니다...", level=logging.INFO)
try:
await asyncio.wait_for(self.browser_controller.browser.close(), timeout=1)
self.logger.log("브라우저 종료 완료.", level=logging.INFO)
except asyncio.TimeoutError:
self.logger.log("브라우저 종료가 타임아웃되었습니다. 강제 종료를 시도합니다.", level=logging.WARNING)
self.browser_controller.force_terminate_browser()
# Playwright 종료
if self.browser_controller.playwright:
self.logger.log('Playwright 종료 중...', level=logging.INFO)
await self.browser_controller.playwright.stop()
self.logger.log('Playwright 종료 완료.', level=logging.INFO)
# 이벤트 루프 종료
if self.browser_controller.loop and not self.browser_controller.loop.is_closed():
self.browser_controller.loop.call_soon_threadsafe(self.browser_controller.loop.stop)
self.logger.log('이벤트 루프 종료 완료.', level=logging.INFO)
except Exception as e:
self.logger.log(f"리소스 정리 중 오류 발생: {e}", level=logging.ERROR)
@Slot()
def on_start_PercentyJob_clicked(self):

View File

@ -102,6 +102,10 @@ class LocatorManager:
'detail_tab_locator': self.config.get('BrowserControl', 'detail_tab_locator').strip("'"),
'upload_tab_locator': self.config.get('BrowserControl', 'upload_tab_locator').strip("'"),
'save_button_locator': self.config.get('BrowserControl', 'save_button_locator').strip("'"),
'group_dropdown_locator': self.config.get('BrowserControl', 'group_dropdown_locator').strip("'"),
'dropdown_openstatus_locator': self.config.get('BrowserControl', 'dropdown_openstatus_locator').strip("'"),
'group_index_template': self.config.get('BrowserControl', 'group_index_template').strip("'"),
'selected_group_name_locator': self.config.get('BrowserControl', 'selected_group_name_locator').strip("'"),
}
# DetailPageTextTemplates 섹션

View File

@ -54,7 +54,7 @@ build_exe_options = {
'packages': [
'ctypes', 'asyncio', 'os', 're', 'time', 'math', 'json', 'logging', 'shutil', 'random', 'base64',
'subprocess', 'configparser', 'pyperclip', 'numpy', 'cv2', 'requests', 'win32clipboard', 'win32gui',
'win32con', 'win32process', 'PIL', 'bs4', 'sqlalchemy', 'sqlalchemy.orm', 'PySide6',
'win32con', 'win32process', 'PIL', 'bs4', 'sqlalchemy', 'sqlalchemy.orm', 'PySide6', 'psutil',
'sqlalchemy.exc', 'collections', 'pandas', 'pymongo', 'translatepy', 'comtypes',
],
@ -85,7 +85,7 @@ executables = [
# Setup 설정
setup(
name='AutoPercenty3',
version='3.1',
version='3.2',
description='자동화도구',
options={'build_exe': build_exe_options},
executables=executables