from playwright.async_api import async_playwright import os, random, traceback, asyncio from ui.two_factor_auth_dialog import TwoFactorAuthDialog from playwright._impl._errors import TargetClosedError class PlaywrightHelper: def __init__(self): self.browser = None self.context = None self.page = None self.dialog = TwoFactorAuthDialog() self.market_names = [ '쿠팡', '스마트스토어', 'esm', '11번가-국내', '11번가-글로벌', '롯데온', '인터파크', '위메프', '옥션1.0' ] self.market_xpaths = { '쿠팡': "div#rc-tabs-0-tab-cp", '스마트스토어': "div#rc-tabs-0-tab-ss", 'esm': "div#rc-tabs-0-tab-esm", '11번가-국내': "div#rc-tabs-0-tab-est", '11번가-글로벌': "div#rc-tabs-0-tab-est_global", '롯데온': "div#rc-tabs-0-tab-lotteon", '인터파크': "div#rc-tabs-0-tab-ip", '위메프': "div#rc-tabs-0-tab-wmp", '옥션1.0': "div#rc-tabs-0-tab-at" } self.progress_steps = { '쿠팡': 35, '스마트스토어': 40, 'esm': 45, '11번가-국내': 50, '11번가-글로벌': 55, '롯데온': 60, '인터파크': 65, '위메프': 70, '옥션1.0': 75 } # 마켓 이름과 XPath 매핑 self.market_xpath_mapping = { '쿠팡': 'cp', '스마트스토어': 'ss', 'esm': 'esm', '11번가-국내': 'est', '11번가-글로벌': 'est_global', '롯데온': 'lotteon', '인터파크': 'ip', '위메프': 'wmp', '옥션1.0': 'at' } self.api_key_validation_button = { '쿠팡': "div#rc-tabs-0-panel-cp div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary", '스마트스토어': "div#rc-tabs-0-panel-ss div.ant-row.css-1li46mu > button[type=\"button\"]:nth-child(3)", 'esm': "div#rc-tabs-0-panel-esm div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary", '11번가-국내': "div#rc-tabs-0-panel-est div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary", '11번가-글로벌': "div#rc-tabs-0-panel-est_global div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary", '롯데온': "div#rc-tabs-0-panel-lotteon div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary", '인터파크': "div#rc-tabs-0-panel-ip div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary", '위메프': "div#rc-tabs-0-panel-wmp div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary", '옥션1.0': "div#rc-tabs-0-panel-at div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary", } self.api_key_status = { '쿠팡': "div#rc-tabs-0-panel-cp span:nth-child(2) > sup", '스마트스토어': "div#rc-tabs-0-panel-ss span:nth-child(2) > sup", 'esm': "div#rc-tabs-0-panel-esm span:nth-child(2) > sup", '11번가-국내': "div#rc-tabs-0-panel-est span:nth-child(2) > sup", '11번가-글로벌': "div#rc-tabs-0-panel-est_global span:nth-child(2) > sup", '롯데온': "div#rc-tabs-0-panel-lotteon span:nth-child(2) > sup", '인터파크': "div#rc-tabs-0-panel-ip span:nth-child(2) > sup", '위메프': "div#rc-tabs-0-panel-wmp span:nth-child(2) > sup", '옥션1.0': "div#rc-tabs-0-panel-at sup", } self.order_collection_button = { '쿠팡': "div#rc-tabs-0-panel-cp div:nth-child(5) > button[type=\"button\"]", # '스마트스토어': "div#rc-tabs-0-panel-ss div:nth-child(4) > button[type=\"button\"]", '스마트스토어': "#rc-tabs-0-panel-ss .css-1li46mu[role='switch'][type='button']", 'esm': "div#rc-tabs-0-panel-esm div:nth-child(3) > button[type=\"button\"]", '11번가-국내': "div#rc-tabs-0-panel-est div > div > div:nth-child(2) > button[type=\"button\"]", '11번가-글로벌': "div#rc-tabs-0-panel-est_global div > div > div:nth-child(2) > button[type=\"button\"]", '롯데온': "div#rc-tabs-0-panel-lotteon div > div > div:nth-child(2) > button[type=\"button\"]", '인터파크': "div#rc-tabs-0-panel-ip div:nth-child(25) > button[type=\"button\"]", '위메프': "div#rc-tabs-0-panel-wmp div:nth-child(2) > button[type=\"button\"]", '옥션1.0': "div#rc-tabs-0-panel-at div:nth-child(1) > button[type=\"button\"]", } self.smartstore_elements = { # 'account_button': "div#rc-tabs-0-panel-ss div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary.ant-btn-background-ghost", 'account_button': "#rc-tabs-0-panel-ss .css-1li46mu .ant-btn-background-ghost[type='button']", # 'popup_login_type_button': "div#root div.Login_login_content__Ia6Rm > ul > li:nth-child(1)", 'popup_login_type_button': ".Login_type_list__KPqjy .Login_type_item__2_QV8 .Login_title__3ixGa", # 'popup_login_id_input': "div#root li.Login_login_item__2fOX0.Login_hover__2Wdak.Login_on__A5e8I > input", 'popup_login_id_input': ".Login_login_area__cMnCU input[placeholder='아이디 또는 이메일 주소']", # 'popup_login_pw_input': "div#root li:nth-child(2) > input", 'popup_login_pw_input': ".Login_login_item__2fOX0 [placeholder='비밀번호']", # 'popup_login_button': "div#root div.Login_login_content__Ia6Rm > div > div > button[type=\"button\"]", 'popup_login_button': ".Login_btn_box__22wei [type='button']", # 'login_success_check': "div#_gnb_nav span.login-id.text-overflow" 'login_success_check': "[ng-if='vm.loginInfo'] .login-id.text-overflow", '2fa_check': '#twoFactorAuth', # 2단계 인증 확인용 셀렉터 '2fa_complete_check': '#twoFactorAuthComplete' # 2단계 인증 완료 확인용 셀렉터 } self.toggle_states = { '쿠팡': 'on', '스마트스토어': 'on', 'esm': 'on', '11번가-국내': 'on', '11번가-글로벌': 'on', '롯데온': 'on', '인터파크': 'on', '위메프': 'off', '옥션1.0': 'off', } self.api_key_xpaths = { '쿠팡': { '쿠팡id': "div#rc-tabs-0-panel-cp div:nth-child(1) > input", '업체코드': "div#rc-tabs-0-panel-cp div:nth-child(2) > input", 'accesskey': "div#rc-tabs-0-panel-cp div:nth-child(3) > input", 'secretkey': "div#rc-tabs-0-panel-cp div:nth-child(4) > input", }, '스마트스토어': { '업로드할스마트스토어계정명' : "div#rc-tabs-0-panel-ss div:nth-child(1) > input", '업로드할스마트스토어계정id' : "div#rc-tabs-0-panel-ss div:nth-child(1) > input", '업로드할스마트스토어계정pw' : "div#rc-tabs-0-panel-ss div:nth-child(1) > input", '애플리케이션id': "div#rc-tabs-0-panel-ss div:nth-child(2) > input", '애플리케이션시크릿': "div#rc-tabs-0-panel-ss div:nth-child(3) > input", }, 'esm': { '옥션id': "div#rc-tabs-0-panel-esm div:nth-child(1) > input", '지마켓id': "div#rc-tabs-0-panel-esm div:nth-child(2) > input", }, '11번가-국내': { 'apikey': "div#rc-tabs-0-panel-est input", }, '11번가-글로벌': { 'apikey': "div#rc-tabs-0-panel-est_global input", }, '롯데온': { 'apikey': "div#rc-tabs-0-panel-lotteon input", }, '인터파크': { '상품상태재고수정인증키': "div#rc-tabs-0-panel-ip div:nth-child(1) > input", '상품상태재고수정비밀키':"div#rc-tabs-0-panel-ip div:nth-child(2) > input", '상품재고조회인증키':"div#rc-tabs-0-panel-ip div:nth-child(3) > input", '상품재고조회비밀키':"div#rc-tabs-0-panel-ip div:nth-child(4) > input", '상품정보조회인증':"div#rc-tabs-0-panel-ip div:nth-child(6) > input", '상품정보조회비밀키':"div#rc-tabs-0-panel-ip div:nth-child(7) > input", '상품수정인증키':"div#rc-tabs-0-panel-ip div:nth-child(8) > input", '상품수정비밀키':"div#rc-tabs-0-panel-ip div:nth-child(9) > input", '상품등록인증키':"div#rc-tabs-0-panel-ip div:nth-child(11) > input", '상품등록비밀키':"div#rc-tabs-0-panel-ip div:nth-child(12) > input", '반품배송지조회인증키':"div#rc-tabs-0-panel-ip div:nth-child(13) > input", '반품배송지조회비밀키':"div#rc-tabs-0-panel-ip div:nth-child(14) > input", '반품배송지등록인증키':"div#rc-tabs-0-panel-ip div:nth-child(16) > input", '반품배송지등록비밀키':"div#rc-tabs-0-panel-ip div:nth-child(17) > input", '상품qna등록인증키':"div#rc-tabs-0-panel-ip div:nth-child(18) > input", '상품qna등록비밀키':"div#rc-tabs-0-panel-ip div:nth-child(19) > input", '상품qna조회인증키':"div#rc-tabs-0-panel-ip div:nth-child(21) > input", '상품qna조회비밀키':"div#rc-tabs-0-panel-ip div:nth-child(22) > input", '인터파크업체번호':"div#rc-tabs-0-panel-ip div:nth-child(23) > input", '공급계약일련번호':"div#rc-tabs-0-panel-ip div:nth-child(24) > input", }, '위메프': { 'apikey': "div#rc-tabs-0-panel-wmp input", }, '옥션1.0': { 'apikey': "div#rc-tabs-0-panel-at div:nth-child(1) > input", '멤버id': "div#rc-tabs-0-panel-at div:nth-child(2) > input", }, } async def init_browser(self, isHeadless_mode = True): try: """브라우저 설정 및 인스턴스 생성""" playwright = await async_playwright().start() # self.browser = await playwright.chromium.launch(headless=False) # 드라이버의 경로 설정 driver_path = os.path.join(os.path.dirname(__file__), 'drivers', 'chromium-1112', 'chrome-win','chrome.exe') # 확장 프로그램 경로 extension_path = os.path.join(os.path.dirname(__file__), 'drivers', 'extensions', '1.1.100_0') # 사용자 데이터 디렉토리 설정 user_data_dir = os.path.join(os.path.dirname(__file__), 'drivers', '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", ]) self.browser = await playwright.chromium.launch_persistent_context(user_data_dir, headless=isHeadless_mode, 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}' ], executable_path=driver_path, user_agent=user_agent # user agent 설정 추가 ) # headless 모드 설정 # self.page = await self.browser.new_page() # 첫 번째 페이지를 가져옴 self.page = self.browser.pages[0] # await self.page.set_default_navigation_timeout(40000) # 기본 타임아웃(40초) 변경 except Exception as e: print(f"브라우저 초기화 중 에러발생 : {e}") traceback.print_exc() async def close_browser(self): if self.browser: await self.browser.close() async def login_and_fetch_api_keys(self, url, username, password, status_label, progress_bar): try: print('로그인 동작 실행') await self.login_Process(url, username, password, progress_bar, status_label) print('로그인 동작 완료') # # context = await self.browser.new_context() # # page = await context.new_page() # # self.page = await self.browser.new_page() # 첫 번째 페이지를 가져옴 # await self.page.goto(url) # progress_bar.setValue(10) # status_label.setText('현재 상태: 웹페이지 접속완료') # # 현재 로그인 상태 확인 # # login_status = await self.page.query_selector("div#root div:nth-child(3) > span") # login_status = await self.page.query_selector(".ant-space-item .ant-typography:nth-child(2)", 2000) # if not login_status: # print('로그인 실행') # # 홈 페이지에서 로그인 버튼 클릭 # await self.page.click(".signList > .ant-btn-default > span") # # 로그인 페이지에서 로그인 수행 # await self.page.fill(".ant-input:nth-child(4)", username) # await self.page.fill(".ant-input:nth-child(1)", password) # await self.page.click(".ant-btn-primary") # progress_bar.setValue(20) # status_label.setText('현재 상태: 퍼센티 로그인 완료') # # 마켓 설정 페이지로 이동 # await self.page.click("xpath=/html/body/div[1]/div/div/div/div/aside/div/ul/li[7]/ul/li[2]") # # await self.page.click("div#root li.ant-menu-item.ant-menu-item-selected.ant-menu-item-only-child > span") # # 마켓 설정상 팝업 다이얼로그가 있는 경우 닫기 (있는 경우) # try: # # await self.page.click(".ant-modal-footer [type='button']") # await self.page.click("div.ant-modal-footer > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",2) # # .ant-modal-content # # .ant-modal-footer [type='button'] # except: # pass # progress_bar.setValue(30) # status_label.setText('현재 상태: 마켓 설정') api_keys = {} for market in self.market_names: print(f"self.market_xpaths[market] : {self.market_xpaths[market]}") print(f"self.api_key_xpaths[market] : {self.api_key_xpaths[market]}") api_keys[market] = await self.fetch_market_api_keys(self.market_xpaths[market], self.api_key_xpaths[market]) print(f"api_keys['{market}'] \n {api_keys[market]}") progress_bar.setValue(self.progress_steps[market]) status_label.setText(f'현재 상태: {market} 가져오기 완료') return api_keys except Exception as e: print(f"오류발생 : {e}") # 각 마켓별 API 키 가져오기 async def fetch_market_api_keys(self, market_xpath, api_key_xpaths): await self.page.click(market_xpath) # await self.page.wait_for_timeout(1000) # 잠시 대기 await asyncio.sleep(1) # 잠시 대기 return {key: await self.page.get_attribute(xpath, 'value') for key, xpath in api_key_xpaths.items()} async def login_check(self): try: print("login_check 실행") await self.page.wait_for_load_state() # 페이지의 모든 요소가 로드될때 까지 대기 print("웹 로딩 완료") # await self.page.wait_for_selector("div#root .ant-typography.css-1li46mu:nth-child(3) > span", timeout=2000) # 2초 내 로그인 여부 판단 await self.page.wait_for_selector("div#root .ant-space-item .ant-typography.css-1li46mu[style='color: rgba(0, 0, 0, 0.85); font-size: 14px;']", timeout=2000) # 2초 내 로그인 여부 판단 return True except: return False async def login_Process(self, url, username, password, progress_bar, status_label): try: await self.page.goto(url) progress_bar.setValue(10) status_label.setText('현재 상태: 웹페이지 접속완료') # 현재 로그인 상태 확인 # login_status = await self.page.query_selector("div#root div:nth-child(3) > span") # login_status = await self.page.query_selector(".ant-space-item .ant-typography:nth-child(2)", timeout=3000) # login_status = await self.page.query_selector("#root > div > div > div > header > div > div.ant-space.css-1li46mu.ant-space-horizontal.ant-space-align-center.ant-space-gap-row-small.ant-space-gap-col-small > div:nth-child(3) > span", timeout=3000) login_status = await self.login_check() print(f"login_status : {login_status}") if not login_status: print('로그인 실행') # 홈 페이지에서 로그인 버튼 클릭 # 로그인 버튼이 보이도록 가로 스크롤 await self.page.evaluate('window.scrollBy(800, 0)') # 가로 스크롤 await self.page.wait_for_selector(".signList > .ant-btn-default > span", state='visible') print('로그인 페이지 스크롤') await self.page.click(".signList > .ant-btn-default > span") # div#root .css-1li46mu .hojyII .signList button.ant-btn.ant-btn-default[type='button'] span print('로그인 페이지의 로그인 버튼 클릭') # 로그인 페이지에서 로그인 수행 await self.page.fill(".ant-input:nth-child(4)", username) await self.page.fill(".ant-input:nth-child(1)", password) await self.page.click(".ant-btn-primary") # 알림 메시지가 나타나면 닫기 try: await self.page.wait_for_selector("#ch-shadow-root-wrapper > div.ModalLayer__ModalLayerWrapper-ch-front__sc-10u1w92-0.jImPdL > div > article > div > div > div.PopupCloseBtn__CloseButtonArea-ch-front__sc-14jjsiy-2.kSHXDH > button", timeout=2000) await self.page.click("#ch-shadow-root-wrapper > div.ModalLayer__ModalLayerWrapper-ch-front__sc-10u1w92-0.jImPdL > div > article > div > div > div.PopupCloseBtn__CloseButtonArea-ch-front__sc-14jjsiy-2.kSHXDH > button") print("알림 메시지를 닫았습니다.") except Exception as e: print("알림 메시지가 나타나지 않았습니다.") progress_bar.setValue(20) status_label.setText('현재 상태: 퍼센티 로그인 완료') # 마켓 설정 페이지로 이동 await self.page.click("xpath=/html/body/div[1]/div/div/div/div/aside/div/ul/li[7]/ul/li[2]") print("마켓 설정 페이지로 이동") # await self.page.click("[role='menu'] .ant-menu-item.ant-menu-item-selected") # 팝업 다이얼로그 닫기 (있는 경우) try: # await self.page.click("xpath=body > div:nth-child(10) > div > div.ant-modal-wrap.ant-modal-centered > div > div.ant-modal-content > div.ant-modal-footer > button.ant-btn.css-1li46mu.ant-btn-primary") print("팝업다이알로그가 존재한다면 클릭") await self.page.click("div.ant-modal-footer > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",timeout=1000) except: pass progress_bar.setValue(30) status_label.setText('현재 상태: 마켓 설정') except Exception as e: print(f"오류발생 : {e}") async def update_api_keys(self, url, username, password, api_keys, status_label, progress_bar): try: print('로그인 동작 실행') await self.login_Process(url, username, password, progress_bar, status_label) print('로그인 동작 완료') # await self.page.goto(url) # progress_bar.setValue(10) # status_label.setText('현재 상태: 웹페이지 접속완료') # # 현재 로그인 상태 확인 # # login_status = await self.page.query_selector("div#root div:nth-child(3) > span") # login_status = await self.page.query_selector(".ant-space-item .ant-typography:nth-child(2)", 2000) # if not login_status: # print('로그인 실행') # # 홈 페이지에서 로그인 버튼 클릭 # await self.page.click(".signList > .ant-btn-default > span") # # 로그인 페이지에서 로그인 수행 # await self.page.fill(".ant-input:nth-child(4)", username) # await self.page.fill(".ant-input:nth-child(1)", password) # await self.page.click(".ant-btn-primary") # progress_bar.setValue(20) # status_label.setText('현재 상태: 퍼센티 로그인 완료') # # 마켓 설정 페이지로 이동 # await self.page.click("xpath=/html/body/div[1]/div/div/div/div/aside/div/ul/li[7]/ul/li[2]") # # 팝업 다이얼로그 닫기 (있는 경우) # try: # await self.page.click("xpath=body > div:nth-child(10) > div > div.ant-modal-wrap.ant-modal-centered > div > div.ant-modal-content > div.ant-modal-footer > button.ant-btn.css-1li46mu.ant-btn-primary") # await self.page.click("div.ant-modal-footer > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",2) # except: # pass # 각 마켓의 API 키를 업데이트 for market in self.market_xpath_mapping.keys(): if market in api_keys: await self.update_market_api_keys(market, api_keys[market]) progress_bar.setValue(progress_bar.value() + 5) status_label.setText(f'현재 상태: {market} 업데이트 완료') await self.browser.close() except Exception as e: print(f"오류발생 : {e}") # 각 마켓별 API 키 업데이트 async def update_market_api_keys(self, market, market_api_keys): print(f"update_market_api_keys || market : {market} 시도") market_xpath = f"div#rc-tabs-0-tab-{self.market_xpath_mapping[market]}" api_key_xpaths = self.api_key_xpaths[market] print(f"update_market_api_keys || market_xpath : {market_xpath} ") print(f"update_market_api_keys || api_key_xpaths : {api_key_xpaths} ") await self.page.click(market_xpath) # await self.page.wait_for_timeout(1000) # 잠시 대기 await asyncio.sleep(1) # 잠시 대기 # 주문 수집 기능 설정 print("toggle_state 시작") toggle_state = await self.page.get_attribute(self.order_collection_button[market], "aria-checked") print(f"toggle_state : {toggle_state}, self.toggle_states[market] : {self.toggle_states[market]}, ") if self.toggle_states[market] == 'on' and toggle_state == 'false': await self.page.click(self.order_collection_button[market]) elif self.toggle_states[market] == 'off' and toggle_state == 'true': await self.page.click(self.order_collection_button[market]) # API 키 입력 for key, value in api_key_xpaths.items(): api_key_value = market_api_keys.get(key, '') print(f"key : {key}") print(f"value : {value}") print(f"api_key_value : {api_key_value}") if key not in ['업로드할스마트스토어계정명', '업로드할스마트스토어계정id', '업로드할스마트스토어계정pw']: print(f"key : {key}이므로 fill 실행") await self.page.fill(value, api_key_value) # 스마트스토어의 경우 로그인 작업 수행 if market == '스마트스토어' and key == '업로드할스마트스토어계정명': print(f"스마트스토어 작업 시작") # 새 창이 열리는 이벤트를 기다림 async with self.page.context.expect_page() as new_page_info: # 새 팝업이 아닌 새창 await self.page.click(self.smartstore_elements['account_button']) print(f"page.click - account_button") popup_page = await new_page_info.value print(f"새 창 열림: {popup_page.url}") try: await popup_page.wait_for_load_state() print("새 창 로드 완료") except Exception as e: print(f"새 창 로드 중 에러 발생: {e}") traceback.print_exc() return await popup_page.evaluate('(selector) => document.querySelector(selector).scrollIntoViewIfNeeded()', self.smartstore_elements['popup_login_type_button']) await popup_page.click(self.smartstore_elements['popup_login_type_button']) print(f"page.click - popup_login_type_button") await popup_page.evaluate('(selector) => document.querySelector(selector).scrollIntoViewIfNeeded()', self.smartstore_elements['popup_login_id_input']) await popup_page.fill(self.smartstore_elements['popup_login_id_input'], market_api_keys.get('업로드할스마트스토어계정id', '')) print(f"page.fill - 업로드할스마트스토어계정id") await popup_page.fill(self.smartstore_elements['popup_login_pw_input'], market_api_keys.get('업로드할스마트스토어계정pw', '')) print(f"page.fill - 업로드할스마트스토어계정pw") await popup_page.evaluate('(selector) => document.querySelector(selector).scrollIntoViewIfNeeded()', self.smartstore_elements['popup_login_button']) await popup_page.click(self.smartstore_elements['popup_login_button']) print(f"page.click - popup_login_button") try: # 2단계 인증 처리 popup_page = await self.handle_two_factor_authentication(popup_page, market_api_keys) # 팝업 페이지가 닫혀 있는지 확인 if popup_page.is_closed(): print("팝업 페이지가 닫혔습니다. 로그인 성공으로 간주합니다.") else: # 로그인 성공 확인 login_success = await popup_page.inner_text(self.smartstore_elements['login_success_check']) if login_success != market_api_keys.get('업로드할스마트스토어계정id', ''): print("스마트스토어 로그인 실패") return # 팝업 닫기 await popup_page.close() except Exception as e: print(f"로그인 팝업윈도우 처리 중 에러발생 : {e}") traceback.print_exc() # API 검증 버튼 클릭 await self.page.click(self.api_key_validation_button[market]) # await self.page.wait_for_timeout(2000) # 잠시 대기 await asyncio.sleep(2) # 잠시 대기 # API 상태 확인 api_status = await self.page.inner_text(self.api_key_status[market]) print(f"{market} API 상태 : {api_status}") if "검증 완료" not in api_status: print(f"{market} API 검증 실패") return async def handle_two_factor_authentication(self, popup_page, market_api_keys): try: print("2단계 인증 발생 확인 중...") try: await popup_page.wait_for_load_state() # 2단계 인증 페이지가 있는지 확인 # two_factor_element = await popup_page.wait_for_selector('div#root h2', timeout=2000) # two_factor_element = await popup_page.wait_for_selector("#root > div > div.Layout_wrap__3uDBh > div > div > h2", timeout=2000) two_factor_element = await popup_page.wait_for_selector("div#root .Layout_layout__2COMk .Layout_inner__3QZZI .TwoStepCertify_h_title__1XYdA", timeout=2000) # print(f"two_factor_element 존재 : {two_factor_element}") two_factor_text = await two_factor_element.inner_text() print(f"two_factor_text : [{two_factor_text}]") print("2단계 인증 필요. 사용자에게 인증 번호 입력을 요청합니다.") # PyQt5 다이얼로그를 통해 사용자 입력 받기 self.dialog.popup_page = popup_page await self.dialog.show_dialog() while self.dialog.isVisible(): await asyncio.sleep(1) if not self.dialog.auth_method or not self.dialog.code: print("사용자가 인증을 완료하지 않았습니다.") return popup_page # 페이지 다시 로드 및 상태 확인 await popup_page.wait_for_load_state() print("2단계 인증 완료 후 페이지 리로드") # 팝업 페이지가 닫혀 있는지 확인 if popup_page.is_closed(): print("팝업 페이지가 닫혔습니다. 로그인 성공으로 간주합니다.") return popup_page # 로그인 성공 확인 login_success = await popup_page.inner_text(self.smartstore_elements['login_success_check']) if login_success != market_api_keys.get('업로드할스마트스토어계정id', ''): print("스마트스토어 로그인 실패") return await popup_page.close() except TargetClosedError: print("팝업 페이지가 닫혔습니다. 로그인 성공으로 간주합니다.") return popup_page except TimeoutError: print("2단계 인증 페이지 로드 시간 초과. 2단계 인증이 발생하지 않았습니다.") return popup_page except Exception as e: print(f"2단계 인증 처리 중 에러 발생: {e}") traceback.print_exc() return popup_page