优化滚动到元素逻辑;修复iframe内截图不准确问题

This commit is contained in:
g1879 2023-02-20 11:26:23 +08:00
parent 11d4a70090
commit 4354f21583
9 changed files with 67 additions and 27 deletions

View File

@ -14,7 +14,7 @@ from .configs.session_options import SessionOptions
# 常用工具 # 常用工具
from .action_chains import ActionChains from .action_chains import ActionChains
from .keys import Keys from .common.keys import Keys
# 旧版页面类和启动配置类 # 旧版页面类和启动配置类
from .mixpage.mix_page import MixPage from .mixpage.mix_page import MixPage

View File

@ -5,8 +5,8 @@
""" """
from time import sleep from time import sleep
from .common.keys import modifierBit, keyDescriptionForString
from .common.web import location_in_viewport from .common.web import location_in_viewport
from .keys import _modifierBit, _keyDescriptionForString
class ActionChains: class ActionChains:
@ -230,7 +230,7 @@ class ActionChains:
:return: self :return: self
""" """
if key in ('\ue009', '\ue008', '\ue00a', '\ue03d'): # 如果上修饰符,添加到变量 if key in ('\ue009', '\ue008', '\ue00a', '\ue03d'): # 如果上修饰符,添加到变量
self.modifier |= _modifierBit.get(key, 0) self.modifier |= modifierBit.get(key, 0)
return self return self
data = self._get_key_data(key, 'keyDown') data = self._get_key_data(key, 'keyDown')
@ -243,7 +243,7 @@ class ActionChains:
:return: self :return: self
""" """
if key in ('\ue009', '\ue008', '\ue00a', '\ue03d'): # 如果上修饰符,添加到变量 if key in ('\ue009', '\ue008', '\ue00a', '\ue03d'): # 如果上修饰符,添加到变量
self.modifier ^= _modifierBit.get(key, 0) self.modifier ^= modifierBit.get(key, 0)
return self return self
data = self._get_key_data(key, 'keyUp') data = self._get_key_data(key, 'keyUp')
@ -272,7 +272,7 @@ class ActionChains:
:param action: 'keyDown' 'keyUp' :param action: 'keyDown' 'keyUp'
:return: 按键信息 :return: 按键信息
""" """
description = _keyDescriptionForString(self.modifier, key) description = keyDescriptionForString(self.modifier, key)
text = description['text'] text = description['text']
if action != 'keyUp': if action != 'keyUp':
action = 'keyDown' if text else 'rawKeyDown' action = 'keyDown' if text else 'rawKeyDown'

View File

@ -937,8 +937,17 @@ class ChromiumPageScroll(ChromiumScroll):
:param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串 :param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串
:return: None :return: None
""" """
ID = None
ele = self._driver.ele(loc_or_ele) ele = self._driver.ele(loc_or_ele)
ele.run_js('this.scrollIntoView({behavior: "smooth", block: "center", inline: "center"});') ele.run_js('this.scrollIntoView({behavior: "auto", block: "nearest", inline: "nearest"});')
x, y = ele.location
try:
ID = ele.page.run_cdp('DOM.getNodeForLocation', x=x, y=y).get('nodeId', None)
except Exception:
pass
if ID != ele.ids.node_id:
ele.run_js('this.scrollIntoView({behavior: "auto", block: "center", inline: "center"});')
class Timeout(object): class Timeout(object):

View File

