diff --git a/src/pages/AutoLib.py b/src/pages/AutoLib.py index fab83e4..6b608e0 100644 --- a/src/pages/AutoLib.py +++ b/src/pages/AutoLib.py @@ -149,9 +149,6 @@ class AutoLib(MsgBase): except WebDriverException as e: self._showTrace(f"浏览器驱动初始化失败: {e}", self.TraceLevel.ERROR) return False - except Exception as e: - self._showTrace(f"浏览器驱动初始化失败: {e}", self.TraceLevel.ERROR) - return False self._showTrace(f"浏览器驱动已初始化, 类型: {self.__driver_type}, 路径: {self.__driver_path}") return True diff --git a/src/pages/LoginPage.py b/src/pages/LoginPage.py index db34756..3194276 100644 --- a/src/pages/LoginPage.py +++ b/src/pages/LoginPage.py @@ -85,9 +85,7 @@ class LoginPage: EC.presence_of_element_located(self.CAPTCHA_IMG) ) return True - except (NoSuchElementException, TimeoutException): - return False - except Exception: + except TimeoutException: return False def fillCredentials( @@ -104,17 +102,21 @@ class LoginPage: el.clear() el.send_keys(password) return True - except (NoSuchElementException, TimeoutException): - return False - except Exception: + except (NoSuchElementException, ElementNotInteractableException): return False def getCaptchaImageSrc( self, - ) -> str: + ) -> str | None: - captcha_el = self._driver.find_element(*self.CAPTCHA_IMG) - return captcha_el.get_attribute("src") + # return 'None' if captcha image element is not found. + # But the 'get_attribute("src")' also return 'None' if there's no attribute with + # that name, which is not what we want. + try: + captcha_el = self._driver.find_element(*self.CAPTCHA_IMG) + return captcha_el.get_attribute("src") + except NoSuchElementException: + return None def refreshCaptcha( self, @@ -123,10 +125,7 @@ class LoginPage: try: self._driver.find_element(*self.CAPTCHA_IMG).click() return True - except (NoSuchElementException, TimeoutException, - ElementNotInteractableException): - return False - except Exception: + except (NoSuchElementException, ElementNotInteractableException): return False def fillCaptcha( @@ -139,9 +138,7 @@ class LoginPage: el.clear() el.send_keys(captcha_text) return True - except (NoSuchElementException, TimeoutException): - return False - except Exception: + except (NoSuchElementException, ElementNotInteractableException): return False def clickLogin( @@ -151,10 +148,7 @@ class LoginPage: try: self._driver.find_element(*self.LOGIN_BUTTON).click() return True - except (NoSuchElementException, TimeoutException, - ElementNotInteractableException): - return False - except Exception: + except (NoSuchElementException, ElementNotInteractableException): return False def waitLoginSuccess( @@ -172,9 +166,7 @@ class LoginPage: EC.presence_of_element_located(self.SUCCESS_INDICATOR_CONTENT) ) return True - except (NoSuchElementException, TimeoutException): - return False - except Exception: + except TimeoutException: return False def stopPageLoad( diff --git a/src/pages/MainShell.py b/src/pages/MainShell.py index a9b6247..9e3bf18 100644 --- a/src/pages/MainShell.py +++ b/src/pages/MainShell.py @@ -14,8 +14,10 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.remote.webdriver import WebDriver from selenium.common.exceptions import ( + ElementNotInteractableException, NoSuchElementException, TimeoutException, + WebDriverException, ) from pages.ReserveView import ReserveView @@ -38,6 +40,15 @@ class MainShell: self._driver = driver + def _clickTab( + self, + locator: tuple, + ) -> None: + + WebDriverWait(self._driver, 2).until( + EC.element_to_be_clickable(locator) + ).click() + def gotoReserveView( self, ) -> ReserveView: @@ -65,9 +76,7 @@ class MainShell: try: self._driver.find_element(*self.TAB_LOGOUT).click() return True - except NoSuchElementException: - return False - except Exception: + except (NoSuchElementException, ElementNotInteractableException): return False def waitCheckinButton( @@ -81,8 +90,6 @@ class MainShell: return True except TimeoutException: return False - except Exception: - return False def waitExtendButton( self, @@ -95,40 +102,50 @@ class MainShell: return True except TimeoutException: return False - except Exception: - return False def isCheckinButtonDisabled( self, ) -> bool: - btn = self._driver.find_element(*self.BTN_CHECKIN) - return "disabled" in btn.get_attribute("class") + try: + btn = self._driver.find_element(*self.BTN_CHECKIN) + return "disabled" in btn.get_attribute("class") + except NoSuchElementException: + return True def isExtendButtonDisabled( self, ) -> bool: - btn = self._driver.find_element(*self.BTN_EXTEND) - return "disabled" in btn.get_attribute("class") + try: + btn = self._driver.find_element(*self.BTN_EXTEND) + return "disabled" in btn.get_attribute("class") + except NoSuchElementException: + return True def clickCheckinButton( self, ) -> None: - btn = WebDriverWait(self._driver, 2).until( - EC.element_to_be_clickable(self.BTN_CHECKIN) - ) - btn.click() + try: + btn = WebDriverWait(self._driver, 2).until( + EC.element_to_be_clickable(self.BTN_CHECKIN) + ) + btn.click() + except (TimeoutException, ElementNotInteractableException): + return def clickExtendButton( self, ) -> None: - btn = WebDriverWait(self._driver, 2).until( - EC.element_to_be_clickable(self.BTN_EXTEND) - ) - btn.click() + try: + btn = WebDriverWait(self._driver, 2).until( + EC.element_to_be_clickable(self.BTN_EXTEND) + ) + btn.click() + except (TimeoutException, ElementNotInteractableException): + return def enableCheckinButtonByJS( self, @@ -154,13 +171,7 @@ class MainShell: self, ) -> None: - self._driver.refresh() - - def _clickTab( - self, - locator: tuple, - ) -> None: - - WebDriverWait(self._driver, 2).until( - EC.element_to_be_clickable(locator) - ).click() + try: + self._driver.refresh() + except (TimeoutException, WebDriverException): + return diff --git a/src/pages/RecordsView.py b/src/pages/RecordsView.py index dc42faa..2c6d5d6 100644 --- a/src/pages/RecordsView.py +++ b/src/pages/RecordsView.py @@ -44,8 +44,6 @@ class RecordsView: return self._driver.find_elements(*self.RECORDS_LIST) except TimeoutException: return None - except Exception: - return None def getRecordTimeElement( self, @@ -71,8 +69,6 @@ class RecordsView: ) except TimeoutException: return False - except Exception: - return False try: more_btn = self._driver.find_element(*self.MORE_BTN) if more_btn.is_displayed() and more_btn.is_enabled(): @@ -82,8 +78,6 @@ class RecordsView: return False except (NoSuchElementException, StaleElementReferenceException): return False - except Exception: - return False def getRecordText( self, diff --git a/src/pages/ReserveView.py b/src/pages/ReserveView.py index 1f7e459..c8f1366 100644 --- a/src/pages/ReserveView.py +++ b/src/pages/ReserveView.py @@ -53,6 +53,50 @@ class ReserveView: self._driver = driver + def _clickOptionByJS( + self, + trigger_id: str, + option_css: str, + ) -> bool: + + script = f""" + try {{ + var trigger = document.getElementById('{trigger_id}'); + if (trigger) {{ + trigger.click(); + var option = document.querySelector("{option_css}"); + if (option) {{ + option.click(); + return true; + }} + return false; + }} + return false; + }} catch (e) {{ + return false; + }} + """ + result = self._driver.execute_script(script) + time.sleep(0.1) + return result + + def _clickOption( + self, + trigger: tuple, + option: tuple, + ) -> bool: + + try: + WebDriverWait(self._driver, 2).until( + EC.element_to_be_clickable(trigger) + ).click() + WebDriverWait(self._driver, 2).until( + EC.element_to_be_clickable(option) + ).click() + return True + except (TimeoutException, ElementNotInteractableException): + return False + def selectDate( self, date_str: str, @@ -109,21 +153,15 @@ class ReserveView: ).click() except (TimeoutException, ElementNotInteractableException): return None - except Exception: - return None try: WebDriverWait(self._driver, 2).until( EC.element_to_be_clickable((By.ID, self.ROOM_BTN_FMT.format(room=room))) ).click() except (TimeoutException, ElementNotInteractableException): return None - except Exception: - return None try: return SeatMapDialog(self._driver) - except (TimeoutException): - return None - except Exception: + except TimeoutException: return None def submitReserve( @@ -137,57 +175,12 @@ class ReserveView: return True except (TimeoutException, ElementNotInteractableException): return False - except Exception: - return False def refresh( self, ) -> None: - self._driver.refresh() - - def _clickOptionByJS( - self, - trigger_id: str, - option_css: str, - ) -> bool: - - script = f""" - try {{ - var trigger = document.getElementById('{trigger_id}'); - if (trigger) {{ - trigger.click(); - var option = document.querySelector("{option_css}"); - if (option) {{ - option.click(); - return true; - }} - return false; - }} - return false; - }} catch (e) {{ - return false; - }} - """ - result = self._driver.execute_script(script) - time.sleep(0.1) - return result - - def _clickOption( - self, - trigger: tuple, - option: tuple, - ) -> bool: - try: - WebDriverWait(self._driver, 2).until( - EC.element_to_be_clickable(trigger) - ).click() - WebDriverWait(self._driver, 2).until( - EC.element_to_be_clickable(option) - ).click() - return True - except (TimeoutException, ElementNotInteractableException): - return False - except Exception: - return False + self._driver.refresh() + except TimeoutException: + return diff --git a/src/pages/components/CheckinResultDialog.py b/src/pages/components/CheckinResultDialog.py index 7843f41..b8252b1 100644 --- a/src/pages/components/CheckinResultDialog.py +++ b/src/pages/components/CheckinResultDialog.py @@ -45,9 +45,8 @@ class CheckinResultDialog(Dialog): self._waitPresence(self.RESULT_MSG) el = self._find(*self.RESULT_MSG) return el.text - except (TimeoutException, NoSuchElementException, StaleElementReferenceException): - return "" - except Exception: + except (TimeoutException, NoSuchElementException, + StaleElementReferenceException): return "" def getDetails( @@ -59,8 +58,6 @@ class CheckinResultDialog(Dialog): return [el.text for el in elements if el.text.strip()] except (NoSuchElementException, StaleElementReferenceException): return [] - except Exception: - return [] def clickOk( self, @@ -69,7 +66,5 @@ class CheckinResultDialog(Dialog): try: self._waitClickable(self.OK_BTN).click() return True - except (NoSuchElementException, TimeoutException, ElementNotInteractableException): - return False - except Exception: + except (TimeoutException, ElementNotInteractableException): return False diff --git a/src/pages/components/RenewDialog.py b/src/pages/components/RenewDialog.py index 150eca1..3628f1a 100644 --- a/src/pages/components/RenewDialog.py +++ b/src/pages/components/RenewDialog.py @@ -10,6 +10,7 @@ See the LICENSE file for details. from selenium.common.exceptions import ( ElementNotInteractableException, NoSuchElementException, + StaleElementReferenceException, TimeoutException, ) from selenium.webdriver.common.by import By @@ -50,9 +51,7 @@ class RenewDialog(Dialog): self._waitVisible(self.ROOT) self._waitPresence(self.MESSAGE_HEAD) self._waitPresence(self.RESULT_MSG) - except (NoSuchElementException, TimeoutException): - return False - except Exception: + except TimeoutException: return False head_msg = self._find(*self.MESSAGE_HEAD).text.strip() if "警告" in head_msg: @@ -60,9 +59,7 @@ class RenewDialog(Dialog): try: self._waitAllPresence(self.TIME_OPTS) self._waitPresence(self.OK_BTN) - except (NoSuchElementException, TimeoutException): - return False - except Exception: + except TimeoutException: return False return True @@ -70,13 +67,19 @@ class RenewDialog(Dialog): self, ) -> str: - return self._find(*self.MESSAGE_HEAD).text.strip() + try: + return self._find(*self.MESSAGE_HEAD).text.strip() + except (NoSuchElementException, StaleElementReferenceException): + return "" def getResultMessage( self, ) -> str: - return self._find(*self.RESULT_MSG).text.strip() + try: + return self._find(*self.RESULT_MSG).text.strip() + except (NoSuchElementException, StaleElementReferenceException): + return "" def getTimeOptions( self, @@ -101,7 +104,10 @@ class RenewDialog(Dialog): prefer_earlier, ) if result.selected_index >= 0: - all_time_opts[result.selected_index].click() + try: + all_time_opts[result.selected_index].click() + except (ElementNotInteractableException, StaleElementReferenceException): + return TimeSelectionResult(free_times=result.free_times) return result def getOkButton( @@ -117,8 +123,5 @@ class RenewDialog(Dialog): try: self._find(*self.OK_BTN).click() return True - except (NoSuchElementException, TimeoutException, - ElementNotInteractableException): - return False - except Exception: + except (NoSuchElementException, ElementNotInteractableException): return False diff --git a/src/pages/components/ReserveResultDialog.py b/src/pages/components/ReserveResultDialog.py index 8ddb858..dbe8338 100644 --- a/src/pages/components/ReserveResultDialog.py +++ b/src/pages/components/ReserveResultDialog.py @@ -45,8 +45,6 @@ class ReserveResultDialog(Dialog): return self._find(*self._titleLocator()).text except (NoSuchElementException, StaleElementReferenceException): return "" - except Exception: - return "" def isSuccess( self, @@ -77,5 +75,3 @@ class ReserveResultDialog(Dialog): return [el.text for el in elements if el.text.strip()] except (NoSuchElementException, StaleElementReferenceException): return [] - except Exception: - return [] diff --git a/src/pages/components/SeatMapDialog.py b/src/pages/components/SeatMapDialog.py index 9f9a5d5..23f82ed 100644 --- a/src/pages/components/SeatMapDialog.py +++ b/src/pages/components/SeatMapDialog.py @@ -43,9 +43,7 @@ class SeatMapDialog(Dialog): try: self._waitAllPresence(self.SEAT_ITEMS) - except (NoSuchElementException, TimeoutException): - return None - except Exception: + except TimeoutException: return None try: seat_el = self._find(By.ID, f"seat_{int(seat_id):03d}") @@ -58,8 +56,6 @@ class SeatMapDialog(Dialog): except (NoSuchElementException, ValueError, TimeoutException, ElementNotInteractableException, StaleElementReferenceException): pass - except Exception: - pass try: all_seats = self._findAll(*self.SEAT_ITEMS) seat_id_upper = seat_id.lstrip('0').upper() @@ -76,5 +72,3 @@ class SeatMapDialog(Dialog): except (NoSuchElementException, TimeoutException, ElementNotInteractableException, StaleElementReferenceException): return None - except Exception: - return None diff --git a/src/pages/components/TimeSelectDialog.py b/src/pages/components/TimeSelectDialog.py index 1a843a3..f911591 100644 --- a/src/pages/components/TimeSelectDialog.py +++ b/src/pages/components/TimeSelectDialog.py @@ -13,7 +13,8 @@ import logging from typing import TYPE_CHECKING, Callable, Optional from selenium.common.exceptions import ( - NoSuchElementException, + ElementNotInteractableException, + StaleElementReferenceException, TimeoutException, ) from selenium.webdriver.common.by import By @@ -106,9 +107,7 @@ class TimeSelectDialog(Dialog): self._waitAllPresence( (By.CSS_SELECTOR, f"#{time_id} ul li a") ) - except (NoSuchElementException, TimeoutException): - return [] - except Exception: + except TimeoutException: return [] return self._findAll( By.CSS_SELECTOR, @@ -133,7 +132,10 @@ class TimeSelectDialog(Dialog): prefer_earlier, ) if result.selected_index >= 0: - all_time_opts[result.selected_index].click() + try: + all_time_opts[result.selected_index].click() + except (ElementNotInteractableException, StaleElementReferenceException): + return TimeSelectionResult(free_times=result.free_times) return result def selectTimeRange( diff --git a/src/pages/flows/CheckinFlow.py b/src/pages/flows/CheckinFlow.py index bb0689e..73ee581 100644 --- a/src/pages/flows/CheckinFlow.py +++ b/src/pages/flows/CheckinFlow.py @@ -10,6 +10,7 @@ See the LICENSE file for details. import queue from selenium.common.exceptions import ( + ElementNotInteractableException, NoSuchElementException, TimeoutException, ) @@ -83,9 +84,6 @@ class CheckinFlow(MsgBase): dialog.clickOk() self._showTrace(f"用户 {username} 签到失败 !", self.TraceLevel.ERROR) return False - except (NoSuchElementException, TimeoutException): - self._showTrace("签到时发生未知错误 !", self.TraceLevel.ERROR) - return False - except Exception: + except (TimeoutException, NoSuchElementException, ElementNotInteractableException): self._showTrace("签到时发生未知错误 !", self.TraceLevel.ERROR) return False diff --git a/src/pages/flows/RenewFlow.py b/src/pages/flows/RenewFlow.py index b8f6421..e52dd52 100644 --- a/src/pages/flows/RenewFlow.py +++ b/src/pages/flows/RenewFlow.py @@ -39,6 +39,28 @@ class RenewFlow(MsgBase): self._driver: WebDriver = driver self._shell: MainShell = shell + def _validateRenewTime( + self, + end_time: str, + target_renew_mins: int, + ) -> bool: + + if target_renew_mins > self.LIBRARY_CLOSE_MINS: + actual_renew_duration = self.LIBRARY_CLOSE_MINS - timeStrToMins(end_time) + if actual_renew_duration <= 0: + self._showTrace( + f"当前结束时间 {end_time} 已接近闭馆时间,无法续约 !", self.TraceLevel.ERROR + ) + return False + self._showTrace( + f"续约时间已调整至闭馆时间 " + f"{minsToTimeStr(self.LIBRARY_CLOSE_MINS)}," + f"实际续约时长为 " + f"{actual_renew_duration // 60} 小时 " + f"{actual_renew_duration % 60} 分钟" + ) + return True + def execute( self, username: str, @@ -106,37 +128,7 @@ class RenewFlow(MsgBase): self._showTrace(f"当前可供续约的时间有: {result.free_times}") self._shell.refresh() return False - except (NoSuchElementException, TimeoutException) as e: + except (NoSuchElementException, TimeoutException, ElementNotInteractableException) as e: self._showTrace(f"用户 {username} 续约失败 ! : {e}", self.TraceLevel.ERROR) self._shell.refresh() return False - except (ElementNotInteractableException) as e: - self._showTrace(f"用户 {username} 续约失败 ! : {e}", self.TraceLevel.ERROR) - self._shell.refresh() - return False - except Exception as e: - self._showTrace(f"用户 {username} 续约失败 ! : {e}", self.TraceLevel.ERROR) - self._shell.refresh() - return False - - def _validateRenewTime( - self, - end_time: str, - target_renew_mins: int, - ) -> bool: - - if target_renew_mins > self.LIBRARY_CLOSE_MINS: - actual_renew_duration = self.LIBRARY_CLOSE_MINS - timeStrToMins(end_time) - if actual_renew_duration <= 0: - self._showTrace( - f"当前结束时间 {end_time} 已接近闭馆时间,无法续约 !", self.TraceLevel.ERROR - ) - return False - self._showTrace( - f"续约时间已调整至闭馆时间 " - f"{minsToTimeStr(self.LIBRARY_CLOSE_MINS)}," - f"实际续约时长为 " - f"{actual_renew_duration // 60} 小时 " - f"{actual_renew_duration % 60} 分钟" - ) - return True diff --git a/src/pages/flows/ReserveFlow.py b/src/pages/flows/ReserveFlow.py index 1722903..88a2864 100644 --- a/src/pages/flows/ReserveFlow.py +++ b/src/pages/flows/ReserveFlow.py @@ -12,7 +12,6 @@ from dataclasses import dataclass from selenium.common.exceptions import ( ElementNotInteractableException, - NoSuchElementException, TimeoutException, ) from selenium.webdriver.remote.webdriver import WebDriver @@ -70,10 +69,7 @@ class ReserveFlow(MsgBase): try: view = self._shell.gotoReserveView() - except (NoSuchElementException, TimeoutException) as e: - self._showTrace(f"加载预约选座页面失败 ! : {e}", self.TraceLevel.ERROR) - return False - except Exception as e: + except (TimeoutException, ElementNotInteractableException) as e: self._showTrace(f"加载预约选座页面失败 ! : {e}", self.TraceLevel.ERROR) return False if not view.selectDate(ctx.date): @@ -140,8 +136,6 @@ class ReserveFlow(MsgBase): self._showTrace("预约结果加载失败 !", self.TraceLevel.ERROR) except (TimeoutException, ElementNotInteractableException): self._showTrace("预约提交失败 !", self.TraceLevel.ERROR) - except Exception: - self._showTrace("预约提交失败 !", self.TraceLevel.ERROR) if not submit_reserve and have_hover_on_page: view.refresh() if reserve_success: