3.2.12页面对象增加滚动行为和等待滚动结束设置;优化滚动逻辑;修复保存图片未正确等待问题

This commit is contained in:
g1879 2023-03-06 18:41:42 +08:00
parent 7c43573fad
commit 04e82ee5e6
6 changed files with 161 additions and 52 deletions

View File

@ -931,6 +931,11 @@ class ChromiumBaseSetter(object):
"""返回用于设置页面加载策略的对象"""
return PageLoadStrategy(self._page)
@property
def scroll(self):
"""返回用于设置页面滚动设置的对象"""
return PageScrollSetter(self._page.scroll)
def timeouts(self, implicit=None, page_load=None, script=None):
"""设置超时时间,单位为秒
:param implicit: 查找元素超时时间
@ -1089,16 +1094,30 @@ class ChromiumPageScroll(ChromiumScroll):
self.t1 = 'window'
self.t2 = 'document.documentElement'
def to_see(self, loc_or_ele):
def to_see(self, loc_or_ele, center=False):
"""滚动页面直到元素可见
:param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串
:param center: 是否尽量滚动到页面正中
:return: None
"""
ele = self._driver._ele(loc_or_ele)
ele.run_js('this.scrollIntoView({behavior: "auto", block: "nearest", inline: "nearest"});')
self._to_see(ele, center)
def _to_see(self, ele, center):
"""执行滚动页面直到元素可见
:param ele: 元素对象
:param center: 是否尽量滚动到页面正中
:return: None
"""
if center:
ele.run_js('this.scrollIntoViewIfNeeded();')
self._wait_scrolled()
return
ele.run_js('this.scrollIntoViewIfNeeded(false);')
if ele.states.is_covered:
ele.run_js('this.scrollIntoView({behavior: "auto", block: "center", inline: "center"});')
ele.run_js('this.scrollIntoViewIfNeeded();')
self._wait_scrolled()
class Timeout(object):
@ -1149,3 +1168,27 @@ class PageLoadStrategy(object):
def none(self):
"""设置页面加载策略为none"""
self._page._page_load_strategy = 'none'
class PageScrollSetter(object):
def __init__(self, scroll):
self._scroll = scroll
def wait_complete(self, on_off=True):
"""设置滚动命令后是否等待完成
:param on_off: 开或关
:return: None
"""
if not isinstance(on_off, bool):
raise TypeError('on_off必须为bool。')
self._scroll._wait_complete = on_off
def smooth(self, on_off=True):
"""设置页面滚动是否平滑滚动
:param on_off: 开或关
:return: None
"""
if not isinstance(on_off, bool):
raise TypeError('on_off必须为bool。')
b = 'smooth' if on_off else 'auto'
self._scroll._driver.run_js(f'document.documentElement.style.setProperty("scroll-behavior","{b}");')

View File

@ -241,7 +241,9 @@ class ChromiumBaseWaiter(object):
class ChromiumPageScroll(ChromiumScroll):
def __init__(self, page: ChromiumBase): ...
def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement]) -> None: ...
def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement], center: bool = False) -> None: ...
def _to_see(self, ele: ChromiumElement, center: bool) -> None: ...
class ChromiumBaseSetter(object):
@ -251,6 +253,9 @@ class ChromiumBaseSetter(object):
@property
def load_strategy(self) -> PageLoadStrategy: ...
@property
def scroll(self) -> PageScrollSetter: ...
def timeouts(self, implicit: float = None, page_load: float = None, script: float = None): ...
def user_agent(self, ua: str, platform: str = None) -> None: ...
@ -286,3 +291,12 @@ class PageLoadStrategy(object):
def eager(self) -> None: ...
def none(self) -> None: ...
class PageScrollSetter(object):
def __init__(self, scroll: ChromiumPageScroll):
self._scroll: ChromiumPageScroll = ...
def wait_complete(self, on_off: bool = True): ...
def smooth(self, on_off: bool = True): ...

View File

@ -172,7 +172,7 @@ class ChromiumElement(DrissionElement):
def scroll(self):
"""用于滚动滚动条的对象"""
if self._scroll is None:
self._scroll = ChromiumScroll(self)
self._scroll = ChromiumElementScroll(self)
return self._scroll
@property
@ -429,6 +429,7 @@ class ChromiumElement(DrissionElement):
while perf_counter() < end_time:
try:
result = self.page.run_cdp('Page.getResourceContent', frameId=frame, url=src)
break
except CallMethodError:
pass
sleep(.1)
@ -477,7 +478,7 @@ class ChromiumElement(DrissionElement):
while not self.run_js(js) and perf_counter() < end_time:
sleep(.1)
self.page.scroll.to_see(self)
self.scroll.to_see(center=True)
sleep(1)
left, top = self.location
width, height = self.size
@ -1648,7 +1649,7 @@ class Click(object):
"""
if not by_js:
try:
self._ele.page.scroll.to_see(self._ele)
self._ele.scroll.to_see()
if self._ele.states.is_in_viewport and not self._ele.states.is_covered:
client_x, client_y = self._ele.locations.viewport_click_point
self._click(client_x, client_y)
@ -1711,10 +1712,12 @@ class ChromiumScroll(object):
"""
self._driver = ele
self.t1 = self.t2 = 'this'
self._wait_complete = False
def _run_js(self, js):
js = js.format(self.t1, self.t2, self.t2)
self._driver.run_js(js)
self._wait_scrolled()
def to_top(self):
"""滚动到顶端,水平位置不变"""
@ -1774,6 +1777,36 @@ class ChromiumScroll(object):
"""
self._run_js(f'{{}}.scrollBy({pixel}, 0);')
def _wait_scrolled(self):
if not self._wait_complete:
return
page = self._driver.page if isinstance(self._driver, ChromiumElement) else self._driver
r = page.run_cdp('Page.getLayoutMetrics')
x = r['layoutViewport']['pageX']
y = r['layoutViewport']['pageY']
while True:
sleep(.1)
r = page.run_cdp('Page.getLayoutMetrics')
x1 = r['layoutViewport']['pageX']
y1 = r['layoutViewport']['pageY']
if x == x1 and y == y1:
break
x = x1
y = y1
class ChromiumElementScroll(ChromiumScroll):
def to_see(self, center=False):
"""滚动页面直到元素可见
:param center: 是否尽量滚动到页面正中
:return: None
"""
self._driver.page.scroll.to_see(self._driver, center=center)
class ChromiumSelect(object):
"""ChromiumSelect 类专门用于处理 d 模式下 select 标签"""