@ -12,9 +12,9 @@ from warnings import warn
from .base import DrissionElement, BaseElement from .base import DrissionElement, BaseElement
from .common.constants import FRAME_ELEMENT, NoneElement from .common.constants import FRAME_ELEMENT, NoneElement
from .common.errors import ContextLossError, ElementLossError, JavaScriptError, NoRectError from .common.errors import ContextLossError, ElementLossError, JavaScriptError, NoRectError
from .common.keys import keys_to_typing, keyDescriptionForString, keyDefinitions
from .common.locator import get_loc from .common.locator import get_loc
from .common.web import make_absolute_link, get_ele_txt, format_html, is_js_func, location_in_viewport, offset_scroll from .common.web import make_absolute_link, get_ele_txt, format_html, is_js_func, location_in_viewport, offset_scroll
from .keys import _keys_to_typing, _keyDescriptionForString, _keyDefinitions
from .session_element import make_session_ele from .session_element import make_session_ele
@ -457,6 +457,7 @@ class ChromiumElement(DrissionElement):
sleep(.1) sleep(.1)
self.page.scroll.to_see(self) self.page.scroll.to_see(self)
sleep(1)
left, top = self.location left, top = self.location
height, width = self.size height, width = self.size
left_top = (left, top) left_top = (left, top)
@ -486,7 +487,7 @@ class ChromiumElement(DrissionElement):
# ------------处理字符------------- # ------------处理字符-------------
if not isinstance(vals, (tuple, list)): if not isinstance(vals, (tuple, list)):
vals = (str(vals),) vals = (str(vals),)
modifier, vals = _keys_to_typing(vals) modifier, vals = keys_to_typing(vals)
if modifier != 0: # 包含修饰符 if modifier != 0: # 包含修饰符
for key in vals: for key in vals:
@ -1386,11 +1387,11 @@ def send_enter(ele):
def send_key(ele, modifier, key): def send_key(ele, modifier, key):
"""发送一个字,在键盘中的字符触发按键,其它直接发送文本""" """发送一个字,在键盘中的字符触发按键,其它直接发送文本"""
if key not in _keyDefinitions: if key not in keyDefinitions:
ele.page.run_cdp('Input.insertText', text=key) ele.page.run_cdp('Input.insertText', text=key)
else: else:
description = _keyDescriptionForString(modifier, key) description = keyDescriptionForString(modifier, key)
text = description['text'] text = description['text']
data = {'type': 'keyDown' if text else 'rawKeyDown', data = {'type': 'keyDown' if text else 'rawKeyDown',
'modifiers': modifier, 'modifiers': modifier,
@ -1574,11 +1575,14 @@ class Locations(object):
return self._ele.page.run_cdp('DOM.getBoxModel', nodeId=self._ele.ids.node_id)['model'][quad] return self._ele.page.run_cdp('DOM.getBoxModel', nodeId=self._ele.ids.node_id)['model'][quad]
def _get_page_coord(self, x, y): def _get_page_coord(self, x, y):
"""根据绝对坐标获取窗口坐标""" """根据视口坐标获取绝对坐标"""
js = 'return document.documentElement.scrollLeft+" "+document.documentElement.scrollTop;' # js = 'return document.documentElement.scrollLeft+" "+document.documentElement.scrollTop;'
xy = self._ele.run_js(js) # xy = self._ele.run_js(js)
sx, sy = xy.split(' ') # sx, sy = xy.split(' ')
return int(x + float(sx)), int(y + float(sy)) r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
sx = r['pageX']
sy = r['pageY']
return x + sx, y + sy
class Click(object): class Click(object):

View File

@ -220,6 +220,11 @@ class ChromiumFrame(ChromiumBase):
self._check_ok() self._check_ok()
return self.frame_ele.location return self.frame_ele.location
@property
def locations(self):
"""返回用于获取元素位置的对象"""
return self.frame_ele.locations
@property @property
def xpath(self): def xpath(self):
"""返回frame的xpath绝对路径""" """返回frame的xpath绝对路径"""
@ -296,7 +301,10 @@ class ChromiumFrame(ChromiumBase):
:return: 运行的结果 :return: 运行的结果
""" """
self._check_ok() self._check_ok()
return self.doc_ele.run_js(script, *args, as_expr=as_expr) if script.startswith('this.scrollIntoView'):
return self.frame_ele.run_js(script, *args, as_expr=as_expr)
else:
return self.doc_ele.run_js(script, *args, as_expr=as_expr)
def parent(self, level_or_loc=1): def parent(self, level_or_loc=1):
"""返回上面某一级父元素,可指定层数或用查询语法定位 """返回上面某一级父元素,可指定层数或用查询语法定位
@ -382,6 +390,22 @@ class ChromiumFrame(ChromiumBase):
self._check_ok() self._check_ok()
return self.frame_ele.afters(filter_loc, timeout) return self.frame_ele.afters(filter_loc, timeout)
def get_screenshot(self, path=None, as_bytes=None, full_page=False, left_top=None, right_bottom=None):
"""对页面进行截图可对整个网页、可见网页、指定范围截图。对可视范围外截图需要90以上版本浏览器支持
:param path: 完整路径后缀可选 'jpg','jpeg','png','webp'
:param as_bytes: 是否已字节形式返回图片可选 'jpg','jpeg','png','webp'生效时path参数无效
:param full_page: 是否整页截图为True截取整个网页为False截取可视窗口
:param left_top: 截取范围左上角坐标
:param right_bottom: 截取范围右下角角坐标
:return: 图片完整路径或字节文本
"""
if full_page:
raise RuntimeError('暂未实现对iframe全页截图功能。')
if left_top is None and right_bottom is None:
return self.frame_ele.get_screenshot(path=path, as_bytes=as_bytes)
else:
raise RuntimeError('暂未实现对异域iframe内元素截图功能。')
def _ele(self, loc_or_ele, timeout=None, single=True, relative=False): def _ele(self, loc_or_ele, timeout=None, single=True, relative=False):
"""在frame内查找单个元素 """在frame内查找单个元素
:param loc_or_ele: 定位符或元素对象 :param loc_or_ele: 定位符或元素对象

View File

@ -6,7 +6,7 @@
from typing import Union, Tuple, List, Any from typing import Union, Tuple, List, Any
from .chromium_base import ChromiumBase, ChromiumPageScroll, ChromiumBaseSetter from .chromium_base import ChromiumBase, ChromiumPageScroll, ChromiumBaseSetter
from .chromium_element import ChromiumElement from .chromium_element import ChromiumElement, Locations
class ChromiumFrame(ChromiumBase): class ChromiumFrame(ChromiumBase):
@ -83,6 +83,9 @@ class ChromiumFrame(ChromiumBase):
@property @property
def location(self) -> Tuple[int, int]: ... def location(self) -> Tuple[int, int]: ...
@property
def locations(self) -> Locations: ...
@property @property
def xpath(self) -> str: ... def xpath(self) -> str: ...

View File

@ -81,7 +81,7 @@ class Keys:
# ZENKAKU_HANKAKU = '\ue040' # ZENKAKU_HANKAKU = '\ue040'
_keyDefinitions = { keyDefinitions = {
'0': {'keyCode': 48, 'key': '0', 'code': 'Digit0'}, '0': {'keyCode': 48, 'key': '0', 'code': 'Digit0'},
'1': {'keyCode': 49, 'key': '1', 'code': 'Digit1'}, '1': {'keyCode': 49, 'key': '1', 'code': 'Digit1'},
'2': {'keyCode': 50, 'key': '2', 'code': 'Digit2'}, '2': {'keyCode': 50, 'key': '2', 'code': 'Digit2'},
@ -333,20 +333,20 @@ _keyDefinitions = {
'}': {'keyCode': 221, 'key': '}', 'code': 'BracketRight'}, '}': {'keyCode': 221, 'key': '}', 'code': 'BracketRight'},
'"': {'keyCode': 222, 'key': '"', 'code': 'Quote'}, '"': {'keyCode': 222, 'key': '"', 'code': 'Quote'},
} }
_modifierBit = {'\ue00a': 1, modifierBit = {'\ue00a': 1,
'\ue009': 2, '\ue009': 2,
'\ue03d': 4, '\ue03d': 4,
'\ue008': 8} '\ue008': 8}
def _keys_to_typing(value) -> Tuple[int, str]: def keys_to_typing(value) -> Tuple[int, str]:
"""把要输入的内容连成字符串,去掉其中 ctrl 等键。 """把要输入的内容连成字符串,去掉其中 ctrl 等键。
返回的modifier表示是否有按下组合键""" 返回的modifier表示是否有按下组合键"""
typing: List[str] = [] typing: List[str] = []
modifier = 0 modifier = 0
for val in value: for val in value:
if val in ('\ue009', '\ue008', '\ue00a', '\ue03d'): if val in ('\ue009', '\ue008', '\ue00a', '\ue03d'):
modifier |= _modifierBit.get(val, 0) modifier |= modifierBit.get(val, 0)
continue continue
if isinstance(val, (int, float)): if isinstance(val, (int, float)):
val = str(val) val = str(val)
@ -359,7 +359,7 @@ def _keys_to_typing(value) -> Tuple[int, str]:
return modifier, ''.join(typing) return modifier, ''.join(typing)
def _keyDescriptionForString(_modifiers: int, keyString: str) -> Dict: # noqa: C901 def keyDescriptionForString(_modifiers: int, keyString: str) -> Dict: # noqa: C901
shift = _modifiers & 8 shift = _modifiers & 8
description = {'key': '', description = {'key': '',
'keyCode': 0, 'keyCode': 0,

View File

@ -6,7 +6,7 @@ with open("README.md", "r", encoding='utf-8') as fh:
setup( setup(
name="DrissionPage", name="DrissionPage",
version="3.1.4", version="3.2.0",
author="g1879", author="g1879",
author_email="g1879@qq.com", author_email="g1879@qq.com",
description="A module that integrates selenium and requests session, encapsulates common page operations.", description="A module that integrates selenium and requests session, encapsulates common page operations.",
@ -37,7 +37,7 @@ setup(
python_requires='>=3.6', python_requires='>=3.6',
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'dp = DrissionPage.cli:main', 'dp = DrissionPage.common.cli:main',
], ],
}, },
) )