diff --git a/DrissionPage/_base/chromium_driver.py b/DrissionPage/_base/chromium_driver.py index 4985a98..50dae8a 100644 --- a/DrissionPage/_base/chromium_driver.py +++ b/DrissionPage/_base/chromium_driver.py @@ -24,7 +24,7 @@ class ChromiumDriver(object): self.address = address self.type = tab_type self._debug = False - self.has_alert = False + self.alert_flag = False # 标记alert出现,跳过一条请求后复原 self._websocket_url = f'ws://{address}/devtools/{tab_type}/{tab_id}' self._cur_id = 0 @@ -77,8 +77,9 @@ class ChromiumDriver(object): return self.method_results[message['id']].get_nowait() except Empty: - if self.has_alert: - return {'error': {'message': 'alert exists'}, 'type': 'alert_exists'} + if self.alert_flag: + self.alert_flag = False + return {'result': []} if timeout is not None and perf_counter() > timeout: return {'error': {'message': 'timeout'}} @@ -114,7 +115,10 @@ class ChromiumDriver(object): print(f'<收 {msg_json}') break - if "method" in msg: + if 'method' in msg: + if msg['method'].startswith('Page.javascriptDialog'): + self.alert_flag = msg['method'].endswith('Opening') + self.event_queue.put(msg) elif msg.get('id') in self.method_results: diff --git a/DrissionPage/_base/chromium_driver.pyi b/DrissionPage/_base/chromium_driver.pyi index aeba4f9..378879b 100644 --- a/DrissionPage/_base/chromium_driver.pyi +++ b/DrissionPage/_base/chromium_driver.pyi @@ -23,7 +23,7 @@ class ChromiumDriver(object): address: str type: str _debug: bool - has_alert: bool + alert_flag: bool _websocket_url: str _cur_id: int _ws: Optional[WebSocket] diff --git a/DrissionPage/_commons/constants.py b/DrissionPage/_commons/constants.py index 9192448..a1c8054 100644 --- a/DrissionPage/_commons/constants.py +++ b/DrissionPage/_commons/constants.py @@ -5,7 +5,6 @@ """ from ..errors import ElementNotFoundError -HANDLE_ALERT_METHOD = 'Page.handleJavaScriptDialog' FRAME_ELEMENT = ('iframe', 'frame') ERROR = 'error' diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 260eba3..68ee961 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -18,7 +18,7 @@ from .._units.clicker import Clicker from .._units.setter import ChromiumElementSetter from .._units.waiter import ChromiumElementWaiter from ..errors import ContextLossError, ElementLossError, JavaScriptError, ElementNotFoundError, \ - CDPError, NoResourceError, NoRectError + CDPError, NoResourceError, NoRectError, AlertExistsError class ChromiumElement(DrissionElement): @@ -206,6 +206,14 @@ class ChromiumElement(DrissionElement): return self._select + def check(self, uncheck=False): + """选中或取消选中当前元素 + :param uncheck: 是否取消选中 + :return: None + """ + js = 'this.checked=false' if uncheck else 'this.checked=true' + self.run_js(js) + def parent(self, level_or_loc=1, index=1): """返回上面某一级父元素,可指定层数或用查询语法定位 :param level_or_loc: 第几级父元素,或定位符 @@ -1303,6 +1311,9 @@ def run_js(page_or_ele, script, as_expr=False, timeout=None, args=None): obj_id = page_or_ele._root_id is_page = True + if page.has_alert: + raise AlertExistsError + try: if as_expr: res = page.run_cdp('Runtime.evaluate', expression=script, returnByValue=False, diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index b10dd0a..3f584a3 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -150,6 +150,8 @@ class ChromiumElement(DrissionElement): @property def select(self) -> ChromiumSelect: ... + def check(self, uncheck: bool = False) -> None: ... + def attr(self, attr: str) -> Union[str, None]: ... def remove_attr(self, attr: str) -> None: ... diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index d8df4e9..5595dac 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -15,7 +15,7 @@ from requests import get from .._base.base import BasePage from .._base.chromium_driver import ChromiumDriver -from .._commons.constants import HANDLE_ALERT_METHOD, ERROR, NoneElement +from .._commons.constants import ERROR, NoneElement from .._commons.locator import get_loc from .._commons.tools import get_usable_path, clean_folder from .._commons.web import location_in_viewport @@ -47,6 +47,7 @@ class ChromiumBase(BasePage): self._screencast = None self._actions = None self._listener = None + self._has_alert = False self._download_path = str(Path('../..').absolute()) @@ -103,6 +104,9 @@ class ChromiumBase(BasePage): if is_init and hasattr(self, '_driver'): return # ChromiumPage接收ChromiumDriver方式启动时 self._driver = ChromiumDriver(tab_id=tab_id, tab_type='page', address=self.address) + self._alert = Alert() + self._driver.set_listener('Page.javascriptDialogOpening', self._on_alert_open) + self._driver.set_listener('Page.javascriptDialogClosed', self._on_alert_close) self._driver.call_method('DOM.enable') self._driver.call_method('Page.enable') @@ -429,14 +433,19 @@ class ChromiumBase(BasePage): self._listener = NetworkListener(self) return self._listener + @property + def has_alert(self): + """返回是否存在提示框""" + return self._has_alert + def run_cdp(self, cmd, **cmd_args): """执行Chrome DevTools Protocol语句 :param cmd: 协议项目 :param cmd_args: 参数 :return: 执行的结果 """ - if self.driver.has_alert and cmd != HANDLE_ALERT_METHOD: - raise AlertExistsError + # if self.driver.has_alert and cmd != HANDLE_ALERT_METHOD: + # raise AlertExistsError r = self.driver.call_method(cmd, **cmd_args) if ERROR not in r: @@ -824,6 +833,48 @@ class ChromiumBase(BasePage): if cookies: self.run_cdp_loaded('Network.clearBrowserCookies') + def handle_alert(self, accept=True, send=None, timeout=None): + """处理提示框,可以自动等待提示框出现 + :param accept: True表示确认,False表示取消,其它值不会按按钮但依然返回文本值 + :param send: 处理prompt提示框时可输入文本 + :param timeout: 等待提示框出现的超时时间,为None则使用self.timeout属性的值 + :return: 提示框内容文本,未等到提示框则返回False + """ + timeout = self.timeout if timeout is None else timeout + timeout = .1 if timeout <= 0 else timeout + end_time = perf_counter() + timeout + while not self._alert.activated and perf_counter() < end_time: + sleep(.1) + if not self._alert.activated: + return False + + res_text = self._alert.text + if self._alert.type == 'prompt': + self.driver.call_method('Page.handleJavaScriptDialog', accept=accept, promptText=send) + else: + self.driver.call_method('Page.handleJavaScriptDialog', accept=accept) + return res_text + + def _on_alert_close(self, **kwargs): + """alert关闭时触发的方法""" + self._alert.activated = False + self._alert.text = None + self._alert.type = None + self._alert.defaultPrompt = None + self._alert.response_accept = kwargs.get('result') + self._alert.response_text = kwargs['userInput'] + self._has_alert = False + + def _on_alert_open(self, **kwargs): + """alert出现时触发的方法""" + self._alert.activated = True + self._alert.text = kwargs['message'] + self._alert.type = kwargs['message'] + self._alert.defaultPrompt = kwargs.get('defaultPrompt', None) + self._alert.response_accept = None + self._alert.response_text = None + self._has_alert = True + def _d_connect(self, to_url, times=0, interval=1, show_errmsg=False, timeout=None): """尝试连接,重试若干次 :param to_url: 要访问的url @@ -1166,3 +1217,15 @@ class ScreencastMode(object): def imgs_mode(self): self._screencast._mode = 'imgs' + + +class Alert(object): + """用于保存alert信息的类""" + + def __init__(self): + self.activated = False + self.text = None + self.type = None + self.defaultPrompt = None + self.response_accept = None + self.response_text = None diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index f036256..edabfa1 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -47,10 +47,12 @@ class ChromiumBase(BasePage): self._screencast: Screencast = ... self._actions: ActionChains = ... self._listener: NetworkListener = ... + self._alert: Alert = ... + self._has_alert: bool = ... def _connect_browser(self, tab_id: str = None) -> None: ... - def _driver_init(self, tab_id: str, is_init:bool=True) -> None: ... + def _driver_init(self, tab_id: str, is_init: bool = True) -> None: ... def _get_document(self) -> None: ... @@ -156,6 +158,9 @@ class ChromiumBase(BasePage): @property def listen(self) -> NetworkListener: ... + @property + def has_alert(self) -> bool: ... + def run_js(self, script: str, *args: Any, as_expr: bool = False) -> Any: ... def run_js_loaded(self, script: str, *args: Any, as_expr: bool = False) -> Any: ... @@ -226,6 +231,12 @@ class ChromiumBase(BasePage): cache: bool = True, cookies: bool = True) -> None: ... + def handle_alert(self, accept: bool = True, send: str = None, timeout: float = None) -> Union[str, False]: ... + + def _on_alert_close(self, **kwargs): ... + + def _on_alert_open(self, **kwargs): ... + def _d_connect(self, to_url: str, times: int = 0, @@ -286,3 +297,14 @@ class ScreencastMode(object): def frugal_imgs_mode(self) -> None: ... def imgs_mode(self) -> None: ... + + +class Alert(object): + + def __init__(self): + self.activated: bool = ... + self.text: str = ... + self.type: str = ... + self.defaultPrompt: str = ... + self.response_accept: str = ... + self.response_text: str = ... diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index 6fd1026..98e67ef 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -4,7 +4,7 @@ @Contact : g1879@qq.com """ from pathlib import Path -from time import perf_counter, sleep +from time import sleep from requests import get @@ -85,13 +85,8 @@ class ChromiumPage(ChromiumBase): def _page_init(self): """浏览器相关设置""" - self._alert = Alert() - self._driver.set_listener('Page.javascriptDialogOpening', self._on_alert_open) - self._driver.set_listener('Page.javascriptDialogClosed', self._on_alert_close) - self._rect = None self._main_tab = self.tab_id - self._browser.connect_to_page() @property @@ -284,64 +279,10 @@ class ChromiumPage(ChromiumBase): """ self.close_tabs(tabs_or_ids, True) - def handle_alert(self, accept=True, send=None, timeout=None): - """处理提示框,可以自动等待提示框出现 - :param accept: True表示确认,False表示取消,其它值不会按按钮但依然返回文本值 - :param send: 处理prompt提示框时可输入文本 - :param timeout: 等待提示框出现的超时时间,为None则使用self.timeout属性的值 - :return: 提示框内容文本,未等到提示框则返回False - """ - timeout = self.timeout if timeout is None else timeout - timeout = .1 if timeout <= 0 else timeout - end_time = perf_counter() + timeout - while not self._alert.activated and perf_counter() < end_time: - sleep(.1) - if not self._alert.activated: - return False - - res_text = self._alert.text - if self._alert.type == 'prompt': - self.driver.call_method('Page.handleJavaScriptDialog', accept=accept, promptText=send) - else: - self.driver.call_method('Page.handleJavaScriptDialog', accept=accept) - return res_text - def quit(self): """关闭浏览器""" self.browser.quit() - def _on_alert_close(self, **kwargs): - """alert关闭时触发的方法""" - self._alert.activated = False - self._alert.text = None - self._alert.type = None - self._alert.defaultPrompt = None - self._alert.response_accept = kwargs.get('result') - self._alert.response_text = kwargs['userInput'] - self._driver.has_alert = False - - def _on_alert_open(self, **kwargs): - """alert出现时触发的方法""" - self._alert.activated = True - self._alert.text = kwargs['message'] - self._alert.type = kwargs['message'] - self._alert.defaultPrompt = kwargs.get('defaultPrompt', None) - self._alert.response_accept = None - self._alert.response_text = None - self._driver.has_alert = True - - -class Alert(object): - """用于保存alert信息的类""" - - def __init__(self): - self.activated = False - self.text = None - self.type = None - self.defaultPrompt = None - self.response_accept = None - self.response_text = None - def get_rename(original, rename): if '.' in rename: diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 15b6714..bf82ca6 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -23,7 +23,6 @@ class ChromiumPage(ChromiumBase): timeout: float = None): self._driver_options: ChromiumOptions = ... self._main_tab: str = ... - self._alert: Alert = ... self._browser: Browser = ... self._rect: Optional[ChromiumTabRect] = ... @@ -83,24 +82,7 @@ class ChromiumPage(ChromiumBase): def close_other_tabs(self, tabs_or_ids: Union[ str, ChromiumTab, List[Union[str, ChromiumTab]], Tuple[Union[str, ChromiumTab]]] = None) -> None: ... - def handle_alert(self, accept: bool = True, send: str = None, timeout: float = None) -> Union[str, False]: ... - def quit(self) -> None: ... - def _on_alert_close(self, **kwargs): ... - - def _on_alert_open(self, **kwargs): ... - - -class Alert(object): - - def __init__(self): - self.activated: bool = ... - self.text: str = ... - self.type: str = ... - self.defaultPrompt: str = ... - self.response_accept: str = ... - self.response_text: str = ... - def get_rename(original: str, rename: str) -> str: ... diff --git a/README.md b/README.md index 8d5fe9e..49e9d7f 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ python 版本:3.6 及以上 # 🛠 如何使用 -**📖 使用文档:** [点击查看](http://g1879.gitee.io/drissionpagedocs) +**📖 使用文档:** [点击查看](https://g1879.gitee.io/drissionpagedocs) -**交流 QQ 群:** 897838127[已满]、558778073 +**交流 QQ 群:** 897838127[已满]、558778073[已满]、636361957 ---