mirror of
https://gitee.com/g1879/DrissionPage.git
synced 2024-12-10 04:00:23 +08:00
优化滚动到元素逻辑;修复iframe内截图不准确问题
This commit is contained in:
parent
11d4a70090
commit
4354f21583
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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: 定位符或元素对象
|
||||||
|
@ -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: ...
|
||||||
|
|
||||||
|
@ -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,
|
4
setup.py
4
setup.py
@ -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',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user