优化点击逻辑

This commit is contained in:
g1879 2023-11-08 11:21:11 +08:00
parent 3dfbfb957f
commit c6273d9bf2
8 changed files with 201 additions and 162 deletions

View File

@ -16,6 +16,7 @@ from .._commons.tools import get_usable_path
from .._commons.web import make_absolute_link, get_ele_txt, format_html, is_js_func, offset_scroll
from .._units.clicker import Clicker
from .._units.element_states import ChromiumElementStates, ShadowRootStates
from .._units.locations import Locations
from .._units.select_element import SelectElement
from .._units.setter import ChromiumElementSetter
from .._units.waiter import ChromiumElementWaiter
@ -1438,103 +1439,6 @@ def send_key(ele, modifier, key):
ele.page.run_cdp('Input.dispatchKeyEvent', **data)
class Locations(object):
def __init__(self, ele):
"""
:param ele: ChromiumElement
"""
self._ele = ele
@property
def location(self):
"""返回元素左上角的绝对坐标"""
cl = self.viewport_location
return self._get_page_coord(cl[0], cl[1])
@property
def midpoint(self):
"""返回元素中间点的绝对坐标"""
cl = self.viewport_midpoint
return self._get_page_coord(cl[0], cl[1])
@property
def click_point(self):
"""返回元素接受点击的点的绝对坐标"""
cl = self.viewport_click_point
return self._get_page_coord(cl[0], cl[1])
@property
def viewport_location(self):
"""返回元素左上角在视口中的坐标"""
m = self._get_viewport_rect('border')
return int(m[0]), int(m[1])
@property
def viewport_midpoint(self):
"""返回元素中间点在视口中的坐标"""
m = self._get_viewport_rect('border')
return int(m[0] + (m[2] - m[0]) // 2), int(m[3] + (m[5] - m[3]) // 2)
@property
def viewport_click_point(self):
"""返回元素接受点击的点视口坐标"""
m = self._get_viewport_rect('padding')
return int(self.viewport_midpoint[0]), int(m[1]) + 3
@property
def screen_location(self):
"""返回元素左上角在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.page.rect.viewport_location
ex, ey = self.viewport_location
pr = self._ele.page.run_js('return window.devicePixelRatio;')
return int((vx + ex) * pr), int((ey + vy) * pr)
@property
def screen_midpoint(self):
"""返回元素中点在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.page.rect.viewport_location
ex, ey = self.viewport_midpoint
pr = self._ele.page.run_js('return window.devicePixelRatio;')
return int((vx + ex) * pr), int((ey + vy) * pr)
@property
def screen_click_point(self):
"""返回元素中点在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.page.rect.viewport_location
ex, ey = self.viewport_click_point
pr = self._ele.page.run_js('return window.devicePixelRatio;')
return int((vx + ex) * pr), int((ey + vy) * pr)
@property
def rect(self):
"""返回元素四个角坐标顺序坐上、右上、右下、左下没有大小的元素抛出NoRectError"""
vr = self._get_viewport_rect('border')
r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
sx = r['pageX']
sy = r['pageY']
return [(vr[0] + sx, vr[1] + sy), (vr[2] + sx, vr[3] + sy), (vr[4] + sx, vr[5] + sy), (vr[6] + sx, vr[7] + sy)]
@property
def viewport_rect(self):
"""返回元素四个角视口坐标顺序坐上、右上、右下、左下没有大小的元素抛出NoRectError"""
r = self._get_viewport_rect('border')
return [(r[0], r[1]), (r[2], r[3]), (r[4], r[5]), (r[6], r[7])]
def _get_viewport_rect(self, quad):
"""按照类型返回在可视窗口中的范围
:param quad: 方框类型margin border padding
:return: 四个角坐标
"""
return self._ele.page.run_cdp('DOM.getBoxModel', backendNodeId=self._ele.ids.backend_id)['model'][quad]
def _get_page_coord(self, x, y):
"""根据视口坐标获取绝对坐标"""
r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
sx = r['pageX']
sy = r['pageY']
return x + sx, y + sy
class ChromiumScroll(object):
"""用于滚动的对象"""

View File

@ -4,7 +4,7 @@
@Contact : g1879@qq.com
"""
from pathlib import Path
from typing import Union, Tuple, List, Any, Optional
from typing import Union, Tuple, List, Any
from .._base.base import DrissionElement, BaseElement
from .._commons.constants import NoneElement
@ -15,6 +15,7 @@ from .._pages.chromium_page import ChromiumPage
from .._pages.web_page import WebPage
from .._units.clicker import Clicker
from .._units.element_states import ShadowRootStates, ChromiumElementStates
from .._units.locations import Locations
from .._units.select_element import SelectElement
from .._units.setter import ChromiumElementSetter
from .._units.waiter import ChromiumElementWaiter
@ -363,48 +364,6 @@ def send_enter(ele: ChromiumElement) -> None: ...
def send_key(ele: ChromiumElement, modifier: int, key: str) -> None: ...
class Locations(object):
def __init__(self, ele: ChromiumElement):
self._ele: ChromiumElement = ...
@property
def location(self) -> Tuple[int, int]: ...
@property
def midpoint(self) -> Tuple[int, int]: ...
@property
def click_point(self) -> Tuple[int, int]: ...
@property
def viewport_location(self) -> Tuple[int, int]: ...
@property
def viewport_midpoint(self) -> Tuple[int, int]: ...
@property
def viewport_click_point(self) -> Tuple[int, int]: ...
@property
def screen_location(self) -> Tuple[int, int]: ...
@property
def screen_midpoint(self) -> Tuple[int, int]: ...
@property
def screen_click_point(self) -> Tuple[int, int]: ...
@property
def rect(self) -> list: ...
@property
def viewport_rect(self) -> list: ...
def _get_viewport_rect(self, quad: str) -> Union[list, None]: ...
def _get_page_coord(self, x: int, y: int) -> Tuple[int, int]: ...
class ChromiumScroll(object):
def __init__(self, page_or_ele: Union[ChromiumBase, ChromiumElement, ChromiumFrame]):
self.t1: str = ...

View File

@ -103,6 +103,11 @@ class ChromiumBase(BasePage):
self._driver.call_method('Page.enable')
self._driver.call_method('Emulation.setFocusEmulationEnabled', enabled=True)
r = self.run_cdp('Page.getFrameTree')
for i in findall(r"'id': '(.*?)'", str(r)):
self.browser._frames[i] = self.tab_id
self._frame_id = r['frameTree']['frame']['id']
self._driver.set_listener('Page.frameStartedLoading', self._onFrameStartedLoading)
self._driver.set_listener('Page.frameNavigated', self._onFrameNavigated)
self._driver.set_listener('Page.domContentEventFired', self._onDomContentEventFired)
@ -111,10 +116,6 @@ class ChromiumBase(BasePage):
self._driver.set_listener('Page.frameAttached', self._onFrameAttached)
self._driver.set_listener('Page.frameDetached', self._onFrameDetached)
r = self.run_cdp('Page.getFrameTree')
for i in findall(r"'id': '(.*?)'", str(r)):
self.browser._frames[i] = self.tab_id
self._frame_id = r['frameTree']['frame']['id']
def _get_document(self):
if self._is_reading:

View File

@ -7,7 +7,7 @@ from time import perf_counter, sleep
from .._commons.constants import Settings
from .._commons.web import offset_scroll
from ..errors import CanNotClickError, CDPError
from ..errors import CanNotClickError, CDPError, NoRectError
class Clicker(object):
@ -26,7 +26,7 @@ class Clicker(object):
"""
return self.left(by_js, timeout)
def left(self, by_js=False, timeout=1):
def left(self, by_js=False, timeout=2):
"""点击元素可选择是否用js点击
:param by_js: 是否用js点击为None时先用模拟点击遇到遮挡改用js为True时直接用js点击为False时只用模拟点击
:param timeout: 模拟点击的超时时间等待元素可见不被遮挡进入视口
@ -35,34 +35,48 @@ class Clicker(object):
if not by_js: # 模拟点击
can_click = False
timeout = self._ele.page.timeout if timeout is None else timeout
if timeout == 0 and self._ele.states.has_rect:
self._ele.scroll.to_see()
if self._ele.states.is_in_viewport and self._ele.states.is_enabled and self._ele.states.is_displayed:
can_click = True
rect = None
if timeout == 0:
try:
self._ele.scroll.to_see()
if self._ele.states.is_enabled and self._ele.states.is_displayed:
rect = self._ele.locations.viewport_rect
can_click = True
except NoRectError:
if by_js is False:
raise
else:
rect = self._ele.states.has_rect
end_time = perf_counter() + timeout
while not self._ele.states.has_rect and perf_counter() < end_time:
while not rect and perf_counter() < end_time:
rect = self._ele.states.has_rect
sleep(.001)
if self._ele.states.has_rect:
self._ele.wait.stop_moving(timeout=end_time - perf_counter())
if rect:
self._ele.scroll.to_see()
rect = self._ele.locations.rect
while perf_counter() < end_time:
if (self._ele.states.is_in_viewport and self._ele.states.is_enabled
and self._ele.states.is_displayed):
if self._ele.states.is_enabled and self._ele.states.is_displayed:
can_click = True
break
sleep(.001)
if not self._ele.states.has_rect or not self._ele.states.is_in_viewport:
elif by_js is False:
raise NoRectError
if can_click and not self._ele.states.is_in_viewport:
by_js = True
elif can_click and (by_js is False or not self._ele.states.is_covered):
vx, vy = self._ele.locations.click_point
x = int(rect[1][0] - (rect[1][0] - rect[0][0]) / 2)
y = rect[0][0] + 3
try:
r = self._ele.page.run_cdp('DOM.getNodeForLocation', x=vx, y=vy, includeUserAgentShadowDOM=True,
r = self._ele.page.run_cdp('DOM.getNodeForLocation', x=x, y=y, includeUserAgentShadowDOM=True,
ignorePointerEventsNone=True)
if r['backendNodeId'] != self._ele.ids.backend_id:
vx, vy = self._ele.locations.viewport_click_point
vx, vy = self._ele.locations.viewport_midpoint
else:
vx, vy = self._ele.locations.viewport_click_point

View File

@ -1,4 +1,8 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
"""
from .._commons.web import location_in_viewport
from ..errors import CDPError, NoRectError
@ -71,9 +75,9 @@ class ChromiumElementStates(object):
@property
def has_rect(self):
"""返回元素是否拥有位置和大小没有返回False有返回大小元组"""
"""返回元素是否拥有位置和大小没有返回False有返回四个角在页面中坐标组成的列表"""
try:
return self._ele.size
return self._ele.locations.rect
except NoRectError:
return False

View File

@ -1,5 +1,9 @@
# -*- coding:utf-8 -*-
from typing import Union, Tuple
"""
@Author : g1879
@Contact : g1879@qq.com
"""
from typing import Union, Tuple, List
from .._elements.chromium_element import ChromiumShadowRoot, ChromiumElement
@ -33,7 +37,7 @@ class ChromiumElementStates(object):
def is_covered(self) -> bool: ...
@property
def has_rect(self) -> Union[bool, Tuple[int, int]]: ...
def has_rect(self) -> Union[bool, List[Tuple[float, float]]]: ...
class ShadowRootStates(object):

View File

@ -0,0 +1,103 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
"""
class Locations(object):
def __init__(self, ele):
"""
:param ele: ChromiumElement
"""
self._ele = ele
@property
def location(self):
"""返回元素左上角的绝对坐标"""
cl = self.viewport_location
return self._get_page_coord(cl[0], cl[1])
@property
def midpoint(self):
"""返回元素中间点的绝对坐标"""
cl = self.viewport_midpoint
return self._get_page_coord(cl[0], cl[1])
@property
def click_point(self):
"""返回元素接受点击的点的绝对坐标"""
cl = self.viewport_click_point
return self._get_page_coord(cl[0], cl[1])
@property
def viewport_location(self):
"""返回元素左上角在视口中的坐标"""
m = self._get_viewport_rect('border')
return int(m[0]), int(m[1])
@property
def viewport_midpoint(self):
"""返回元素中间点在视口中的坐标"""
m = self._get_viewport_rect('border')
return int(m[0] + (m[2] - m[0]) // 2), int(m[3] + (m[5] - m[3]) // 2)
@property
def viewport_click_point(self):
"""返回元素接受点击的点视口坐标"""
m = self._get_viewport_rect('padding')
return int(self.viewport_midpoint[0]), int(m[1]) + 3
@property
def screen_location(self):
"""返回元素左上角在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.page.rect.viewport_location
ex, ey = self.viewport_location
pr = self._ele.page.run_js('return window.devicePixelRatio;')
return int((vx + ex) * pr), int((ey + vy) * pr)
@property
def screen_midpoint(self):
"""返回元素中点在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.page.rect.viewport_location
ex, ey = self.viewport_midpoint
pr = self._ele.page.run_js('return window.devicePixelRatio;')
return int((vx + ex) * pr), int((ey + vy) * pr)
@property
def screen_click_point(self):
"""返回元素中点在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.page.rect.viewport_location
ex, ey = self.viewport_click_point
pr = self._ele.page.run_js('return window.devicePixelRatio;')
return int((vx + ex) * pr), int((ey + vy) * pr)
@property
def rect(self):
"""返回元素四个角坐标顺序坐上、右上、右下、左下没有大小的元素抛出NoRectError"""
vr = self._get_viewport_rect('border')
r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
sx = int(r['pageX'])
sy = int(r['pageY'])
return [(vr[0] + sx, vr[1] + sy), (vr[2] + sx, vr[3] + sy),
(vr[4] + sx, vr[5] + sy), (vr[6] + sx, vr[7] + sy)]
@property
def viewport_rect(self):
"""返回元素四个角视口坐标顺序坐上、右上、右下、左下没有大小的元素抛出NoRectError"""
r = self._get_viewport_rect('border')
return [(int(r[0]), int(r[1])), (int(r[2]), int(r[3])), (int(r[4]), int(r[5])), (int(r[6]), int(r[7]))]
def _get_viewport_rect(self, quad):
"""按照类型返回在可视窗口中的范围
:param quad: 方框类型margin border padding
:return: 四个角坐标
"""
return self._ele.page.run_cdp('DOM.getBoxModel', backendNodeId=self._ele.ids.backend_id)['model'][quad]
def _get_page_coord(self, x, y):
"""根据视口坐标获取绝对坐标"""
r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
sx = r['pageX']
sy = r['pageY']
return x + sx, y + sy

View File

@ -0,0 +1,50 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
"""
from typing import Tuple, Union, List
from .._elements.chromium_element import ChromiumElement
class Locations(object):
def __init__(self, ele: ChromiumElement):
self._ele: ChromiumElement = ...
@property
def location(self) -> Tuple[int, int]: ...
@property
def midpoint(self) -> Tuple[int, int]: ...
@property
def click_point(self) -> Tuple[int, int]: ...
@property
def viewport_location(self) -> Tuple[int, int]: ...
@property
def viewport_midpoint(self) -> Tuple[int, int]: ...
@property
def viewport_click_point(self) -> Tuple[int, int]: ...
@property
def screen_location(self) -> Tuple[int, int]: ...
@property
def screen_midpoint(self) -> Tuple[int, int]: ...
@property
def screen_click_point(self) -> Tuple[int, int]: ...
@property
def rect(self) -> List[Tuple[int, int], ...]: ...
@property
def viewport_rect(self) -> List[Tuple[int, int], ...]: ...
def _get_viewport_rect(self, quad: str) -> Union[list, None]: ...
def _get_page_coord(self, x: int, y: int) -> Tuple[int, int]: ...