优化滚动到元素逻辑;修复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 .keys import Keys
from .common.keys import Keys
# 旧版页面类和启动配置类
from .mixpage.mix_page import MixPage

View File

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

View File

@ -937,8 +937,17 @@ class ChromiumPageScroll(ChromiumScroll):
:param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串
:return: None
"""
ID = None
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):

View File

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

View File

@ -220,6 +220,11 @@ class ChromiumFrame(ChromiumBase):
self._check_ok()
return self.frame_ele.location
@property
def locations(self):
"""返回用于获取元素位置的对象"""
return self.frame_ele.locations
@property
def xpath(self):
"""返回frame的xpath绝对路径"""
@ -296,7 +301,10 @@ class ChromiumFrame(ChromiumBase):
:return: 运行的结果
"""
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):
"""返回上面某一级父元素,可指定层数或用查询语法定位
@ -382,6 +390,22 @@ class ChromiumFrame(ChromiumBase):
self._check_ok()
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):
"""在frame内查找单个元素
:param loc_or_ele: 定位符或元素对象

View File

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

View File

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

View File

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