View File

@ -27,7 +27,7 @@ class ChromiumElement(DrissionElement):
self._backend_id: str = ...
self._doc_id: str = ...
self._ids: ChromiumElementIds = ...
self._scroll: ChromiumScroll = ...
self._scroll: ChromiumElementScroll = ...
self._click: Click = ...
self._select: ChromiumSelect = ...
self._wait: ChromiumElementWaiter = ...
@ -89,7 +89,7 @@ class ChromiumElement(DrissionElement):
def sr(self) -> Union[None, ChromiumShadowRoot]: ...
@property
def scroll(self) -> ChromiumScroll: ...
def scroll(self) -> ChromiumElementScroll: ...
@property
def click(self) -> Click: ...
@ -163,7 +163,7 @@ class ChromiumElement(DrissionElement):
def _find_elements(self, loc_or_str: Union[Tuple[str, str], str], timeout: float = None,
single: bool = True, relative: bool = False, raise_err: bool = False) \
-> Union[ChromiumElement, ChromiumFrame, str, NoneElement,
List[Union[ChromiumElement, ChromiumFrame, str]]]: ...
List[Union[ChromiumElement, ChromiumFrame, str]]]: ...
def style(self, style: str, pseudo_ele: str = '') -> str: ...
@ -453,6 +453,7 @@ class ChromiumScroll(object):
self.t1: str = ...
self.t2: str = ...
self._driver: Union[ChromiumPage, ChromiumElement, ChromiumFrame] = ...
self._wait_complete: bool = ...
def _run_js(self, js: str): ...
@ -476,6 +477,13 @@ class ChromiumScroll(object):
def right(self, pixel: int = 300) -> None: ...
def _wait_scrolled(self) -> None: ...
class ChromiumElementScroll(ChromiumScroll):
def to_see(self, center: bool = False) -> None: ...
class ChromiumSelect(object):
def __init__(self, ele: ChromiumElement):

View File

