From 2c0595e57a1fb4971fccb41e52f0de2f6abe037c Mon Sep 17 00:00:00 2001 From: g1879 Date: Sun, 12 Feb 2023 23:07:26 +0800 Subject: [PATCH] =?UTF-8?q?wait=5Fele()=E5=92=8Cscroll=5Fto=5Fsee()?= =?UTF-8?q?=E5=BC=83=E7=94=A8=EF=BC=8C=E6=95=B4=E5=90=88=E5=88=B0wait?= =?UTF-8?q?=E5=92=8Cscroll=E5=B1=9E=E6=80=A7=EF=BC=8C=E5=BE=85=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/action_chains.py | 2 +- DrissionPage/chromium_base.py | 141 +++++++++++++++++++++--------- DrissionPage/chromium_base.pyi | 28 +++++- DrissionPage/chromium_element.py | 86 ++++++++++++------ DrissionPage/chromium_element.pyi | 19 +++- DrissionPage/chromium_frame.py | 16 +++- DrissionPage/chromium_frame.pyi | 14 ++- 7 files changed, 227 insertions(+), 79 deletions(-) diff --git a/DrissionPage/action_chains.py b/DrissionPage/action_chains.py index f6b5196..80403a7 100644 --- a/DrissionPage/action_chains.py +++ b/DrissionPage/action_chains.py @@ -37,7 +37,7 @@ class ActionChains: ly = ele_or_loc[1] + offset_y elif isinstance(ele_or_loc, str) or 'ChromiumElement' in str(type(ele_or_loc)): ele_or_loc = self.page(ele_or_loc) - self.page.scroll_to_see(ele_or_loc) + self.page.scroll.to_see(ele_or_loc) x, y = ele_or_loc.location if offset_x or offset_y else ele_or_loc.midpoint lx = x + offset_x ly = y + offset_y diff --git a/DrissionPage/chromium_base.py b/DrissionPage/chromium_base.py index 7f5ac2e..1825e80 100644 --- a/DrissionPage/chromium_base.py +++ b/DrissionPage/chromium_base.py @@ -6,13 +6,15 @@ from json import loads from pathlib import Path from time import perf_counter, sleep +from warnings import warn from requests import Session from .functions.tools import get_usable_path from .base import BasePage from .chromium_driver import ChromiumDriver -from .chromium_element import ChromiumElementWaiter, ChromiumScroll, ChromiumElement, run_js, make_chromium_ele +from .chromium_element import ChromiumWaiter, ChromiumScroll, ChromiumElement, run_js, make_chromium_ele, \ + ChromiumElementWaiter from .functions.locator import get_loc from .functions.web import offset_scroll, cookies_to_tuple from .session_element import make_session_ele @@ -306,9 +308,9 @@ class ChromiumBase(BasePage): @property def scroll(self): """返回用于滚动滚动条的对象""" - self._wait_loaded() + self.wait.load_complete() # todo: 用run_js()负责等待,这里删除 if not hasattr(self, '_scroll'): - self._scroll = ChromiumScroll(self) + self._scroll = ChromiumPageScroll(self) return self._scroll @property @@ -326,6 +328,11 @@ class ChromiumBase(BasePage): """返回等待上传文件列表""" return self._upload_list + @property + def wait(self): + """返回用于等待的对象""" + return ChromiumPageWaiter(self) + def set_timeouts(self, implicit=None, page_load=None, script=None): """设置超时时间,单位为秒 :param implicit: 查找元素超时时间 @@ -378,20 +385,6 @@ class ChromiumBase(BasePage): timeout=timeout) return self._url_available - def wait_loading(self, timeout=None): - """阻塞程序,等待页面进入加载状态 - :param timeout: 超时时间 - :return: 等待结束时是否进入加载状态 - """ - if timeout != 0: - timeout = self.timeout if timeout in (None, True) else timeout - end_time = perf_counter() + timeout - while perf_counter() < end_time: - if self.is_loading: - return True - sleep(.005) - return False - def get_cookies(self, as_dict=False): """获取cookies信息 :param as_dict: 为True时返回由{name: value}键值对组成的dict @@ -503,29 +496,6 @@ class ChromiumBase(BasePage): else: return [make_chromium_ele(self, node_id=i) for i in nodeIds['nodeIds']] - def wait_ele(self, loc_or_ele, timeout=None): - """返回用于等待元素到达某个状态的等待器对象 - :param loc_or_ele: 可以是元素、查询字符串、loc元组 - :param timeout: 等待超时时间 - :return: 用于等待的ElementWaiter对象 - """ - return ChromiumElementWaiter(self, loc_or_ele, timeout) - - def scroll_to_see(self, loc_or_ele): - """滚动页面直到元素可见 - :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串(详见ele函数注释) - :return: None - """ - ele = self.ele(loc_or_ele) - node_id = ele.node_id - try: - self._wait_driver.DOM.scrollIntoViewIfNeeded(nodeId=node_id) - except Exception: - self.ele(loc_or_ele).run_js("this.scrollIntoView();") - - if not ele.is_in_viewport: - offset_scroll(ele, 0, 0) - def refresh(self, ignore_cache=False): """刷新当前页面 :param ignore_cache: 是否忽略缓存 @@ -533,7 +503,7 @@ class ChromiumBase(BasePage): """ self._is_loading = True self.driver.Page.reload(ignoreCache=ignore_cache) - self.wait_loading() + self.wait.load_start() def forward(self, steps=1): """在浏览历史中前进若干步 @@ -597,7 +567,7 @@ class ChromiumBase(BasePage): return r if 'Cannot find context with specified id' in r['error']: - raise RuntimeError('页面被刷新,请操作前尝试等待页面刷新或加载完成,可尝试wait.load_complete()方法。') + raise RuntimeError('页面被刷新,请操作前尝试等待页面刷新或加载完成。') elif 'Could not find node with given id' in r['error']: raise RuntimeError('该元素已不在当前页面中。') elif 'tab closed' in r['error']: @@ -764,6 +734,93 @@ class ChromiumBase(BasePage): return True + def wait_loading(self, timeout=None): + """阻塞程序,等待页面进入加载状态 + :param timeout: 超时时间 + :return: 等待结束时是否进入加载状态 + """ + warn("此方法即将弃用,请用wait.load_start()方法代替。", DeprecationWarning) + return self.wait.load_start(timeout) + + def wait_ele(self, loc_or_ele, timeout=None): + """返回用于等待元素到达某个状态的等待器对象 + :param loc_or_ele: 可以是元素、查询字符串、loc元组 + :param timeout: 等待超时时间 + :return: 用于等待的ElementWaiter对象 + """ + warn("此方法即将弃用,请用wait.ele_xxxx()方法代替。", DeprecationWarning) + return ChromiumElementWaiter(self, loc_or_ele, timeout) + + def scroll_to_see(self, loc_or_ele): + """滚动页面直到元素可见 + :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串(详见ele函数注释) + :return: None + """ + warn("此方法即将弃用,请用scroll.to_see()方法代替。", DeprecationWarning) + self.scroll.to_see(loc_or_ele) + + +class ChromiumPageWaiter(ChromiumWaiter): + def __init__(self, page): + """ + :param page: 所属页面对象 + """ + super().__init__(page) + + def _loading(self, timeout=None, start=True): + """等待页面开始加载或加载完成 + :param timeout: 超时时间,为None时使用页面timeout属性 + :param start: 等待开始还是结束 + :return: 是否等待成功 + """ + if timeout != 0: + timeout = self._driver.timeout if timeout in (None, True) else timeout + end_time = perf_counter() + timeout + while perf_counter() < end_time: + if self._driver.is_loading == start: + return True + sleep(.005) + return False + + def load_start(self, timeout=None): + """等待页面开始加载 + :param timeout: 超时时间,为None时使用页面timeout属性 + :return: 是否等待成功 + """ + return self._loading(timeout=timeout) + + def load_complete(self, timeout=None): + """等待页面开始加载 + :param timeout: 超时时间,为None时使用页面timeout属性 + :return: 是否等待成功 + """ + return self._loading(timeout=timeout, start=False) + + +class ChromiumPageScroll(ChromiumScroll): + def __init__(self, page): + """ + :param page: 页面对象 + """ + super().__init__(page) + self.t1 = 'window' + self.t2 = 'document.documentElement' + + def to_see(self, loc_or_ele): + """滚动页面直到元素可见 + :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串 + :return: None + """ + ele = self._driver.ele(loc_or_ele) + node_id = ele.node_id + try: + self._driver._wait_driver.DOM.scrollIntoViewIfNeeded(nodeId=node_id) + except Exception: + ele.run_js("this.scrollIntoView();") + + if not ele.is_in_viewport: + offset_scroll(ele, 0, 0) + class Timeout(object): """用于保存d模式timeout信息的类""" diff --git a/DrissionPage/chromium_base.pyi b/DrissionPage/chromium_base.pyi index 6c90daf..b449e4d 100644 --- a/DrissionPage/chromium_base.pyi +++ b/DrissionPage/chromium_base.pyi @@ -12,7 +12,7 @@ from requests.cookies import RequestsCookieJar from .base import BasePage from .chromium_driver import ChromiumDriver -from .chromium_element import ChromiumElement, ChromiumElementWaiter, ChromiumScroll +from .chromium_element import ChromiumElement, ChromiumScroll, ChromiumElementWaiter, ChromiumWaiter from .chromium_frame import ChromiumFrame from .session_element import SessionElement @@ -109,7 +109,7 @@ class ChromiumBase(BasePage): def page_load_strategy(self) -> str: ... @property - def scroll(self) -> ChromiumScroll: ... + def scroll(self) -> ChromiumPageScroll: ... @property def timeouts(self) -> Timeout: ... @@ -120,6 +120,9 @@ class ChromiumBase(BasePage): @property def upload_list(self) -> list: ... + @property + def wait(self) -> ChromiumPageWaiter: ... + def set_timeouts(self, implicit: float = None, page_load: float = None, script: float = None) -> None: ... def run_js(self, script: str, as_expr: bool = False, *args: Any) -> Any: ... @@ -133,7 +136,7 @@ class ChromiumBase(BasePage): interval: float = None, timeout: float = None) -> Union[None, bool]: ... - def wait_loading(self, timeout: float = 1) -> bool: ... + def wait_loading(self, timeout: float = None) -> bool: ... def get_cookies(self, as_dict: bool = False) -> Union[list, dict]: ... @@ -161,7 +164,7 @@ class ChromiumBase(BasePage): def wait_ele(self, loc_or_ele: Union[str, tuple, ChromiumElement], - timeout: float = None) -> 'ChromiumElementWaiter': ... + timeout: float = None) -> ChromiumElementWaiter: ... def scroll_to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement]) -> None: ... @@ -207,6 +210,23 @@ class ChromiumBase(BasePage): timeout: float = None) -> Union[bool, None]: ... +class ChromiumPageWaiter(ChromiumWaiter): + def __init__(self, page: ChromiumBase): + self._driver: ChromiumBase = ... + + def _loading(self, timeout: Union[int, float] = None, start: bool = True) -> bool: ... + + def load_start(self, timeout: Union[int, float] = None) -> bool: ... + + def load_complete(self, timeout: Union[int, float] = None) -> bool: ... + + +class ChromiumPageScroll(ChromiumScroll): + def __init__(self, page: ChromiumBase): ... + + def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement]) -> None: ... + + class Timeout(object): def __init__(self, page: ChromiumBase, implicit=None, page_load=None, script=None): diff --git a/DrissionPage/chromium_element.py b/DrissionPage/chromium_element.py index 49db46c..43ea562 100644 --- a/DrissionPage/chromium_element.py +++ b/DrissionPage/chromium_element.py @@ -7,6 +7,7 @@ from os import sep from os.path import basename from pathlib import Path from time import perf_counter, sleep +from warnings import warn from .base import DrissionElement, BaseElement from .functions.locator import get_loc @@ -270,13 +271,10 @@ class ChromiumElement(DrissionElement): """ return super().afters(filter_loc, timeout) - def wait_ele(self, loc_or_ele, timeout=None): - """返回用于等待子元素到达某个状态的等待器对象 - :param loc_or_ele: 可以是元素、查询字符串、loc元组 - :param timeout: 等待超时时间 - :return: 用于等待的ElementWaiter对象 - """ - return ChromiumElementWaiter(self, loc_or_ele, timeout) + @property + def wait(self): + """返回用于等待的对象""" + return ChromiumWaiter(self) @property def select(self): @@ -617,7 +615,7 @@ class ChromiumElement(DrissionElement): return True if not by_js: - self.page.scroll_to_see(self) + self.page.scroll.to_see(self) if self.is_in_viewport: client_x, client_y = self._client_click_point if client_x: @@ -625,7 +623,7 @@ class ChromiumElement(DrissionElement): click = do_it(client_x, client_y, loc_x, loc_y) if click: - self.page.wait_loading(wait_loading) + self.page.wait.load_start(wait_loading) return True timeout = timeout if timeout is not None else self.page.timeout @@ -634,12 +632,12 @@ class ChromiumElement(DrissionElement): click = do_it(client_x, client_y, loc_x, loc_y) if click is not None: - self.page.wait_loading(wait_loading) + self.page.wait.load_start(wait_loading) return True if by_js is not False: self.run_js('this.click();') - self.page.wait_loading(wait_loading) + self.page.wait.load_start(wait_loading) return True return False @@ -651,13 +649,13 @@ class ChromiumElement(DrissionElement): :param button: 左键还是右键 :return: None """ - self.page.scroll_to_see(self) + self.page.scroll.to_see(self) x, y = offset_scroll(self, offset_x, offset_y) self._click(x, y, button) def r_click(self): """右键单击""" - self.page.scroll_to_see(self) + self.page.scroll.to_see(self) x, y = self._client_click_point self._click(x, y, 'right') @@ -671,7 +669,7 @@ class ChromiumElement(DrissionElement): def m_click(self): """中键单击""" - self.page.scroll_to_see(self) + self.page.scroll.to_see(self) x, y = self._client_click_point self._click(x, y, 'middle') @@ -693,7 +691,7 @@ class ChromiumElement(DrissionElement): :param offset_y: 相对元素左上角坐标的y轴偏移量 :return: None """ - self.page.scroll_to_see(self) + self.page.scroll.to_see(self) x, y = offset_scroll(self, offset_x, offset_y) self.page.driver.Input.dispatchMouseEvent(type='mouseMoved', x=x, y=y) @@ -835,6 +833,15 @@ class ChromiumElement(DrissionElement): sx, sy = xy.split(' ') return int(x + float(sx)), int(y + float(sy)) + def wait_ele(self, loc_or_ele, timeout=None): + """返回用于等待子元素到达某个状态的等待器对象 + :param loc_or_ele: 可以是元素、查询字符串、loc元组 + :param timeout: 等待超时时间 + :return: 用于等待的ElementWaiter对象 + """ + warn("此方法即将弃用,请用wait.ele_xxxx()方法代替。", DeprecationWarning) + return ChromiumElementWaiter(self, loc_or_ele, timeout) + class ChromiumShadowRootElement(BaseElement): """ChromiumShadowRootElement是用于处理ShadowRoot的类,使用方法和ChromiumElement基本一致""" @@ -1395,19 +1402,12 @@ def _send_key(ele, modifier, key): class ChromiumScroll(object): """用于滚动的对象""" - def __init__(self, page_or_ele): + def __init__(self, ele): """ - :param page_or_ele: 页面对象、元素对象或frame对象 + :param ele: 元素对象 """ - self._driver = page_or_ele - if isinstance(page_or_ele, ChromiumElement): - self.t1 = self.t2 = 'this' - elif 'ChromiumFrame' in str(type(page_or_ele)): - self._driver = page_or_ele.doc_ele - self.t1 = self.t2 = 'this.documentElement' - else: - self.t1 = 'window' - self.t2 = 'document.documentElement' + self._driver = ele + self.t1 = self.t2 = 'this' def _run_js(self, js): js = js.format(self.t1, self.t2, self.t2) @@ -1660,6 +1660,38 @@ class ChromiumSelect(object): return success +class ChromiumWaiter(object): + def __init__(self, page_or_ele): + """ + :param page_or_ele: 页面对象或元素对象 + """ + self._driver = page_or_ele + + def ele_delete(self, loc_or_ele, timeout=None): + """ + :param loc_or_ele: 要等待的元素,可以是已有元素、定位符 + :param timeout: 超时时间,默认读取页面超时时间 + :return: 是否等待成功 + """ + return ChromiumElementWaiter(self._driver, loc_or_ele, timeout).delete() + + def ele_display(self, loc_or_ele, timeout=None): + """ + :param loc_or_ele: 要等待的元素,可以是已有元素、定位符 + :param timeout: 超时时间,默认读取页面超时时间 + :return: 是否等待成功 + """ + return ChromiumElementWaiter(self._driver, loc_or_ele, timeout).display() + + def ele_hidden(self, loc_or_ele, timeout=None): + """ + :param loc_or_ele: 要等待的元素,可以是已有元素、定位符 + :param timeout: 超时时间,默认读取页面超时时间 + :return: 是否等待成功 + """ + return ChromiumElementWaiter(self._driver, loc_or_ele, timeout).hidden() + + class ChromiumElementWaiter(object): """等待元素在dom中某种状态,如删除、显示、隐藏""" @@ -1693,7 +1725,7 @@ class ChromiumElementWaiter(object): end_time = perf_counter() + self.timeout while perf_counter() < end_time: - if not self.loc_or_ele.is_alive: + if not ele.is_alive: return True return False diff --git a/DrissionPage/chromium_element.pyi b/DrissionPage/chromium_element.pyi index 6a8d48f..5d03be1 100644 --- a/DrissionPage/chromium_element.pyi +++ b/DrissionPage/chromium_element.pyi @@ -141,7 +141,10 @@ class ChromiumElement(DrissionElement): def wait_ele(self, loc_or_ele: Union[str, tuple, ChromiumElement], - timeout: float = None) -> ChromiumElementWaiter: ... + timeout: float = None) -> ChromiumWaiter: ... + + @property + def wait(self) -> ChromiumWaiter: ... @property def select(self) -> ChromiumSelect: ... @@ -455,12 +458,24 @@ class ChromiumSelect(object): deselect: bool = False) -> bool: ... +class ChromiumWaiter(object): + def __init__(self, page_or_ele): + self._driver: Union[ChromiumBase, ChromiumElement] = ... + + def ele_delete(self, loc_or_ele: Union[str, tuple, ChromiumElement], timeout: Union[int, float] = None) -> bool: ... + + def ele_display(self, loc_or_ele: Union[str, tuple, ChromiumElement], + timeout: Union[int, float] = None) -> bool: ... + + def ele_hidden(self, loc_or_ele: Union[str, tuple, ChromiumElement], timeout: Union[int, float] = None) -> bool: ... + + class ChromiumElementWaiter(object): def __init__(self, page_or_ele: Union[ChromiumBase, ChromiumElement], loc_or_ele: Union[str, tuple, ChromiumElement], - timeout: float = None): + timeout: Union[int, float] = None): self.loc_or_ele: Union[str, tuple, ChromiumElement] = ... self.timeout: float = ... self.driver: Union[ChromiumPage, ChromiumPage] = ... diff --git a/DrissionPage/chromium_frame.py b/DrissionPage/chromium_frame.py index 80614e8..53448c0 100644 --- a/DrissionPage/chromium_frame.py +++ b/DrissionPage/chromium_frame.py @@ -6,7 +6,7 @@ from re import search from time import sleep -from .chromium_base import ChromiumBase +from .chromium_base import ChromiumBase, ChromiumPageScroll from .chromium_element import ChromiumElement @@ -259,6 +259,11 @@ class ChromiumFrame(ChromiumBase): except: pass + @property + def scroll(self): + """返回用于等待的对象""" + return ChromiumFrameScroll(self) + def refresh(self): """刷新frame页面""" self._check_ok() @@ -445,3 +450,12 @@ class ChromiumFrame(ChromiumBase): def _is_inner_frame(self): """返回当前frame是否同域""" return self.frame_id in str(self.page.run_cdp('Page.getFrameTree')['frameTree']) + + +class ChromiumFrameScroll(ChromiumPageScroll): + def __init__(self, frame): + """ + :param frame: ChromiumFrame对象 + """ + self._driver = frame.doc_ele + self.t1 = self.t2 = 'this.documentElement' diff --git a/DrissionPage/chromium_frame.pyi b/DrissionPage/chromium_frame.pyi index e9d396d..9c8667c 100644 --- a/DrissionPage/chromium_frame.pyi +++ b/DrissionPage/chromium_frame.pyi @@ -5,8 +5,8 @@ """ from typing import Union, Tuple, List, Any -from .chromium_element import ChromiumElement -from .chromium_base import ChromiumBase +from .chromium_element import ChromiumElement, ChromiumScroll +from .chromium_base import ChromiumBase, ChromiumPageScroll class ChromiumFrame(ChromiumBase): @@ -99,6 +99,12 @@ class ChromiumFrame(ChromiumBase): @property def css_path(self) -> str: ... + @property + def ready_state(self) -> str: ... + + @property + def scroll(self) -> ChromiumFrameScroll: ... + def refresh(self) -> None: ... def attr(self, attr: str) -> Union[str, None]: ... @@ -160,3 +166,7 @@ class ChromiumFrame(ChromiumBase): timeout: float = None) -> Union[bool, None]: ... def _is_inner_frame(self) -> bool: ... + + +class ChromiumFrameScroll(ChromiumPageScroll): + def __init__(self, frame: ChromiumFrame) -> None: ...