@ -428,48 +428,55 @@ class ChromiumFrame(ChromiumBase):
return super().get_screenshot(path=path, as_bytes=as_bytes, as_base64=as_base64,
full_page=full_page, left_top=left_top, right_bottom=right_bottom)
else:
if as_bytes:
if as_bytes is True:
pic_type = 'png'
else:
if as_bytes not in ('jpg', 'jpeg', 'png', 'webp'):
raise ValueError("只能接收 'jpg', 'jpeg', 'png', 'webp' 四种格式。")
pic_type = 'jpeg' if as_bytes == 'jpg' else as_bytes
elif as_base64:
if as_base64 is True:
pic_type = 'png'
else:
if as_base64 not in ('jpg', 'jpeg', 'png', 'webp'):
raise ValueError("只能接收 'jpg', 'jpeg', 'png', 'webp' 四种格式。")
pic_type = 'jpeg' if as_base64 == 'jpg' else as_base64
if as_bytes:
if as_bytes is True:
pic_type = 'png'
else:
if not path:
path = f'{self.title}.jpg'
path = get_usable_path(path)
pic_type = path.suffix.lower()
if pic_type not in ('.jpg', '.jpeg', '.png', '.webp'):
raise TypeError(f'不支持的文件格式:{pic_type}')
pic_type = 'jpeg' if pic_type == '.jpg' else pic_type[1:]
if as_bytes not in ('jpg', 'jpeg', 'png', 'webp'):
raise ValueError("只能接收 'jpg', 'jpeg', 'png', 'webp' 四种格式。")
pic_type = 'jpeg' if as_bytes == 'jpg' else as_bytes
self.scroll.to_see(ele)
cx, cy = ele.locations.viewport_location
w, h = ele.size
img_data = f'data:image/{pic_type};base64,{self.frame_ele.get_screenshot(as_base64=True)}'
body = self.page('t:body')
first_child = body('c::first-child')
js = f'''
haskell = document.createElement('img');
haskell.src = "{img_data}";
arguments[0].insertBefore(haskell, this);
return haskell;'''
new_ele = first_child.run_js(js, body)
r = self.page.get_screenshot(path=path, as_bytes=as_bytes, as_base64=as_base64,
left_top=(cx, cy), right_bottom=(cx + w, cy + h))
self.page.remove_ele(new_ele)
return r
elif as_base64:
if as_base64 is True:
pic_type = 'png'
else:
if as_base64 not in ('jpg', 'jpeg', 'png', 'webp'):
raise ValueError("只能接收 'jpg', 'jpeg', 'png', 'webp' 四种格式。")
pic_type = 'jpeg' if as_base64 == 'jpg' else as_base64
else:
if not path:
path = f'{self.title}.jpg'
path = get_usable_path(path)
pic_type = path.suffix.lower()
if pic_type not in ('.jpg', '.jpeg', '.png', '.webp'):
raise TypeError(f'不支持的文件格式:{pic_type}')
pic_type = 'jpeg' if pic_type == '.jpg' else pic_type[1:]
self.frame_ele.scroll.to_see(center=True)
self.scroll.to_see(ele, center=True)
cx, cy = ele.locations.viewport_location
w, h = ele.size
img_data = f'data:image/{pic_type};base64,{self.frame_ele.get_screenshot(as_base64=True)}'
body = self.page('t:body')
first_child = body('c::first-child')
if not isinstance(first_child, ChromiumElement):
first_child = first_child.frame_ele
js = f'''
img = document.createElement('img');
img.src = "{img_data}";
img.style.setProperty("z-index",9999999);
img.style.setProperty("position","fixed");
arguments[0].insertBefore(img, this);
return img;'''
new_ele = first_child.run_js(js, body)
new_ele.scroll.to_see(True)
top = int(self.frame_ele.style('border-top').split('px')[0])
left = int(self.frame_ele.style('border-left').split('px')[0])
r = self.page.get_screenshot(path=path, as_bytes=as_bytes, as_base64=as_base64,
left_top=(cx + left, cy + top), right_bottom=(cx + w + left, cy + h + top))
self.page.remove_ele(new_ele)
return r
def _find_elements(self, loc_or_ele, timeout=None, single=True, relative=False, raise_err=None):
"""在frame内查找单个元素
@ -621,14 +628,16 @@ class ChromiumFrameScroll(ChromiumPageScroll):
"""
self._driver = frame.doc_ele
self.t1 = self.t2 = 'this.documentElement'
self._wait_complete = False
def to_see(self, loc_or_ele):
def to_see(self, loc_or_ele, center=False):
"""滚动页面直到元素可见
:param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串
:param center: 是否尽量滚动到页面正中
:return: None
"""
ele = loc_or_ele if isinstance(loc_or_ele, ChromiumElement) else self._driver._ele(loc_or_ele)
ele.run_js('this.scrollIntoView({behavior: "auto", block: "center", inline: "center"});')
self._to_see(ele, center)
class ChromiumFrameSetter(ChromiumBaseSetter):

View File

@ -196,6 +196,8 @@ class ChromiumFrameIds(object):
class ChromiumFrameScroll(ChromiumPageScroll):
def __init__(self, frame: ChromiumFrame) -> None: ...
def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement], center: bool = False) -> None: ...
class ChromiumFrameSetter(ChromiumBaseSetter):
_page: ChromiumFrame = ...