Merge pull request !47 from g1879/dev
This commit is contained in:
g1879 2024-06-25 08:25:34 +00:00 committed by Gitee
commit 21b391ef94
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
47 changed files with 1602 additions and 690 deletions

View File

@ -14,4 +14,4 @@ from ._configs.chromium_options import ChromiumOptions
from ._configs.session_options import SessionOptions
__all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__']
__version__ = '4.0.4.21'
__version__ = '4.0.5.3'

View File

@ -169,14 +169,8 @@ class DrissionElement(BaseElement):
loc = loc[1].lstrip('./')
node = self._ele(f'xpath:./{loc}', timeout=timeout, index=index, relative=True, raise_err=False)
if node:
return node
if Settings.raise_when_ele_not_found:
raise ElementNotFoundError(None, 'child()', {'locator': locator, 'index': index,
'ele_only': ele_only})
else:
return NoneElement(self.owner, 'child()', {'locator': locator, 'index': index, 'ele_only': ele_only})
return node if node else NoneElement(self.owner, 'child()',
{'locator': locator, 'index': index, 'ele_only': ele_only})
def prev(self, locator='', index=1, timeout=None, ele_only=True):
"""返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
@ -289,12 +283,8 @@ class DrissionElement(BaseElement):
index = locator
locator = ''
node = self._get_relatives(index, locator, direction, brother, timeout, ele_only)
if node:
return node
if Settings.raise_when_ele_not_found:
raise ElementNotFoundError(None, func, {'locator': locator, 'index': index, 'ele_only': ele_only})
else:
return NoneElement(self.owner, func, {'locator': locator, 'index': index, 'ele_only': ele_only})
return node if node else NoneElement(self.owner, func,
{'locator': locator, 'index': index, 'ele_only': ele_only})
def _get_relatives(self, index=None, locator='', direction='following', brother=True, timeout=.5, ele_only=True):
"""按要求返回兄弟元素或节点组成的列表
@ -411,7 +401,8 @@ class BasePage(BaseParser):
if p.exists():
url = str(p.absolute())
is_file = True
self._url = quote(url, safe='-_.~!*\'"();:@&=+$,/\\?#[]%')
self._url = url if is_file else quote(url, safe='-_.~!*\'"();:@&=+$,/\\?#[]%')
retry = retry if retry is not None else self.retry_times
interval = interval if interval is not None else self.retry_interval
return retry, interval, is_file

View File

@ -12,6 +12,7 @@ from DownloadKit import DownloadKit
from .._elements.none_element import NoneElement
from .._elements.session_element import SessionElement
from .._functions.elements import SessionElementsList
from .._pages.chromium_page import ChromiumPage
from .._pages.session_page import SessionPage
from .._pages.web_page import WebPage
@ -37,7 +38,7 @@ class BaseParser(object):
locator: Union[Tuple[str, str], str, BaseElement, None] = None,
index: int = 1) -> SessionElement: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> List[SessionElement]: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ...
def _ele(self,
locator: Union[Tuple[str, str], str],

View File

@ -5,7 +5,6 @@
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
"""
from os import waitpid
from pathlib import Path
from shutil import rmtree
from time import perf_counter, sleep
@ -135,7 +134,8 @@ class Browser(object):
def tab_ids(self):
"""返回所有标签页id组成的列表"""
j = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp因为顺序不对
return [i['id'] for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]
return [i['id'] for i in j if i['type'] in ('page', 'webview')
and not i['url'].startswith('devtools://')]
@property
def process_id(self):
@ -143,7 +143,7 @@ class Browser(object):
return self._process_id
def find_tabs(self, title=None, url=None, tab_type=None):
"""查找符合条件的tab返回它们组成的列表
"""查找符合条件的tab返回它们组成的列表title和url是与关系
:param title: 要匹配title的文本
:param url: 要匹配url的文本
:param tab_type: tab类型可用列表输入多个
@ -274,10 +274,6 @@ class Browser(object):
if ok:
break
sleep(.05)
if self.process_id:
waitpid(self.process_id, 0)
def _on_disconnect(self):
self.page._on_disconnect()
@ -293,4 +289,4 @@ class Browser(object):
break
except (PermissionError, FileNotFoundError, OSError):
pass
sleep(.05)
sleep(.03)

View File

@ -10,12 +10,12 @@ from queue import Queue, Empty
from threading import Thread, Event
from time import perf_counter, sleep
from requests import get
from requests import Session
from websocket import (WebSocketTimeoutException, WebSocketConnectionClosedException, create_connection,
WebSocketException, WebSocketBadStatusException)
from .._functions.settings import Settings
from ..errors import PageDisconnectedError, TargetNotFoundError
from ..errors import PageDisconnectedError
class Driver(object):
@ -201,13 +201,10 @@ class Driver(object):
try:
self._ws = create_connection(self._websocket_url, enable_multithread=True, suppress_origin=True)
except WebSocketBadStatusException as e:
txt = str(e)
if 'No such target id' in txt:
raise TargetNotFoundError(f'找不到页面:{self.id}')
elif 'Handshake status 403 Forbidden' in txt:
if 'Handshake status 403 Forbidden' in str(e):
raise RuntimeError('请升级websocket-client库。')
else:
raise e
return
self._recv_th.start()
self._handle_event_th.start()
return True
@ -274,11 +271,13 @@ class BrowserDriver(Driver):
self._created = True
BrowserDriver.BROWSERS[tab_id] = self
super().__init__(tab_id, tab_type, address, owner)
self._control_session = Session()
self._control_session.trust_env = False
def __repr__(self):
return f'<BrowserDriver {self.id}>'
def get(self, url):
r = get(url, headers={'Connection': 'close'})
r = self._control_session.get(url, headers={'Connection': 'close'})
r.close()
return r

View File

@ -9,7 +9,7 @@ from queue import Queue
from threading import Thread, Event
from typing import Union, Callable, Dict, Optional
from requests import Response
from requests import Response, Session
from websocket import WebSocket
from .browser import Browser
@ -68,14 +68,10 @@ class Driver(object):
class BrowserDriver(Driver):
BROWSERS: Dict[str, Driver] = ...
owner: Browser = ...
_control_session: Session = ...
def __new__(cls, tab_id: str, tab_type: str, address: str, owner: Browser): ...
def __init__(self, tab_id: str, tab_type: str, address: str, owner: Browser):
"""
:rtype: object
"""
...
def __init__(self, tab_id: str, tab_type: str, address: str, owner: Browser): ...
def get(self, url) -> Response: ...

View File

@ -389,7 +389,7 @@ class ChromiumOptions(object):
return self
def set_paths(self, browser_path=None, local_port=None, address=None, download_path=None,
user_data_path=None, cache_path=None, debugger_address=None):
user_data_path=None, cache_path=None):
"""快捷的路径设置函数
:param browser_path: 浏览器可执行文件路径
:param local_port: 本地端口号
@ -399,7 +399,6 @@ class ChromiumOptions(object):
:param cache_path: 缓存路径
:return: 当前对象
"""
address = address or debugger_address
if browser_path is not None:
self.set_browser_path(browser_path)
@ -568,50 +567,3 @@ class ChromiumOptions(object):
def __repr__(self):
return f'<ChromiumOptions at {id(self)}>'
# ---------------即将废弃--------------
@property
def debugger_address(self):
"""返回浏览器地址ip:port"""
return self._address
@debugger_address.setter
def debugger_address(self, address):
"""设置浏览器地址格式ip:port"""
self.set_address(address)
def set_page_load_strategy(self, value):
return self.set_load_mode(value)
def set_headless(self, on_off=True):
"""设置是否隐藏浏览器界面
:param on_off: 开或关
:return: 当前对象
"""
on_off = 'new' if on_off else 'false'
return self.set_argument('--headless', on_off)
def set_no_imgs(self, on_off=True):
"""设置是否加载图片
:param on_off: 开或关
:return: 当前对象
"""
on_off = None if on_off else False
return self.set_argument('--blink-settings=imagesEnabled=false', on_off)
def set_no_js(self, on_off=True):
"""设置是否禁用js
:param on_off: 开或关
:return: 当前对象
"""
on_off = None if on_off else False
return self.set_argument('--disable-javascript', on_off)
def set_mute(self, on_off=True):
"""设置是否静音
:param on_off: 开或关
:return: 当前对象
"""
on_off = None if on_off else False
return self.set_argument('--mute-audio', on_off)

View File

@ -457,17 +457,6 @@ class SessionOptions(object):
self._adapters = [(k, i) for k, i in session.adapters.items()]
return self
# --------------即将废弃---------------
def set_paths(self, download_path=None):
"""设置默认下载路径
:param download_path: 下载路径
:return: 返回当前对象
"""
if download_path is not None:
self._download_path = str(download_path)
return self
def __repr__(self):
return f'<SessionOptions at {id(self)}>'

View File

@ -17,8 +17,8 @@ from .none_element import NoneElement
from .session_element import make_session_ele
from .._base.base import DrissionElement, BaseElement
from .._functions.keys import input_text_or_keys
from .._functions.locator import get_loc
from .._functions.settings import Settings
from .._functions.locator import get_loc, locator_to_tuple
from .._functions.elements import ChromiumElementsList
from .._functions.web import make_absolute_link, get_ele_txt, format_html, is_js_func, offset_scroll, get_blob
from .._units.clicker import Clicker
from .._units.rect import ElementRect
@ -27,8 +27,8 @@ from .._units.selector import SelectElement
from .._units.setter import ChromiumElementSetter
from .._units.states import ElementStates, ShadowRootStates
from .._units.waiter import ElementWaiter
from ..errors import (ContextLostError, ElementLostError, JavaScriptError, ElementNotFoundError,
CDPError, NoResourceError, AlertExistsError)
from ..errors import ContextLostError, ElementLostError, JavaScriptError, CDPError, NoResourceError, AlertExistsError, \
NoRectError
__FRAME_ELEMENT__ = ('iframe', 'frame')
@ -55,6 +55,7 @@ class ChromiumElement(DrissionElement):
self._tag = None
self._wait = None
self._type = 'ChromiumElement'
self._doc_id = None
if node_id and obj_id and backend_id:
self._node_id = node_id
@ -75,9 +76,6 @@ class ChromiumElement(DrissionElement):
else:
raise ElementLostError
doc = self.run_js('return this.ownerDocument;')
self._doc_id = doc['objectId'] if doc else None
def __repr__(self):
attrs = [f"{k}='{v}'" for k, v in self.attrs.items()]
return f'<ChromiumElement {self.tag} {" ".join(attrs)}>'
@ -93,14 +91,6 @@ class ChromiumElement(DrissionElement):
def __eq__(self, other):
return self._backend_id == getattr(other, '_backend_id', None)
def __getattr__(self, item):
"""获取元素属性
:param item: 属性名
:return: 属性值
"""
a = self.attr(item)
return a if a is not None else self.property(item)
@property
def tag(self):
"""返回元素tag"""
@ -221,25 +211,6 @@ class ChromiumElement(DrissionElement):
def value(self):
return self.property('value')
# -----即将废弃开始--------
@property
def location(self):
"""返回元素左上角的绝对坐标"""
return self.rect.location
@property
def size(self):
"""返回元素宽和高组成的元组"""
return self.rect.size
def prop(self, prop):
return self.property(prop)
def get_src(self, timeout=None, base64_to_bytes=True):
return self.src(timeout=timeout, base64_to_bytes=base64_to_bytes)
# -----即将废弃结束--------
def check(self, uncheck=False, by_js=False):
"""选中或取消选中当前元素
:param uncheck: 是否取消选中
@ -328,7 +299,7 @@ class ChromiumElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 直接子元素或节点文本组成的列表
"""
return super().children(locator, timeout, ele_only=ele_only)
return ChromiumElementsList(self.owner, super().children(locator, timeout, ele_only=ele_only))
def prevs(self, locator='', timeout=None, ele_only=True):
"""返回当前元素前面符合条件的同级元素或节点组成的列表,可用查询语法筛选
@ -337,7 +308,7 @@ class ChromiumElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 兄弟元素或节点文本组成的列表
"""
return super().prevs(locator, timeout, ele_only=ele_only)
return ChromiumElementsList(self.owner, super().prevs(locator, timeout, ele_only=ele_only))
def nexts(self, locator='', timeout=None, ele_only=True):
"""返回当前元素后面符合条件的同级元素或节点组成的列表,可用查询语法筛选
@ -346,7 +317,7 @@ class ChromiumElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 兄弟元素或节点文本组成的列表
"""
return super().nexts(locator, timeout, ele_only=ele_only)
return ChromiumElementsList(self.owner, super().nexts(locator, timeout, ele_only=ele_only))
def befores(self, locator='', timeout=None, ele_only=True):
"""返回文档中当前元素前面符合条件的元素或节点组成的列表,可用查询语法筛选
@ -356,7 +327,7 @@ class ChromiumElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 本元素前面的元素或节点组成的列表
"""
return super().befores(locator, timeout, ele_only=ele_only)
return ChromiumElementsList(self.owner, super().befores(locator, timeout, ele_only=ele_only))
def afters(self, locator='', timeout=None, ele_only=True):
"""返回文档中当前元素后面符合条件的元素或节点组成的列表,可用查询语法筛选
@ -366,7 +337,137 @@ class ChromiumElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 本元素后面的元素或节点组成的列表
"""
return super().afters(locator, timeout, ele_only=ele_only)
return ChromiumElementsList(self.owner, super().afters(locator, timeout, ele_only=ele_only))
def over(self, timeout=None):
"""获取覆盖在本元素上最上层的元素
:param timeout: 等待元素出现的超时时间
:return: 元素对象
"""
timeout = timeout if timeout is None else self.owner.timeout
bid = self.wait.covered(timeout=timeout)
if bid:
return ChromiumElement(owner=self.owner, backend_id=bid)
else:
return NoneElement(page=self.owner, method='on()', args={'timeout': timeout})
def offset(self, offset_x, offset_y):
"""获取相对本元素左上角左边指定偏移量位置的元素
:param offset_x: 横坐标偏移量向右为正
:param offset_y: 纵坐标偏移量向下为正
:return: 元素对象
"""
x, y = self.rect.location
try:
return ChromiumElement(owner=self.owner,
backend_id=self.owner.run_cdp('DOM.getNodeForLocation', x=x + offset_x,
y=y + offset_y, includeUserAgentShadowDOM=True,
ignorePointerEventsNone=False)['backendNodeId'])
except CDPError:
return NoneElement(page=self.owner, method='offset()', args={'offset_x': offset_x, 'offset_y': offset_y})
def east(self, loc_or_pixel=None, index=1):
"""获取元素右边某个指定元素
:param loc_or_pixel: 定位符只支持str或int且不支持xpath和css方式传入int按像素距离获取
:param index: 第几个从1开始
:return: 获取到的元素对象
"""
return self._get_relative_eles(mode='east', locator=loc_or_pixel, index=index)
def south(self, loc_or_pixel=None, index=1):
"""获取元素下方某个指定元素
:param loc_or_pixel: 定位符只支持str或int且不支持xpath和css方式传入int按像素距离获取
:param index: 第几个从1开始
:return: 获取到的元素对象
"""
return self._get_relative_eles(mode='south', locator=loc_or_pixel, index=index)
def west(self, loc_or_pixel=None, index=1):
"""获取元素左边某个指定元素
:param loc_or_pixel: 定位符只支持str或int且不支持xpath和css方式传入int按像素距离获取
:param index: 第几个从1开始
:return: 获取到的元素对象
"""
return self._get_relative_eles(mode='west', locator=loc_or_pixel, index=index)
def north(self, loc_or_pixel=None, index=1):
"""获取元素上方某个指定元素
:param loc_or_pixel: 定位符只支持str或int且不支持xpath和css方式传入int按像素距离获取
:param index: 第几个从1开始
:return: 获取到的元素对象
"""
return self._get_relative_eles(mode='north', locator=loc_or_pixel, index=index)
def _get_relative_eles(self, mode='north', locator=None, index=1):
"""获取元素下方某个指定元素
:param locator: 定位符只支持str或int且不支持xpath和css方式
:param index: 第几个从1开始
:return: 获取到的元素对象
"""
if locator and not (isinstance(locator, str) and not locator.startswith(
('x:', 'xpath:', 'x=', 'xpath=', 'c:', 'css:', 'c=', 'css=')) or isinstance(locator, int)):
raise ValueError('locator参数只能是str格式且不支持xpath和css形式。')
rect = self.states.has_rect
if not rect:
raise NoRectError
if mode == 'east':
cdp_data = {'x': int(rect[1][0]), 'y': int(self.rect.midpoint[1]),
'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False}
variable = 'x'
minus = False
elif mode == 'south':
cdp_data = {'x': int(self.rect.midpoint[0]), 'y': int(rect[2][1]),
'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False}
variable = 'y'
minus = False
elif mode == 'west':
cdp_data = {'x': int(rect[0][0]), 'y': int(self.rect.midpoint[1]),
'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False}
variable = 'x'
minus = True
else: # north
cdp_data = {'x': int(self.rect.midpoint[0]), 'y': int(rect[0][1]),
'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False}
variable = 'y'
minus = True
if isinstance(locator, int):
if minus:
cdp_data[variable] -= locator
else:
cdp_data[variable] += locator
try:
return ChromiumElement(owner=self.owner,
backend_id=self.owner.run_cdp('DOM.getNodeForLocation',
**cdp_data)['backendNodeId'])
except CDPError:
return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator})
num = 0
value = -8 if minus else 8
size = self.owner.rect.size
max_len = size[0] if mode == 'east' else size[1]
loc_data = locator_to_tuple(locator) if locator else None
curr_ele = None
while 0 < cdp_data[variable] < max_len:
cdp_data[variable] += value
try:
bid = self.owner.run_cdp('DOM.getNodeForLocation', **cdp_data)['backendNodeId']
if bid == curr_ele:
continue
else:
curr_ele = bid
ele = ChromiumElement(self.owner, backend_id=bid)
if loc_data is None or _check_ele(ele, loc_data):
num += 1
if num == index:
return ele
except:
pass
return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator})
def attr(self, attr):
"""返回一个attribute属性值
@ -375,14 +476,14 @@ class ChromiumElement(DrissionElement):
"""
attrs = self.attrs
if attr == 'href': # 获取href属性时返回绝对url
link = attrs.get('href', None)
link = attrs.get('href')
if not link or link.lower().startswith(('javascript:', 'mailto:')):
return link
else:
return make_absolute_link(link, self.property('baseURI'))
elif attr == 'src':
return make_absolute_link(attrs.get('src', None), self.property('baseURI'))
return make_absolute_link(attrs.get('src'), self.property('baseURI'))
elif attr == 'text':
return self.text
@ -459,14 +560,7 @@ class ChromiumElement(DrissionElement):
:param index: 获取第几个从1开始可传入负数获取倒数第几个
:return: SessionElement对象或属性文本
"""
r = make_session_ele(self, locator, index=index)
if isinstance(r, NoneElement):
if Settings.raise_when_ele_not_found:
raise ElementNotFoundError(None, 's_ele()', {'locator': locator})
else:
r.method = 's_ele()'
r.args = {'locator': locator}
return r
return make_session_ele(self, locator, index=index, method='s_ele()')
def s_eles(self, locator=None):
"""查找所有符合条件的元素以SessionElement列表形式返回
@ -638,6 +732,7 @@ class ChromiumElement(DrissionElement):
self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
return
self.wait.clickable(wait_moved=False, timeout=.5)
if clear and vals not in ('\n', '\ue007'):
self.clear(by_js=False)
else:
@ -686,7 +781,7 @@ class ChromiumElement(DrissionElement):
"""拖拽当前元素到相对位置
:param offset_x: x变化值
:param offset_y: y变化值
:param duration: 拖动用时传入0即瞬间到j
:param duration: 拖动用时传入0即瞬间到
:return: None
"""
curr_x, curr_y = self.rect.midpoint
@ -704,7 +799,6 @@ class ChromiumElement(DrissionElement):
ele_or_loc = ele_or_loc.rect.midpoint
elif not isinstance(ele_or_loc, (list, tuple)):
raise TypeError('需要ChromiumElement对象或坐标。')
self.owner.actions.hold(self).move_to(ele_or_loc, duration=duration).release()
def _get_obj_id(self, node_id=None, backend_id=None):
@ -917,13 +1011,8 @@ class ShadowRoot(BaseElement):
loc = f'xpath:./{loc}'
ele = self._ele(loc, index=index, relative=True)
if ele:
return ele
if Settings.raise_when_ele_not_found:
raise ElementNotFoundError(None, 'child()', {'locator': locator, 'index': index})
else:
return NoneElement(self.owner, 'child()', {'locator': locator, 'index': index})
return ele if ele else NoneElement(self.owner, 'child()', {'locator': locator, 'index': index})
def next(self, locator='', index=1):
"""返回当前元素后面一个符合条件的同级元素,可用查询语法筛选,可指定返回筛选结果的第几个
@ -938,13 +1027,8 @@ class ShadowRoot(BaseElement):
loc = loc[1].lstrip('./')
xpath = f'xpath:./{loc}'
ele = self.parent_ele._ele(xpath, index=index, relative=True)
if ele:
return ele
if Settings.raise_when_ele_not_found:
raise ElementNotFoundError(None, 'next()', {'locator': locator, 'index': index})
else:
return NoneElement(self.owner, 'next()', {'locator': locator, 'index': index})
return ele if ele else NoneElement(self.owner, 'next()', {'locator': locator, 'index': index})
def before(self, locator='', index=1):
"""返回文档中当前元素前面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个
@ -960,13 +1044,8 @@ class ShadowRoot(BaseElement):
loc = loc[1].lstrip('./')
xpath = f'xpath:./preceding::{loc}'
ele = self.parent_ele._ele(xpath, index=index, relative=True)
if ele:
return ele
if Settings.raise_when_ele_not_found:
raise ElementNotFoundError(None, 'before()', {'locator': locator, 'index': index})
else:
return NoneElement(self.owner, 'before()', {'locator': locator, 'index': index})
return ele if ele else NoneElement(self.owner, 'before()', {'locator': locator, 'index': index})
def after(self, locator='', index=1):
"""返回文档中此当前元素后面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个
@ -976,12 +1055,7 @@ class ShadowRoot(BaseElement):
:return: 本元素后面的某个元素或节点
"""
nodes = self.afters(locator=locator)
if nodes:
return nodes[index - 1]
if Settings.raise_when_ele_not_found:
raise ElementNotFoundError(None, 'after()', {'locator': locator, 'index': index})
else:
return NoneElement(self.owner, 'after()', {'locator': locator, 'index': index})
return nodes[index - 1] if nodes else NoneElement(self.owner, 'after()', {'locator': locator, 'index': index})
def children(self, locator=''):
"""返回当前元素符合条件的直接子元素或节点组成的列表,可用查询语法筛选
@ -1115,8 +1189,8 @@ class ShadowRoot(BaseElement):
r = make_chromium_eles(self.owner, _ids=node_id, is_obj_id=False)
return None if r is False else r
else:
node_ids = [self.owner.run_cdp('DOM.querySelector',
nodeId=self._node_id, selector=i)['nodeId'] for i in css]
node_ids = [self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId']
for i in css]
if 0 in node_ids:
return None
r = make_chromium_eles(self.owner, _ids=node_ids, index=index, is_obj_id=False)
@ -1219,8 +1293,12 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True):
res = ele.owner.run_cdp('Runtime.getProperties', objectId=res['result']['objectId'],
ownProperties=True)['result'][:-1]
if index is None:
r = [make_chromium_eles(ele.owner, _ids=i['value']['objectId'], is_obj_id=True)
if i['value']['type'] == 'object' else i['value']['value'] for i in res]
r = ChromiumElementsList(page=ele.owner)
for i in res:
if i['value']['type'] == 'object':
r.append(make_chromium_eles(ele.owner, _ids=i['value']['objectId'], is_obj_id=True))
else:
r.append(i['value']['value'])
return None if False in r else r
else:
@ -1244,7 +1322,7 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True):
if result:
return result
return NoneElement(ele.owner) if index is not None else []
return NoneElement(ele.owner) if index is not None else ChromiumElementsList(page=ele.owner)
def find_by_css(ele, selector, index, timeout):
@ -1290,7 +1368,7 @@ def find_by_css(ele, selector, index, timeout):
if result:
return result
return NoneElement(ele.owner) if index is not None else []
return NoneElement(ele.owner) if index is not None else ChromiumElementsList(page=ele.owner)
def make_chromium_eles(page, _ids, index=1, is_obj_id=True, ele_only=False):
@ -1322,7 +1400,7 @@ def make_chromium_eles(page, _ids, index=1, is_obj_id=True, ele_only=False):
return get_node_func(page, obj_id, ele_only)
else: # 获取全部
nodes = []
nodes = ChromiumElementsList(page=page)
for obj_id in _ids:
tmp = get_node_func(page, obj_id, ele_only)
if tmp is False:
@ -1569,3 +1647,59 @@ class Pseudo(object):
def after(self):
"""返回当前元素的::after伪元素内容"""
return self._ele.style('content', 'after')
def _check_ele(ele, loc_data):
"""检查元素是否符合loc_data指定的要求
:param ele: 元素对象
:param loc_data: 格式 {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]}
:return: bool
"""
attrs = ele.attrs
if loc_data['and']:
ok = True
for i in loc_data['args']:
name, symbol, value, deny = i
if name == 'tag()':
arg = ele.tag
symbol = '='
elif name == 'text()':
arg = ele.raw_text
elif name is None:
arg = None
else:
arg = attrs.get(name, '')
if ((symbol == '=' and ((deny and arg == value) or (not deny and arg != value)))
or (symbol == ':' and ((deny and value in arg) or (not deny and value not in arg)))
or (symbol == '^' and ((deny and arg.startswith(value))
or (not deny and not arg.startswith(value))))
or (symbol == '$' and ((deny and arg.endswith(value)) or (not deny and not arg.endswith(value))))
or (arg is None and attrs)):
ok = False
break
else:
ok = False
for i in loc_data['args']:
name, value, symbol, deny = i
if name == 'tag()':
arg = ele.tag
symbol = '='
elif name == 'text()':
arg = ele.text
elif name is None:
arg = None
else:
arg = attrs.get(name, '')
if ((symbol == '=' and ((not deny and arg == value) or (deny and arg != value)))
or (symbol == ':' and ((not deny and value in arg) or (deny and value not in arg)))
or (symbol == '^' and ((not deny and arg.startswith(value))
or (deny and not arg.startswith(value))))
or (symbol == '$' and ((not deny and arg.endswith(value)) or (deny and not arg.endswith(value))))
or (arg is None and not attrs)):
ok = True
break
return ok

View File

@ -10,6 +10,7 @@ from typing import Union, Tuple, List, Any, Literal, Optional
from .._base.base import DrissionElement, BaseElement
from .._elements.session_element import SessionElement
from .._functions.elements import SessionElementsList, ChromiumElementsList
from .._pages.chromium_base import ChromiumBase
from .._pages.chromium_frame import ChromiumFrame
from .._pages.chromium_page import ChromiumPage
@ -56,8 +57,6 @@ class ChromiumElement(DrissionElement):
def __eq__(self, other: ChromiumElement) -> bool: ...
def __getattr__(self, item: str) -> str: ...
@property
def tag(self) -> str: ...
@ -138,27 +137,44 @@ class ChromiumElement(DrissionElement):
def children(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ...
ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ...
def prevs(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ...
ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ...
def nexts(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ...
ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ...
def befores(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ...
ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ...
def afters(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ...
ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ...
def over(self, timeout: float = None) -> ChromiumElement: ...
def south(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement: ...
def north(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement: ...
def west(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement: ...
def east(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement: ...
def offset(self, offset_x: int, offset_y: int) -> ChromiumElement: ...
def _get_relative_eles(self,
mode: str = 'north',
locator: Union[int, str] = None,
index: int = 1) -> ChromiumElement: ...
@property
def wait(self) -> ElementWaiter: ...
@ -188,21 +204,20 @@ class ChromiumElement(DrissionElement):
def eles(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> List[ChromiumElement]: ...
timeout: float = None) -> ChromiumElementsList: ...
def s_ele(self,
locator: Union[Tuple[str, str], str] = None,
index: int = 1) -> SessionElement: ...
def s_eles(self, locator: Union[Tuple[str, str], str] = None) -> List[SessionElement]: ...
def s_eles(self, locator: Union[Tuple[str, str], str] = None) -> SessionElementsList: ...
def _find_elements(self,
locator: Union[Tuple[str, str], str],
timeout: float = None,
index: Optional[int] = 1,
relative: bool = False,
raise_err: bool = False) -> Union[ChromiumElement, ChromiumFrame,
List[Union[ChromiumElement, ChromiumFrame]]]: ...
raise_err: bool = False) -> Union[ChromiumElement, ChromiumFrame, ChromiumElementsList]: ...
def style(self, style: str, pseudo_ele: str = '') -> str: ...
@ -318,21 +333,20 @@ class ShadowRoot(BaseElement):
def eles(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> List[ChromiumElement]: ...
timeout: float = None) -> ChromiumElementsList: ...
def s_ele(self,
locator: Union[Tuple[str, str], str] = None,
index: int = 1) -> SessionElement: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> List[SessionElement]: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ...
def _find_elements(self,
locator: Union[Tuple[str, str], str],
timeout: float = None,
index: Optional[int] = 1,
relative: bool = False,
raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, str,
List[Union[ChromiumElement, ChromiumFrame, str]]]: ...
raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, str, ChromiumElementsList]: ...
def _get_node_id(self, obj_id: str) -> int: ...
@ -366,7 +380,7 @@ def make_chromium_eles(page: Union[ChromiumBase, ChromiumPage, WebPage, Chromium
index: Optional[int] = 1,
is_obj_id: bool = True,
ele_only: bool = False
) -> Union[ChromiumElement, ChromiumFrame, List[Union[ChromiumElement, ChromiumFrame]]]: ...
) -> Union[ChromiumElement, ChromiumFrame, ChromiumElementsList]: ...
def make_js_for_find_ele_by_xpath(xpath: str, type_txt: str, node_txt: str) -> str: ...

View File

@ -5,11 +5,20 @@
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
"""
from .._functions.settings import Settings
from ..errors import ElementNotFoundError
class NoneElement(object):
def __init__(self, page=None, method=None, args=None):
"""
:param page: 元素所在页面
:param method: 查找元素的方法
:param args: 查找元素的参数
"""
if method and Settings.raise_when_ele_not_found: # 无传入method时不自动抛出由调用者处理
raise ElementNotFoundError(None, method=method, arguments=args)
if page:
self._none_ele_value = page._none_ele_value
self._none_ele_return_value = page._none_ele_return_value

View File

@ -6,13 +6,14 @@
@License : BSD 3-Clause.
"""
from html import unescape
from re import match, sub, DOTALL
from re import match, sub, DOTALL, search
from lxml.etree import tostring
from lxml.html import HtmlElement, fromstring
from .none_element import NoneElement
from .._base.base import DrissionElement, BasePage, BaseElement
from .._functions.elements import SessionElementsList
from .._functions.locator import get_loc
from .._functions.web import get_ele_txt, make_absolute_link
@ -50,13 +51,6 @@ class SessionElement(DrissionElement):
def __eq__(self, other):
return self.xpath == getattr(other, 'xpath', None)
def __getattr__(self, item):
"""获取元素属性
:param item: 属性名
:return: 属性值
"""
return self.attr(item)
@property
def tag(self):
"""返回元素类型"""
@ -156,7 +150,7 @@ class SessionElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 直接子元素或节点文本组成的列表
"""
return super().children(locator, timeout, ele_only=ele_only)
return SessionElementsList(self.owner, super().children(locator, timeout, ele_only=ele_only))
def prevs(self, locator='', timeout=None, ele_only=True):
"""返回当前元素前面符合条件的同级元素或节点组成的列表,可用查询语法筛选
@ -165,7 +159,7 @@ class SessionElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 同级元素或节点文本组成的列表
"""
return super().prevs(locator, timeout, ele_only=ele_only)
return SessionElementsList(self.owner, super().prevs(locator, timeout, ele_only=ele_only))
def nexts(self, locator='', timeout=None, ele_only=True):
"""返回当前元素后面符合条件的同级元素或节点组成的列表,可用查询语法筛选
@ -174,7 +168,7 @@ class SessionElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 同级元素或节点文本组成的列表
"""
return super().nexts(locator, timeout, ele_only=ele_only)
return SessionElementsList(self.owner, super().nexts(locator, timeout, ele_only=ele_only))
def befores(self, locator='', timeout=None, ele_only=True):
"""返回文档中当前元素前面符合条件的元素或节点组成的列表,可用查询语法筛选
@ -184,7 +178,7 @@ class SessionElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 本元素前面的元素或节点组成的列表
"""
return super().befores(locator, timeout, ele_only=ele_only)
return SessionElementsList(self.owner, super().befores(locator, timeout, ele_only=ele_only))
def afters(self, locator='', timeout=None, ele_only=True):
"""返回文档中当前元素后面符合条件的元素或节点组成的列表,可用查询语法筛选
@ -194,7 +188,7 @@ class SessionElement(DrissionElement):
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 本元素后面的元素或节点组成的列表
"""
return super().afters(locator, timeout, ele_only=ele_only)
return SessionElementsList(self.owner, super().afters(locator, timeout, ele_only=ele_only))
def attr(self, name):
"""返回attribute属性值
@ -293,12 +287,13 @@ class SessionElement(DrissionElement):
return f'{path_str[1:]}' if mode == 'css' else path_str
def make_session_ele(html_or_ele, loc=None, index=1):
def make_session_ele(html_or_ele, loc=None, index=1, method=None):
"""从接收到的对象或html文本中查找元素返回SessionElement对象
如要直接从html生成SessionElement而不在下级查找loc输入None即可
:param html_or_ele: html文本BaseParser对象
:param loc: 定位元组或字符串为None时不在下级查找返回根元素
:param index: 获取第几个元素从1开始可传入负数获取倒数第几个None获取所有
:param method: 调用此方法的方法
:return: 返回SessionElement元素或列表或属性文本
"""
# ---------------处理定位符---------------
@ -353,8 +348,14 @@ def make_session_ele(html_or_ele, loc=None, index=1):
page = html_or_ele.owner
xpath = html_or_ele.xpath
# ChromiumElement兼容传入的元素在iframe内的情况
html = html_or_ele.owner.run_cdp('DOM.getOuterHTML', objectId=html_or_ele._doc_id)['outerHTML'] \
if html_or_ele._doc_id else html_or_ele.owner.html
if html_or_ele._doc_id is None:
doc = html_or_ele.run_js('return this.ownerDocument;')
html_or_ele._doc_id = doc['objectId'] if doc else False
if html_or_ele._doc_id:
html = html_or_ele.owner.run_cdp('DOM.getOuterHTML', objectId=html_or_ele._doc_id)['outerHTML']
else:
html = html_or_ele.owner.html
html_or_ele = fromstring(html)
html_or_ele = html_or_ele.xpath(xpath)[0]
@ -373,7 +374,11 @@ def make_session_ele(html_or_ele, loc=None, index=1):
# ShadowRoot
elif isinstance(html_or_ele, BaseElement):
page = html_or_ele.owner
html_or_ele = fromstring(html_or_ele.html)
html = html_or_ele.html
r = search(r'^<shadow_root>[ \n]*?<html>[ \n]*?(.*?)[ \n]*?</html>[ \n]*?</shadow_root>$', html)
if r:
html = r.group(1)
html_or_ele = fromstring(html)
else:
raise TypeError('html_or_ele参数只能是元素、页面对象或html文本。')
@ -390,12 +395,16 @@ def make_session_ele(html_or_ele, loc=None, index=1):
# 把lxml元素对象包装成SessionElement对象并按需要返回一个或全部
if index is None:
return [SessionElement(e, page) if isinstance(e, HtmlElement) else e for e in eles if e != '\n']
r = SessionElementsList(page=page)
for e in eles:
if e != '\n':
r.append(SessionElement(e, page) if isinstance(e, HtmlElement) else e)
return r
else:
eles_count = len(eles)
if eles_count == 0 or abs(index) > eles_count:
return NoneElement(page)
return NoneElement(page, method=method, args={'locator': loc, 'index': index})
if index < 0:
index = eles_count + index + 1
@ -405,7 +414,7 @@ def make_session_ele(html_or_ele, loc=None, index=1):
elif isinstance(ele, str):
return ele
else:
return NoneElement(page)
return NoneElement(page, method=method, args={'locator': loc, 'index': index})
except Exception as e:
if 'Invalid expression' in str(e):

View File

@ -11,6 +11,7 @@ from lxml.html import HtmlElement
from .._base.base import DrissionElement, BaseElement
from .._elements.chromium_element import ChromiumElement
from .._functions.elements import SessionElementsList
from .._pages.chromium_base import ChromiumBase
from .._pages.chromium_frame import ChromiumFrame
from .._pages.session_page import SessionPage
@ -35,8 +36,6 @@ class SessionElement(DrissionElement):
def __eq__(self, other: SessionElement) -> bool: ...
def __getattr__(self, item: str) -> str: ...
@property
def tag(self) -> str: ...
@ -92,27 +91,27 @@ class SessionElement(DrissionElement):
def children(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[SessionElement, str]]: ...
ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ...
def prevs(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[SessionElement, str]]: ...
ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ...
def nexts(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[SessionElement, str]]: ...
ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ...
def befores(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[SessionElement, str]]: ...
ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ...
def afters(self,
locator: Union[Tuple[str, str], str] = '',
timeout: float = None,
ele_only: bool = True) -> List[Union[SessionElement, str]]: ...
ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ...
def attr(self, name: str) -> Optional[str]: ...
@ -123,20 +122,20 @@ class SessionElement(DrissionElement):
def eles(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> List[SessionElement]: ...
timeout: float = None) -> SessionElementsList: ...
def s_ele(self,
locator: Union[Tuple[str, str], str] = None,
index: int = 1) -> SessionElement: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> List[SessionElement]: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ...
def _find_elements(self,
locator: Union[Tuple[str, str], str],
timeout: float = None,
index: Optional[int] = 1,
relative: bool = False,
raise_err: bool = None) -> Union[SessionElement, List[SessionElement]]: ...
raise_err: bool = None) -> Union[SessionElement, SessionElementsList]: ...
def _get_ele_path(self, mode: str) -> str: ...
@ -144,4 +143,5 @@ class SessionElement(DrissionElement):
def make_session_ele(html_or_ele: Union[str, SessionElement, SessionPage, ChromiumElement, BaseElement, ChromiumFrame,
ChromiumBase],
loc: Union[str, Tuple[str, str]] = None,
index: Optional[int] = 1) -> Union[SessionElement, List[SessionElement]]: ...
index: Optional[int] = 1,
method: Optional[str] = None) -> Union[SessionElement, SessionElementsList]: ...

View File

@ -8,12 +8,11 @@
from json import load, dump, JSONDecodeError
from os import environ
from pathlib import Path
from platform import system
from subprocess import Popen, DEVNULL
from tempfile import gettempdir
from time import perf_counter, sleep
from requests import get as requests_get
from requests import Session
from .tools import port_is_using
from .._configs.options_manage import OptionsManager
@ -200,16 +199,21 @@ def test_connect(ip, port, timeout=30):
:return: None
"""
end_time = perf_counter() + timeout
s = Session()
s.trust_env = False
while perf_counter() < end_time:
try:
tabs = requests_get(f'http://{ip}:{port}/json', timeout=10, headers={'Connection': 'close'},
proxies={'http': None, 'https': None}).json()
for tab in tabs:
r = s.get(f'http://{ip}:{port}/json', timeout=10, headers={'Connection': 'close'})
for tab in r.json():
if tab['type'] in ('page', 'webview'):
r.close()
s.close()
return
r.close()
except Exception:
sleep(.2)
s.close()
raise BrowserConnectError(f'\n{ip}:{port}浏览器无法链接。\n请确认:\n1、该端口为浏览器\n'
f'2、已添加\'--remote-debugging-port={port}\'启动项\n'
f'3、用户文件夹没有和已打开的浏览器冲突\n'

View File

@ -0,0 +1,507 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
"""
from time import perf_counter
from .._elements.none_element import NoneElement
class SessionElementsList(list):
def __init__(self, page=None, *args):
super().__init__(*args)
self._page = page
@property
def get(self):
return Getter(self)
@property
def filter(self):
return SessionFilter(self)
@property
def filter_one(self):
return SessionFilterOne(self)
class ChromiumElementsList(SessionElementsList):
@property
def filter(self):
return ChromiumFilter(self)
@property
def filter_one(self):
return ChromiumFilterOne(self)
def search(self, displayed=None, checked=None, selected=None, enabled=None, clickable=None,
have_rect=None, have_text=None):
"""或关系筛选元素
:param displayed: 是否显示boolNone为忽略该项
:param checked: 是否被选中boolNone为忽略该项
:param selected: 是否被选择boolNone为忽略该项
:param enabled: 是否可用boolNone为忽略该项
:param clickable: 是否可点击boolNone为忽略该项
:param have_rect: 是否拥有大小和位置boolNone为忽略该项
:param have_text: 是否含有文本boolNone为忽略该项
:return: 筛选结果
"""
return _search(self, displayed=displayed, checked=checked, selected=selected, enabled=enabled,
clickable=clickable, have_rect=have_rect, have_text=have_text)
def search_one(self, index=1, displayed=None, checked=None, selected=None, enabled=None, clickable=None,
have_rect=None, have_text=None):
"""或关系筛选元素,获取一个结果
:param index: 元素序号从1开始
:param displayed: 是否显示boolNone为忽略该项
:param checked: 是否被选中boolNone为忽略该项
:param selected: 是否被选择boolNone为忽略该项
:param enabled: 是否可用boolNone为忽略该项
:param clickable: 是否可点击boolNone为忽略该项
:param have_rect: 是否拥有大小和位置boolNone为忽略该项
:param have_text: 是否含有文本boolNone为忽略该项
:return: 筛选结果
"""
return _search_one(self, index=index, displayed=displayed, checked=checked, selected=selected,
enabled=enabled, clickable=clickable, have_rect=have_rect, have_text=have_text)
class SessionFilterOne(object):
def __init__(self, _list):
self._list = _list
self._index = 1
def __call__(self, index=1):
"""返回结果中第几个元素
:param index: 元素序号从1开始
:return: 对象自身
"""
self._index = index
return self
def attr(self, name, value, equal=True):
"""以是否拥有某个attribute值为条件筛选元素
:param name: 属性名称
:param value: 属性值
:param equal: True表示匹配name值为value值的元素False表示匹配name值不为value值的
:return: 筛选结果
"""
return self._get_attr(name, value, 'attr', equal=equal)
def text(self, text, fuzzy=True, contain=True):
"""以是否含有指定文本为条件筛选元素
:param text: 用于匹配的文本
:param fuzzy: 是否模糊匹配
:param contain: 是否包含该字符串False表示不包含
:return: 筛选结果
"""
num = 0
if contain:
for i in self._list:
t = i if isinstance(i, str) else i.raw_text
if (fuzzy and text in t) or (not fuzzy and text == t):
num += 1
if self._index == num:
return i
else:
for i in self._list:
t = i if isinstance(i, str) else i.raw_text
if (fuzzy and text not in t) or (not fuzzy and text != t):
num += 1
if self._index == num:
return i
return NoneElement(self._list._page, 'text()',
args={'text': text, 'fuzzy': fuzzy, 'contain': contain, 'index': self._index})
def _get_attr(self, name, value, method, equal=True):
"""返回通过某个方法可获得某个值的元素
:param name: 属性名称
:param value: 属性值
:param method: 方法名称
:return: 筛选结果
"""
num = 0
if equal:
for i in self._list:
if not isinstance(i, str) and getattr(i, method)(name) == value:
num += 1
if self._index == num:
return i
else:
for i in self._list:
if not isinstance(i, str) and getattr(i, method)(name) != value:
num += 1
if self._index == num:
return i
return NoneElement(self._list._page, f'{method}()',
args={'name': name, 'value': value, 'equal': equal, 'index': self._index})
class SessionFilter(SessionFilterOne):
def __iter__(self):
return iter(self._list)
def __next__(self):
return next(self._list)
def __len__(self):
return len(self._list)
def __getitem__(self, item):
return self._list[item]
@property
def get(self):
"""返回用于获取元素属性的对象"""
return self._list.get
def text(self, text, fuzzy=True, contain=True):
"""以是否含有指定文本为条件筛选元素
:param text: 用于匹配的文本
:param fuzzy: 是否模糊匹配
:param contain: 是否包含该字符串False表示不包含
:return: 筛选结果
"""
self._list = _text_all(self._list, SessionElementsList(page=self._list._page),
text=text, fuzzy=fuzzy, contain=contain)
def _get_attr(self, name, value, method, equal=True):
"""返回通过某个方法可获得某个值的元素
:param name: 属性名称
:param value: 属性值
:param method: 方法名称
:return: 筛选结果
"""
self._list = _get_attr_all(self._list, SessionElementsList(page=self._list._page),
name=name, value=value, method=method, equal=equal)
return self
class ChromiumFilterOne(SessionFilterOne):
def displayed(self, equal=True):
"""以是否显示为条件筛选元素
:param equal: 是否匹配显示的元素False匹配不显示的
:return: 筛选结果
"""
return self._any_state('is_displayed', equal=equal)
def checked(self, equal=True):
"""以是否被选中为条件筛选元素
:param equal: 是否匹配被选中的元素False匹配不被选中的
:return: 筛选结果
"""
return self._any_state('is_checked', equal=equal)
def selected(self, equal=True):
"""以是否被选择为条件筛选元素,用于<select>元素项目
:param equal: 是否匹配被选择的元素False匹配不被选择的
:return: 筛选结果
"""
return self._any_state('is_selected', equal=equal)
def enabled(self, equal=True):
"""以是否可用为条件筛选元素
:param equal: 是否匹配可用的元素False表示匹配disabled状态的
:return: 筛选结果
"""
return self._any_state('is_enabled', equal=equal)
def clickable(self, equal=True):
"""以是否可点击为条件筛选元素
:param equal: 是否匹配可点击的元素False表示匹配不是可点击的
:return: 筛选结果
"""
return self._any_state('is_clickable', equal=equal)
def have_rect(self, equal=True):
"""以是否有大小为条件筛选元素
:param equal: 是否匹配有大小的元素False表示匹配没有大小的
:return: 筛选结果
"""
return self._any_state('has_rect', equal=equal)
def style(self, name, value, equal=True):
"""以是否拥有某个style值为条件筛选元素
:param name: 属性名称
:param value: 属性值
:param equal: True表示匹配name值为value值的元素False表示匹配name值不为value值的
:return: 筛选结果
"""
return self._get_attr(name, value, 'style', equal=equal)
def property(self, name, value, equal=True):
"""以是否拥有某个property值为条件筛选元素
:param name: 属性名称
:param value: 属性值
:param equal: True表示匹配name值为value值的元素False表示匹配name值不为value值的
:return: 筛选结果
"""
return self._get_attr(name, value, 'property', equal=equal)
def _any_state(self, name, equal=True):
"""
:param name: 状态名称
:param equal: 是否是指定状态False表示否定状态
:return: 选中的元素
"""
num = 0
if equal:
for i in self._list:
if not isinstance(i, str) and getattr(i.states, name):
num += 1
if self._index == num:
return i
else:
for i in self._list:
if not isinstance(i, str) and not getattr(i.states, name):
num += 1
if self._index == num:
return i
return NoneElement(self._list._page, f'{name}()', args={'equal': equal, 'index': self._index})
class ChromiumFilter(ChromiumFilterOne):
def __iter__(self):
return iter(self._list)
def __next__(self):
return next(self._list)
def __len__(self):
return len(self._list)
def __getitem__(self, item):
return self._list[item]
@property
def get(self):
"""返回用于获取元素属性的对象"""
return self._list.get
def search_one(self, index=1, displayed=None, checked=None, selected=None, enabled=None, clickable=None,
have_rect=None, have_text=None):
"""或关系筛选元素,获取一个结果
:param index: 元素序号从1开始
:param displayed: 是否显示boolNone为忽略该项
:param checked: 是否被选中boolNone为忽略该项
:param selected: 是否被选择boolNone为忽略该项
:param enabled: 是否可用boolNone为忽略该项
:param clickable: 是否可点击boolNone为忽略该项
:param have_rect: 是否拥有大小和位置boolNone为忽略该项
:param have_text: 是否含有文本boolNone为忽略该项
:return: 筛选结果
"""
return _search_one(self._list, index=index, displayed=displayed, checked=checked, selected=selected,
enabled=enabled, clickable=clickable, have_rect=have_rect, have_text=have_text)
def search(self, displayed=None, checked=None, selected=None, enabled=None, clickable=None,
have_rect=None, have_text=None):
"""或关系筛选元素
:param displayed: 是否显示boolNone为忽略该项
:param checked: 是否被选中boolNone为忽略该项
:param selected: 是否被选择boolNone为忽略该项
:param enabled: 是否可用boolNone为忽略该项
:param clickable: 是否可点击boolNone为忽略该项
:param have_rect: 是否拥有大小和位置boolNone为忽略该项
:param have_text: 是否含有文本boolNone为忽略该项
:return: 筛选结果
"""
return _search(self._list, displayed=displayed, checked=checked, selected=selected, enabled=enabled,
clickable=clickable, have_rect=have_rect, have_text=have_text)
def text(self, text, fuzzy=True, contain=True):
"""以是否含有指定文本为条件筛选元素
:param text: 用于匹配的文本
:param fuzzy: 是否模糊匹配
:param contain: 是否包含该字符串False表示不包含
:return: 筛选结果
"""
self._list = _text_all(self._list, ChromiumElementsList(page=self._list._page),
text=text, fuzzy=fuzzy, contain=contain)
return self
def _get_attr(self, name, value, method, equal=True):
"""返回通过某个方法可获得某个值的元素
:param name: 属性名称
:param value: 属性值
:param method: 方法名称
:return: 筛选结果
"""
self._list = _get_attr_all(self._list, ChromiumElementsList(page=self._list._page),
name=name, value=value, method=method, equal=equal)
return self
def _any_state(self, name, equal=True):
"""
:param name: 状态名称
:param equal: 是否是指定状态False表示否定状态
:return: 选中的列表
"""
r = ChromiumElementsList(page=self._list._page)
if equal:
for i in self._list:
if not isinstance(i, str) and getattr(i.states, name):
r.append(i)
else:
for i in self._list:
if not isinstance(i, str) and not getattr(i.states, name):
r.append(i)
self._list = r
return self
class Getter(object):
def __init__(self, _list):
self._list = _list
def links(self):
"""返回所有元素的link属性组成的列表"""
return [e.link for e in self._list if not isinstance(e, str)]
def texts(self):
"""返回所有元素的text属性组成的列表"""
return [e if isinstance(e, str) else e.text for e in self._list]
def attrs(self, name):
"""返回所有元素指定的attr属性组成的列表
:param name: 属性名称
:return: 属性文本组成的列表
"""
return [e.attr(name) for e in self._list if not isinstance(e, str)]
def get_eles(locators, owner, any_one=False, first_ele=True, timeout=10):
"""传入多个定位符获取多个ele
:param locators: 定位符组成的列表
:param owner: 页面或元素对象
:param any_one: 是否找到任何一个即返回
:param first_ele: 每个定位符是否只获取第一个元素
:param timeout: 超时时间
:return: 多个定位符组成的dict
"""
res = {loc: False for loc in locators}
end_time = perf_counter() + timeout
while perf_counter() <= end_time:
for loc in locators:
if res[loc] is not False:
continue
ele = owner.ele(loc, timeout=0) if first_ele else owner.eles(loc, timeout=0)
if ele:
res[loc] = ele
if any_one:
return res
if False not in res.values():
break
return res
def _get_attr_all(src_list, aim_list, name, value, method, equal=True):
if equal:
for i in src_list:
if not isinstance(i, str) and getattr(i, method)(name) == value:
aim_list.append(i)
else:
for i in src_list:
if not isinstance(i, str) and getattr(i, method)(name) != value:
aim_list.append(i)
return aim_list
def _text_all(src_list, aim_list, text, fuzzy=True, contain=True):
"""以是否含有指定文本为条件筛选元素
:param text: 用于匹配的文本
:param fuzzy: 是否模糊匹配
:param contain: 是否包含该字符串False表示不包含
:return: 筛选结果
"""
if contain:
for i in src_list:
t = i if isinstance(i, str) else i.raw_text
if (fuzzy and text in t) or (not fuzzy and text == t):
aim_list.append(i)
else:
for i in src_list:
t = i if isinstance(i, str) else i.raw_text
if (fuzzy and text not in t) or (not fuzzy and text != t):
aim_list.append(i)
return aim_list
def _search(_list, displayed=None, checked=None, selected=None, enabled=None, clickable=None,
have_rect=None, have_text=None):
"""或关系筛选元素
:param displayed: 是否显示boolNone为忽略该项
:param checked: 是否被选中boolNone为忽略该项
:param selected: 是否被选择boolNone为忽略该项
:param enabled: 是否可用boolNone为忽略该项
:param clickable: 是否可点击boolNone为忽略该项
:param have_rect: 是否拥有大小和位置boolNone为忽略该项
:param have_text: 是否含有文本boolNone为忽略该项
:return: 筛选结果
"""
r = ChromiumElementsList(page=_list._page)
for i in _list:
if not isinstance(i, str) and (
(displayed is not None and (displayed is True and i.states.is_displayed)
or (displayed is False and not i.states.is_displayed))
or (checked is not None and (checked is True and i.states.is_checked)
or (checked is False and not i.states.is_checked))
or (selected is not None and (selected is True and i.states.is_selected)
or (selected is False and not i.states.is_selected))
or (enabled is not None and (enabled is True and i.states.is_enabled)
or (enabled is False and not i.states.is_enabled))
or (clickable is not None and (clickable is True and i.states.is_clickable)
or (clickable is False and not i.states.is_clickable))
or (have_rect is not None and (have_rect is True and i.states.has_rect)
or (have_rect is False and not i.states.has_rect))
or (have_text is not None and (have_text is True and i.raw_text)
or (have_text is False and not i.raw_text))):
r.append(i)
return ChromiumFilter(r)
def _search_one(_list, index=1, displayed=None, checked=None, selected=None, enabled=None, clickable=None,
have_rect=None, have_text=None):
"""或关系筛选元素,获取一个结果
:param index: 元素序号从1开始
:param displayed: 是否显示boolNone为忽略该项
:param checked: 是否被选中boolNone为忽略该项
:param selected: 是否被选择boolNone为忽略该项
:param enabled: 是否可用boolNone为忽略该项
:param clickable: 是否可点击boolNone为忽略该项
:param have_rect: 是否拥有大小和位置boolNone为忽略该项
:param have_text: 是否含有文本boolNone为忽略该项
:return: 筛选结果
"""
num = 0
for i in _list:
if not isinstance(i, str) and (
(displayed is not None and (displayed is True and i.states.is_displayed)
or (displayed is False and not i.states.is_displayed))
or (checked is not None and (checked is True and i.states.is_checked)
or (checked is False and not i.states.is_checked))
or (selected is not None and (selected is True and i.states.is_selected)
or (selected is False and not i.states.is_selected))
or (enabled is not None and (enabled is True and i.states.is_enabled)
or (enabled is False and not i.states.is_enabled))
or (clickable is not None and (clickable is True and i.states.is_clickable)
or (clickable is False and not i.states.is_clickable))
or (have_rect is not None and (have_rect is True and i.states.has_rect)
or (have_rect is False and not i.states.has_rect))
or (have_text is not None and (have_text is True and i.raw_text)
or (have_text is False and not i.raw_text))):
num += 1
if num == index:
return i
return NoneElement(_list._page, method='filter()', args={'displayed': displayed,
'checked': checked, 'selected': selected,
'enabled': enabled, 'clickable': clickable,
'have_rect': have_rect, 'have_text': have_text})

View File

@ -0,0 +1,220 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
"""
from typing import Union, List, Optional, Iterable
from .._base.base import BaseParser
from .._elements.chromium_element import ChromiumElement
from .._elements.session_element import SessionElement
def get_eles(locators: Union[List[str], tuple],
owner: BaseParser,
any_one: bool = False,
first_ele: bool = True,
timeout: float = 10) -> dict: ...
class SessionElementsList(list):
_page = ...
def __init__(self, page=None, *args): ...
@property
def get(self) -> Getter: ...
@property
def filter(self) -> SessionFilter: ...
@property
def filter_one(self) -> SessionFilterOne: ...
def __next__(self) -> SessionElement: ...
class ChromiumElementsList(SessionElementsList):
@property
def filter(self) -> ChromiumFilter: ...
@property
def filter_one(self) -> ChromiumFilterOne: ...
def search(self,
displayed: Optional[bool] = None,
checked: Optional[bool] = None,
selected: Optional[bool] = None,
enabled: Optional[bool] = None,
clickable: Optional[bool] = None,
have_rect: Optional[bool] = None,
have_text: Optional[bool] = None) -> ChromiumFilter: ...
def search_one(self,
index: int = 1,
displayed: Optional[bool] = None,
checked: Optional[bool] = None,
selected: Optional[bool] = None,
enabled: Optional[bool] = None,
clickable: Optional[bool] = None,
have_rect: Optional[bool] = None,
have_text: Optional[bool] = None) -> ChromiumElement: ...
def __next__(self) -> ChromiumElement: ...
class SessionFilterOne(object):
_list: SessionElementsList = ...
_index: int = ...
def __init__(self, _list: SessionElementsList, index: int = 1): ...
def __call__(self, index: int = 1) -> SessionFilterOne: ...
def attr(self, name: str, value: str, equal: bool = True) -> SessionElement: ...
def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionElement: ...
def _get_attr(self,
name: str,
value: str,
method: str,
equal: bool = True) -> SessionElement: ...
class SessionFilter(SessionFilterOne):
def __iter__(self) -> Iterable[SessionElement]: ...
def __next__(self) -> SessionElement: ...
def __len__(self) -> int: ...
def __getitem__(self, item: int) -> SessionElement: ...
@property
def get(self) -> Getter: ...
def attr(self, name: str, value: str, equal: bool = True) -> SessionFilter: ...
def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionFilter: ...
def _get_attr(self,
name: str,
value: str,
method: str,
equal: bool = True) -> SessionFilter: ...
class ChromiumFilterOne(SessionFilterOne):
_list: ChromiumElementsList = ...
def __init__(self, _list: ChromiumElementsList): ...
def __call__(self, index: int = 1) -> ChromiumFilterOne: ...
def displayed(self, equal: bool = True) -> ChromiumElement: ...
def checked(self, equal: bool = True) -> ChromiumElement: ...
def selected(self, equal: bool = True) -> ChromiumElement: ...
def enabled(self, equal: bool = True) -> ChromiumElement: ...
def clickable(self, equal: bool = True) -> ChromiumElement: ...
def have_rect(self, equal: bool = True) -> ChromiumElement: ...
def style(self, name: str, value: str, equal: bool = True) -> ChromiumElement: ...
def property(self,
name: str,
value: str, equal: bool = True) -> ChromiumElement: ...
def attr(self, name: str, value: str, equal: bool = True) -> ChromiumElement: ...
def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> ChromiumElement: ...
def _get_attr(self,
name: str,
value: str,
method: str, equal: bool = True) -> ChromiumElement: ...
def _any_state(self, name: str, equal: bool = True) -> ChromiumElement: ...
class ChromiumFilter(ChromiumFilterOne):
def __iter__(self) -> Iterable[ChromiumElement]: ...
def __next__(self) -> ChromiumElement: ...
def __len__(self) -> int: ...
def __getitem__(self, item: int) -> ChromiumElement: ...
@property
def get(self) -> Getter: ...
def displayed(self, equal: bool = True) -> ChromiumFilter: ...
def checked(self, equal: bool = True) -> ChromiumFilter: ...
def selected(self, equal: bool = True) -> ChromiumFilter: ...
def enabled(self, equal: bool = True) -> ChromiumFilter: ...
def clickable(self, equal: bool = True) -> ChromiumFilter: ...
def have_rect(self, equal: bool = True) -> ChromiumFilter: ...
def style(self, name: str, value: str, equal: bool = True) -> ChromiumFilter: ...
def property(self,
name: str,
value: str, equal: bool = True) -> ChromiumFilter: ...
def attr(self, name: str, value: str, equal: bool = True) -> ChromiumFilter: ...
def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> ChromiumFilter: ...
def search(self,
displayed: Optional[bool] = None,
checked: Optional[bool] = None,
selected: Optional[bool] = None,
enabled: Optional[bool] = None,
clickable: Optional[bool] = None,
have_rect: Optional[bool] = None,
have_text: Optional[bool] = None) -> ChromiumFilter: ...
def search_one(self,
index: int = 1,
displayed: Optional[bool] = None,
checked: Optional[bool] = None,
selected: Optional[bool] = None,
enabled: Optional[bool] = None,
clickable: Optional[bool] = None,
have_rect: Optional[bool] = None,
have_text: Optional[bool] = None) -> ChromiumElement: ...
def _get_attr(self,
name: str,
value: str,
method: str, equal: bool = True) -> ChromiumFilter: ...
def _any_state(self, name: str, equal: bool = True) -> ChromiumFilter: ...
class Getter(object):
_list: SessionElementsList = ...
def __init__(self, _list: SessionElementsList): ...
def links(self) -> List[str]: ...
def texts(self) -> List[str]: ...
def attrs(self, name: str) -> List[str]: ...

View File

@ -9,6 +9,80 @@ from re import split
from .by import By
def locator_to_tuple(loc):
"""解析定位字符串生成dict格式数据
:param loc: 待处理的字符串
:return: 格式 {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]}
"""
loc = _preprocess(loc)
# 多属性查找
if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):
args = _get_args(loc)
# 单属性查找
elif loc.startswith('@') and loc != '@':
arg = _get_arg(loc[1:])
arg.append(False)
args = {'and': True, 'args': [arg]}
# 根据tag name查找
elif loc.startswith(('tag:', 'tag=', 'tag^', 'tag$')) and loc not in ('tag:', 'tag=', 'tag^', 'tag$'):
at_ind = loc.find('@')
if at_ind == -1:
args = {'and': True, 'args': [['tag()', '=', loc[4:].lower(), False]]}
else:
args_str = loc[at_ind:]
if args_str.startswith(('@@', '@|', '@!')):
args = _get_args(args_str)
args['args'].append([f'tag()', '=', loc[4:at_ind].lower(), False])
else: # t:div@aa=bb的格式
arg = _get_arg(loc[at_ind + 1:])
arg.append(False)
args = {'and': True, 'args': [['tag()', '=', loc[4:at_ind].lower(), False], arg]}
# 根据文本查找
elif loc.startswith(('text=', 'text:', 'text^', 'text$')):
args = {'and': True, 'args': [['text()', loc[4], loc[5:], False]]}
# 根据文本模糊查找
else:
args = {'and': True, 'args': [['text()', '=', loc, False]]}
return args
def _get_args(text: str = '') -> dict:
"""解析定位参数字符串生成dict格式数据
:param text: 待处理的字符串
:return: 格式 {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]}
"""
arg_list = []
args = split(r'(@!|@@|@\|)', text)[1:]
if '@@' in args and '@|' in args:
raise ValueError('@@和@|不能同时出现在一个定位语句中。')
_and = '@|' not in args
for k in range(0, len(args) - 1, 2):
arg = _get_arg(args[k + 1])
if arg:
arg.append(True if args[k] == '@!' else False) # 是否去除某个属性
arg_list.append(arg)
return {'and': _and, 'args': arg_list}
def _get_arg(text) -> list:
"""解析arg=abc格式字符串生成格式['属性名称', '匹配方式', '属性值', 是否否定]不是式子的返回None"""
r = split(r'([:=$^])', text, maxsplit=1)
if not r[0]:
return [None, None, None, None]
# !=时只有属性名没有属性内容,查询是否存在该属性
name = r[0] if r[0] != 'tx()' else 'text()'
name = name if name != 't()' else 'teg()'
return [name, None, None] if len(r) != 3 else [name, r[1], r[2]]
def is_loc(text):
"""返回text是否定位符"""
return text.startswith(('.', '#', '@', 't:', 't=', 'tag:', 'tag=', 'tx:', 'tx=', 'tx^', 'tx$', 'text:', 'text=',
@ -49,26 +123,8 @@ def str_to_xpath_loc(loc):
:return: 匹配符元组
"""
loc_by = 'xpath'
loc = _preprocess(loc)
if loc.startswith('.'):
if loc.startswith(('.=', '.:', '.^', '.$')):
loc = loc.replace('.', '@class', 1)
else:
loc = loc.replace('.', '@class=', 1)
elif loc.startswith('#'):
if loc.startswith(('#=', '#:', '#^', '#$')):
loc = loc.replace('#', '@id', 1)
else:
loc = loc.replace('#', '@id=', 1)
elif loc.startswith(('t:', 't=')):
loc = f'tag:{loc[2:]}'
elif loc.startswith(('tx:', 'tx=', 'tx^', 'tx$')):
loc = f'text{loc[2:]}'
# ------------------------------------------------------------------
# 多属性查找
if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):
loc_str = _make_multi_xpath_str('*', loc)[1]
@ -78,7 +134,7 @@ def str_to_xpath_loc(loc):
loc_str = _make_single_xpath_str('*', loc)[1]
# 根据tag name查找
elif loc.startswith(('tag:', 'tag=')) and loc not in ('tag:', 'tag='):
elif loc.startswith(('tag:', 'tag=', 'tag^', 'tag$')) and loc not in ('tag:', 'tag=', 'tag^', 'tag$'):
at_ind = loc.find('@')
if at_ind == -1:
loc_str = f'//*[name()="{loc[4:]}"]'
@ -101,16 +157,11 @@ def str_to_xpath_loc(loc):
# 用xpath查找
elif loc.startswith(('xpath:', 'xpath=')) and loc not in ('xpath:', 'xpath='):
loc_str = loc[6:]
elif loc.startswith(('x:', 'x=')) and loc not in ('x:', 'x='):
loc_str = loc[2:]
# 用css selector查找
elif loc.startswith(('css:', 'css=')) and loc not in ('css:', 'css='):
loc_by = 'css selector'
loc_str = loc[4:]
elif loc.startswith(('c:', 'c=')) and loc not in ('c:', 'c='):
loc_by = 'css selector'
loc_str = loc[2:]
# 根据文本模糊查找
elif loc:
@ -127,26 +178,8 @@ def str_to_css_loc(loc):
:return: 匹配符元组
"""
loc_by = 'css selector'
loc = _preprocess(loc)
if loc.startswith('.'):
if loc.startswith(('.=', '.:', '.^', '.$')):
loc = loc.replace('.', '@class', 1)
else:
loc = loc.replace('.', '@class=', 1)
elif loc.startswith('#'):
if loc.startswith(('#=', '#:', '#^', '#$')):
loc = loc.replace('#', '@id', 1)
else:
loc = loc.replace('#', '@id=', 1)
elif loc.startswith(('t:', 't=')):
loc = f'tag:{loc[2:]}'
elif loc.startswith(('tx:', 'tx=', 'tx^', 'tx$')):
loc = f'text{loc[2:]}'
# ------------------------------------------------------------------
# 多属性查找
if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):
loc_str = _make_multi_css_str('*', loc)[1]
@ -156,7 +189,7 @@ def str_to_css_loc(loc):
loc_by, loc_str = _make_single_css_str('*', loc)
# 根据tag name查找
elif loc.startswith(('tag:', 'tag=')) and loc not in ('tag:', 'tag='):
elif loc.startswith(('tag:', 'tag=', 'tag^', 'tag$')) and loc not in ('tag:', 'tag=', 'tag^', 'tag$'):
at_ind = loc.find('@')
if at_ind == -1:
loc_str = loc[4:]
@ -166,14 +199,12 @@ def str_to_css_loc(loc):
loc_by, loc_str = _make_single_css_str(loc[4:at_ind], loc[at_ind:])
# 根据文本查找
elif loc.startswith(('text=', 'text:', 'text^', 'text$', 'xpath=', 'xpath:', 'x:', 'x=')):
elif loc.startswith(('text=', 'text:', 'text^', 'text$', 'xpath=', 'xpath:')):
loc_by, loc_str = str_to_xpath_loc(loc)
# 用css selector查找
elif loc.startswith(('css:', 'css=')) and loc not in ('css:', 'css='):
loc_str = loc[4:]
elif loc.startswith(('c:', 'c=')) and loc not in ('c:', 'c='):
loc_str = loc[2:]
# 根据文本模糊查找
elif loc:
@ -202,6 +233,9 @@ def _make_single_xpath_str(tag: str, text: str) -> tuple:
len_r = len(r)
len_r0 = len(r[0])
if len_r == 3 and len_r0 > 1:
if r[0] in ('@tag()', '@t()'):
arg_str = f'name()="{r[2].lower()}"'
else:
symbol = r[1]
if symbol == '=': # 精确查找
arg = '.' if r[0] in ('@text()', '@tx()') else r[0]
@ -216,12 +250,12 @@ def _make_single_xpath_str(tag: str, text: str) -> tuple:
elif symbol == '$': # 匹配结尾
if r[0] in ('@text()', '@tx()'):
txt_str = f'/text()[substring(., string-length(.) - string-length({_make_search_str(r[2])}) +1) ' \
f'= {_make_search_str(r[2])}]/..'
txt_str = (f'/text()[substring(., string-length(.) - string-length({_make_search_str(r[2])}) '
f'+1) = {_make_search_str(r[2])}]/..')
arg_str = ''
else:
arg_str = f'substring({r[0]}, string-length({r[0]}) - string-length({_make_search_str(r[2])}) +1)' \
f' = {_make_search_str(r[2])}'
arg_str = (f'substring({r[0]}, string-length({r[0]}) - string-length({_make_search_str(r[2])}) '
f'+1) = {_make_search_str(r[2])}')
elif symbol == ':': # 模糊查找
if r[0] in ('@text()', '@tx()'):
@ -234,6 +268,9 @@ def _make_single_xpath_str(tag: str, text: str) -> tuple:
raise ValueError(f'符号不正确:{symbol}')
elif len_r != 3 and len_r0 > 1:
if r[0] in ('@tag()', '@t()'):
arg_str = ''
else:
arg_str = 'normalize-space(text())' if r[0] in ('@text()', '@tx()') else f'{r[0]}'
if arg_str:
@ -252,10 +289,9 @@ def _make_multi_xpath_str(tag: str, text: str) -> tuple:
args = split(r'(@!|@@|@\|)', text)[1:]
if '@@' in args and '@|' in args:
raise ValueError('@@和@|不能同时出现在一个定位语句中。')
elif '@@' in args:
_and = True
else: # @|
_and = False
_and = '@|' not in args
tags = [] if tag == '*' else [f'name()="{tag}"']
tags_connect = ' or '
for k in range(0, len(args) - 1, 2):
r = split(r'([:=$^])', args[k + 1], maxsplit=1)
@ -268,23 +304,39 @@ def _make_multi_xpath_str(tag: str, text: str) -> tuple:
else:
ignore = True if args[k] == '@!' else False # 是否去除某个属性
if len_r != 3: # 只有属性名没有属性内容,查询是否存在该属性
if r[0] in ('tag()', 't()'):
continue
arg_str = 'normalize-space(text())' if r[0] in ('text()', 'tx()') else f'@{r[0]}'
elif len_r == 3: # 属性名和内容都有
arg = '.' if r[0] in ('text()', 'tx()') else f'@{r[0]}'
if r[0] in ('tag()', 't()'):
if ignore:
tags.append(f'not(name()="{r[2]}")')
tags_connect = ' and '
else:
tags.append(f'name()="{r[2]}"')
continue
symbol = r[1]
if r[0] in ('text()', 'tx()'):
arg = '.'
txt = r[2]
else:
arg = f'@{r[0]}'
txt = r[2]
if symbol == '=':
arg_str = f'{arg}={_make_search_str(r[2])}'
arg_str = f'{arg}={_make_search_str(txt)}'
elif symbol == ':':
arg_str = f'contains({arg},{_make_search_str(r[2])})'
arg_str = f'contains({arg},{_make_search_str(txt)})'
elif symbol == '^':
arg_str = f'starts-with({arg},{_make_search_str(r[2])})'
arg_str = f'starts-with({arg},{_make_search_str(txt)})'
elif symbol == '$':
arg_str = f'substring({arg}, string-length({arg}) - string-length({_make_search_str(r[2])}) +1) ' \
f'= {_make_search_str(r[2])}'
arg_str = f'substring({arg}, string-length({arg}) - string-length({_make_search_str(txt)}) +1) ' \
f'= {_make_search_str(txt)}'
else:
raise ValueError(f'符号不正确:{symbol}')
@ -296,9 +348,9 @@ def _make_multi_xpath_str(tag: str, text: str) -> tuple:
arg_list.append(arg_str)
arg_str = ' and '.join(arg_list) if _and else ' or '.join(arg_list)
if tag != '*':
if tags:
condition = f' and ({arg_str})' if arg_str else ''
arg_str = f'name()="{tag}"{condition}'
arg_str = f'({tags_connect.join(tags)}){condition}'
return 'xpath', f'//*[{arg_str}]' if arg_str else f'//*'
@ -330,10 +382,7 @@ def _make_multi_css_str(tag: str, text: str) -> tuple:
args = split(r'(@!|@@|@\|)', text)[1:]
if '@@' in args and '@|' in args:
raise ValueError('@@和@|不能同时出现在一个定位语句中。')
elif '@@' in args:
_and = True
else: # @|
_and = False
_and = '@|' not in args
for k in range(0, len(args) - 1, 2):
r = split(r'([:=$^])', args[k + 1], maxsplit=1)
@ -344,9 +393,18 @@ def _make_multi_css_str(tag: str, text: str) -> tuple:
len_r = len(r)
ignore = True if args[k] == '@!' else False # 是否去除某个属性
if len_r != 3: # 只有属性名没有属性内容,查询是否存在该属性
if r[0] in ('tag()', 't()'):
continue
arg_str = f'[{r[0]}]'
elif len_r == 3: # 属性名和内容都有
if r[0] in ('tag()', 't()'):
if tag == '*':
tag = f':not({r[2].lower()})' if ignore else f'{r[2]}'
else:
tag += f',:not({r[2].lower()})' if ignore else f',{r[2]}'
continue
d = {'=': '', '^': '^', '$': '$', ':': '*'}
arg_str = f'[{r[0]}{d[r[1]]}={css_trans(r[2])}]'
@ -372,6 +430,9 @@ def _make_single_css_str(tag: str, text: str) -> tuple:
return _make_single_xpath_str(tag, text)
r = split(r'([:=$^])', text, maxsplit=1)
if r[0] in ('@tag()', '@t()'):
return 'css selector', r[2]
if len(r) == 3:
d = {'=': '', '^': '^', '$': '$', ':': '*'}
arg_str = f'[{r[0][1:]}{d[r[1]]}={css_trans(r[2])}]'
@ -472,3 +533,32 @@ def css_trans(txt):
c = ('!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@',
'[', '\\', ']', '^', '`', ',', '{', '|', '}', '~', ' ')
return ''.join([fr'\{i}' if i in c else i for i in txt])
def _preprocess(loc):
"""对缩写进行处理,替换回完整写法"""
if loc.startswith('.'):
if loc.startswith(('.=', '.:', '.^', '.$')):
loc = loc.replace('.', '@class', 1)
else:
loc = loc.replace('.', '@class=', 1)
elif loc.startswith('#'):
if loc.startswith(('#=', '#:', '#^', '#$')):
loc = loc.replace('#', '@id', 1)
else:
loc = loc.replace('#', '@id=', 1)
elif loc.startswith(('t:', 't=')):
loc = f'tag:{loc[2:]}'
elif loc.startswith(('tx:', 'tx=', 'tx^', 'tx$')):
loc = f'text{loc[2:]}'
elif loc.startswith(('c:', 'c=')):
loc = f'css:{loc[2:]}'
elif loc.startswith(('x:', 'x=')):
loc = f'xpath:{loc[2:]}'
return loc

View File

@ -8,6 +8,9 @@
from typing import Union
def locator_to_tuple(loc: str) -> dict: ...
def is_loc(text: str) -> bool: ...
@ -17,6 +20,9 @@ def get_loc(loc: Union[tuple, str], translate_css: bool = False, css_mode: bool
def str_to_xpath_loc(loc: str) -> tuple: ...
def str_to_css_loc(loc: str) -> tuple: ...
def translate_loc(loc: tuple) -> tuple: ...

View File

@ -10,7 +10,6 @@ from pathlib import Path
from threading import Lock
from typing import Union, Tuple
from ..errors import BaseError
from .._pages.chromium_base import ChromiumBase
@ -46,4 +45,4 @@ def wait_until(function: callable, kwargs: dict = None, timeout: float = 10): ..
def configs_to_here(file_name: Union[Path, str] = None) -> None: ...
def raise_error(result: dict, ignore: BaseError = None) -> None: ...
def raise_error(result: dict, ignore=None) -> None: ...

View File

@ -8,9 +8,12 @@
from datetime import datetime
from html import unescape
from http.cookiejar import Cookie, CookieJar
from re import sub
from os.path import sep
from pathlib import Path
from re import sub, match
from urllib.parse import urlparse, urljoin, urlunparse
from DataRecorder.tools import make_valid_name
from tldextract import extract
@ -138,8 +141,11 @@ def make_absolute_link(link, baseURI=None):
if not link:
return link
link = link.strip()
link = link.strip().replace('\\', '/')
parsed = urlparse(link)._asdict()
if baseURI:
p = urlparse(baseURI)._asdict()
baseURI = f'{p["scheme"]}://{p["netloc"]}'
# 是相对路径与页面url拼接并返回
if not parsed['netloc']:
@ -207,7 +213,16 @@ def cookies_to_tuple(cookies):
elif isinstance(cookies, str):
c_dict = {}
for attr in cookies.strip().rstrip(';, ').split(',' if ',' in cookies else ';'):
cookies = cookies.rstrip('; ')
cookies = cookies.split(';')
# r = match(r'.*?=([^=]+)=', cookies)
# if not r: # 只有一个
# cookies = [cookies.rstrip(',;')]
# else:
# s = match(r'.*([,;]).*', r.group(1)).group(1)
# cookies = cookies.rstrip(s).split(s)
for attr in cookies:
attr_val = attr.strip().split('=', 1)
c_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else True
cookies = _dict_cookies_to_tuple(c_dict)
@ -314,8 +329,7 @@ def set_browser_cookies(page, cookies):
tmp.append(i)
for i in range(len(tmp)):
d = ''.join(tmp[i:])
cookie['domain'] = d
cookie['domain'] = ''.join(tmp[i:])
page.run_cdp_loaded('Network.setCookie', **cookie)
if is_cookie_in_driver(page, cookie):
break
@ -374,9 +388,92 @@ def get_blob(page, url, as_bytes=True):
return result
def tree(ele_or_page):
def save_page(tab, path=None, name=None, as_pdf=False, kwargs=None):
"""把当前页面保存为文件如果path和name参数都为None只返回文本
:param tab: Tab或Page对象
:param path: 保存路径为None且name不为None时保存在当前路径
:param name: 文件名为None且path不为None时用title属性值
:param as_pdf: 为Ture保存为pdf否则为mhtml且忽略kwargs参数
:param kwargs: pdf生成参数
:return: as_pdf为True时返回bytes否则返回文件文本
"""
if name:
if name.endswith('.pdf'):
name = name[:-4]
as_pdf = True
elif name.endswith('.mhtml'):
name = name[:-6]
as_pdf = False
if path:
path = Path(path)
if path.suffix.lower() == '.mhtml':
name = path.stem
path = path.parent
as_pdf = False
elif path.suffix.lower() == '.pdf':
name = path.stem
path = path.parent
as_pdf = True
return get_pdf(tab, path, name, kwargs) if as_pdf else get_mhtml(tab, path, name)
def get_mhtml(page, path=None, name=None):
"""把当前页面保存为mhtml文件如果path和name参数都为None只返回mhtml文本
:param page: 要保存的页面对象
:param path: 保存路径为None且name不为None时保存在当前路径
:param name: 文件名为None且path不为None时用title属性值
:return: mhtml文本
"""
r = page.run_cdp('Page.captureSnapshot')['data']
if path is None and name is None:
return r
path = path or '.'
Path(path).mkdir(parents=True, exist_ok=True)
name = make_valid_name(name or page.title)
with open(f'{path}{sep}{name}.mhtml', 'w', encoding='utf-8') as f:
f.write(r.replace('\r\n', '\n'))
return r
def get_pdf(page, path=None, name=None, kwargs=None):
"""把当前页面保存为pdf文件如果path和name参数都为None只返回字节
:param page: 要保存的页面对象
:param path: 保存路径为None且name不为None时保存在当前路径
:param name: 文件名为None且path不为None时用title属性值
:param kwargs: pdf生成参数
:return: pdf文本
"""
if not kwargs:
kwargs = {}
kwargs['transferMode'] = 'ReturnAsBase64'
if 'printBackground' not in kwargs:
kwargs['printBackground'] = True
try:
r = page.run_cdp('Page.printToPDF', **kwargs)['data']
except:
raise RuntimeError('保存失败,可能浏览器版本不支持。')
from base64 import b64decode
r = b64decode(r)
if path is None and name is None:
return r
path = path or '.'
Path(path).mkdir(parents=True, exist_ok=True)
name = make_valid_name(name or page.title)
with open(f'{path}{sep}{name}.pdf', 'wb') as f:
f.write(r)
return r
def tree(ele_or_page, text=False, show_js=False, show_css=False):
"""把页面或元素对象DOM结构打印出来
:param ele_or_page: 页面或元素对象
:param text: 是否打印文本输入数字可指定打印文本长度上线
:param show_js: 打印文本时是否包含<script>内文本text参数为False时无效
:param show_css: 打印文本时是否包含<style>内文本text参数为False时无效
:return: None
"""
@ -396,13 +493,33 @@ def tree(ele_or_page):
e = list_ele[i]
attrs = ' '.join([f"{k}='{v}'" for k, v in e.attrs.items()])
print(f'{new_body}{tail}<{e.tag} {attrs}>'.replace('\n', ' '))
show_text = f'{new_body}{tail}<{e.tag} {attrs}>'.replace('\n', ' ')
if text:
t = e('x:/text()')
if t:
t = t.replace('\n', ' ')
if (e.tag not in ('script', 'style') or (e.tag == 'script' and show_js)
or (e.tag == 'style' and show_css)):
if text is not True:
t = t[:text]
show_text = f'{show_text} {t}'
print(show_text)
_tree(e, new_last_one, new_body)
ele = ele_or_page.s_ele()
attrs = ' '.join([f"{k}='{v}'" for k, v in ele.attrs.items()])
print(f'<{ele.tag} {attrs}>'.replace('\n', ' '))
show_text = f'<{ele.tag} {attrs}>'.replace('\n', ' ')
if text:
t = ele('x:/text()')
if t:
t = t.replace('\n', ' ')
if (ele.tag not in ('script', 'style') or (ele.tag == 'script' and show_js)
or (ele.tag == 'style' and show_css)):
if text is not True:
t = t[:text]
show_text = f'{show_text} {t}'
print(show_text)
_tree(ele)

View File

@ -6,7 +6,8 @@
@License : BSD 3-Clause.
"""
from http.cookiejar import Cookie
from typing import Union
from pathlib import Path
from typing import Union, Optional
from requests import Session
from requests.cookies import RequestsCookieJar
@ -14,6 +15,8 @@ from requests.cookies import RequestsCookieJar
from .._base.base import DrissionElement, BaseParser
from .._elements.chromium_element import ChromiumElement
from .._pages.chromium_base import ChromiumBase
from .._pages.chromium_page import ChromiumPage
from .._pages.chromium_tab import ChromiumTab
def get_ele_txt(e: DrissionElement) -> str: ...
@ -52,7 +55,28 @@ def is_cookie_in_driver(page: ChromiumBase, cookie: dict) -> bool: ...
def get_blob(page: ChromiumBase, url: str, as_bytes: bool = True) -> bytes: ...
def tree(ele_or_page: BaseParser) -> None: ...
def save_page(tab: Union[ChromiumPage, ChromiumTab],
path: Union[Path, str, None] = None,
name: Optional[str] = None,
as_pdf: bool = False,
kwargs: dict = None) -> Union[bytes, str]: ...
def get_mhtml(page: Union[ChromiumPage, ChromiumTab],
path: Optional[Path] = None,
name: Optional[str] = None) -> Union[bytes, str]: ...
def get_pdf(page: Union[ChromiumPage, ChromiumTab],
path: Optional[Path] = None,
name: Optional[str] = None,
kwargs: dict = None) -> Union[bytes, str]: ...
def tree(ele_or_page: BaseParser,
text: Union[int, bool] = False,
show_js: bool = False,
show_css: bool = False) -> None: ...
def format_headers(txt: str) -> dict: ...

View File

@ -30,7 +30,7 @@ from .._units.scroller import PageScroller
from .._units.setter import ChromiumBaseSetter
from .._units.states import PageStates
from .._units.waiter import BaseWaiter
from ..errors import ContextLostError, CDPError, PageDisconnectedError, ElementNotFoundError, ElementLostError
from ..errors import ContextLostError, CDPError, PageDisconnectedError, ElementLostError
__ERROR__ = 'error'
@ -478,7 +478,7 @@ class ChromiumBase(BasePage):
def cookies(self, as_dict=False, all_domains=False, all_info=False):
"""返回cookies信息
:param as_dict: 为True时以dict格式返回为False时返回list且all_info无效
:param as_dict: 为True时以dict格式返回且all_info无效为False时返回list
:param all_domains: 是否返回所有域的cookies
:param all_info: 是否返回所有信息为False时只返回namevaluedomain
:return: cookies信息
@ -517,14 +517,7 @@ class ChromiumBase(BasePage):
:param index: 获取第几个从1开始可传入负数获取倒数第几个
:return: SessionElement对象或属性文本
"""
r = make_session_ele(self, locator, index=index)
if isinstance(r, NoneElement):
if Settings.raise_when_ele_not_found:
raise ElementNotFoundError(None, 's_ele()', {'locator': locator})
else:
r.method = 's_ele()'
r.args = {'locator': locator}
return r
return make_session_ele(self, locator, index=index, method='s_ele()')
def s_eles(self, locator):
"""查找所有符合条件的元素以SessionElement列表形式返回
@ -585,6 +578,9 @@ class ChromiumBase(BasePage):
if r is not False:
break
elif nIds[__ERROR__] == 'connection disconnected':
raise PageDisconnectedError
if perf_counter() >= end_time:
return NoneElement(self) if index is not None else []
@ -592,9 +588,11 @@ class ChromiumBase(BasePage):
timeout = end_time - perf_counter()
timeout = .5 if timeout <= 0 else timeout
result = self.driver.run('DOM.performSearch', query=loc, _timeout=timeout, includeUserAgentShadowDOM=True)
if not result or __ERROR__ not in result:
if result and __ERROR__ not in result:
num = result['resultCount']
search_ids.append(result['searchId'])
elif result and result[__ERROR__] == 'connection disconnected':
raise PageDisconnectedError
for _id in search_ids:
self._driver.run('DOM.discardSearchResults', searchId=_id)
@ -1065,7 +1063,7 @@ class ChromiumBase(BasePage):
name = f'{self.title}.jpg'
elif not name.endswith(('.jpg', '.jpeg', '.png', '.webp')):
name = f'{name}.jpg'
path = f'{path}{sep}{name}'
path = f'{path}{sep}{make_valid_name(name)}'
path = Path(path)
pic_type = path.suffix.lower()
@ -1117,50 +1115,6 @@ class ChromiumBase(BasePage):
f.write(png)
return str(path.absolute())
# --------------------即将废弃---------------------
@property
def page_load_strategy(self):
return self._load_mode
@property
def is_alive(self):
return self.states.is_alive
@property
def is_loading(self):
"""返回页面是否正在加载状态"""
return self._is_loading
@property
def ready_state(self):
return self._ready_state
@property
def size(self):
"""返回页面总宽高,格式:(宽, 高)"""
return self.rect.size
def get_session_storage(self, item=None):
return self.session_storage(item)
def get_local_storage(self, item=None):
return self.local_storage(item)
def get_cookies(self, as_dict=False, all_domains=False, all_info=False):
return self.cookies(as_dict=as_dict, all_domains=all_domains, all_info=all_info)
def upload(self, loc_or_ele, file_paths, by_js=False):
"""触发上传文件选择框并自动填入指定路径
:param loc_or_ele: 被点击后会触发文件选择框的元素或它的定位符
:param file_paths: 文件路径如果上传框支持多文件可传入列表或字符串字符串时多个文件用回车分隔
:param by_js: 是否用js方式点击
:return: None
"""
self.set.upload_files(file_paths)
self.ele(loc_or_ele).click(by_js=by_js)
self.wait.upload_paths_inputted()
class Timeout(object):
"""用于保存d模式timeout信息的类"""
@ -1228,50 +1182,3 @@ def close_privacy_dialog(page, tid):
except:
pass
def get_mhtml(page, path=None, name=None):
"""把当前页面保存为mhtml文件如果path和name参数都为None只返回mhtml文本
:param page: 要保存的页面对象
:param path: 保存路径为None且name不为None时保存在当前路径
:param name: 文件名为None且path不为None时用title属性值
:return: mhtml文本
"""
r = page.run_cdp('Page.captureSnapshot')['data']
if path is None and name is None:
return r
path = path or '.'
Path(path).mkdir(parents=True, exist_ok=True)
name = make_valid_name(name or page.title)
with open(f'{path}{sep}{name}.mhtml', 'w', encoding='utf-8') as f:
f.write(r.replace('\r\n', '\n'))
return r
def get_pdf(page, path=None, name=None, kwargs=None):
"""把当前页面保存为pdf文件如果path和name参数都为None只返回字节
:param page: 要保存的页面对象
:param path: 保存路径为None且name不为None时保存在当前路径
:param name: 文件名为None且path不为None时用title属性值
:param kwargs: pdf生成参数
:return: pdf文本
"""
if not kwargs:
kwargs = {}
kwargs['transferMode'] = 'ReturnAsBase64'
if 'printBackground' not in kwargs:
kwargs['printBackground'] = True
try:
r = page.run_cdp('Page.printToPDF', **kwargs)['data']
except:
raise RuntimeError('保存失败,可能浏览器版本不支持。')
from base64 import b64decode
r = b64decode(r)
if path is None and name is None:
return r
path = path or '.'
Path(path).mkdir(parents=True, exist_ok=True)
name = make_valid_name(name or page.title)
with open(f'{path}{sep}{name}.pdf', 'wb') as f:
f.write(r)
return r

View File

@ -14,6 +14,7 @@ from .._base.browser import Browser
from .._base.driver import Driver
from .._elements.chromium_element import ChromiumElement
from .._elements.session_element import SessionElement
from .._functions.elements import SessionElementsList, ChromiumElementsList
from .._pages.chromium_frame import ChromiumFrame
from .._pages.chromium_page import ChromiumPage
from .._units.actions import Actions
@ -187,21 +188,20 @@ class ChromiumBase(BasePage):
def eles(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> List[ChromiumElement]: ...
timeout: float = None) -> ChromiumElementsList: ...
def s_ele(self,
locator: Union[Tuple[str, str], str] = None,
index: int = 1) -> SessionElement: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> List[SessionElement]: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ...
def _find_elements(self,
locator: Union[Tuple[str, str], str, ChromiumElement, ChromiumFrame],
timeout: float = None,
index: Optional[int] = 1,
relative: bool = False,
raise_err: bool = None) \
-> Union[ChromiumElement, ChromiumFrame, List[Union[ChromiumElement, ChromiumFrame]]]: ...
raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, ChromiumElementsList]: ...
def refresh(self, ignore_cache: bool = False) -> None: ...

View File

@ -58,11 +58,11 @@ class ChromiumFrame(ChromiumBase):
self._rect = None
self._type = 'ChromiumFrame'
end_time = perf_counter() + 2
while perf_counter() < end_time: # todo: 优化
if self.url not in (None, 'about:blank'):
break
sleep(.1)
# end_time = perf_counter() + 2
# while perf_counter() < end_time:
# if self.url not in (None, 'about:blank'):
# break
# sleep(.1)
def __call__(self, locator, index=1, timeout=None):
"""在内部查找元素
@ -343,33 +343,6 @@ class ChromiumFrame(ChromiumBase):
except:
return None
# ----------------即将废弃-----------------
@property
def is_alive(self):
"""返回是否仍可用"""
return self.states.is_alive
@property
def page_size(self):
"""返回frame内页面尺寸格式(宽,, 高)"""
return self.rect.size
@property
def size(self):
"""返回frame元素大小"""
return self.frame_ele.rect.size
@property
def location(self):
"""返回frame元素左上角的绝对坐标"""
return self.frame_ele.rect.location
@property
def locations(self):
"""返回用于获取元素位置的对象"""
return self.frame_ele.rect
# ----------------即将废弃结束-----------------
def refresh(self):
"""刷新frame页面"""
self.doc_ele.run_js('this.location.reload();')
@ -480,7 +453,7 @@ class ChromiumFrame(ChromiumBase):
"""返回文档中当前元素前面符合条件的元素或节点组成的列表,可用查询语法筛选
查找范围不限同级元素而是整个DOM文档
:param locator: 用于筛选的查询语法
:param timeout: 查找节点的超时时间
:param timeout: 查找节点的超时时间
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 本元素前面的元素或节点组成的列表
"""
@ -490,7 +463,7 @@ class ChromiumFrame(ChromiumBase):
"""返回文档中当前元素后面符合条件的元素或节点组成的列表,可用查询语法筛选
查找范围不限同级元素而是整个DOM文档
:param locator: 用于筛选的查询语法
:param timeout: 查找节点的超时时间
:param timeout: 查找节点的超时时间
:param ele_only: 是否只获取元素为False时把文本注释节点也纳入
:return: 本元素前面的元素或节点组成的列表
"""
@ -586,7 +559,7 @@ class ChromiumFrame(ChromiumBase):
def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None):
"""在frame内查找单个元素
:param locator: 定位符或元素对象
:param timeout: 查找超时时间
:param timeout: 查找超时时间
:param index: 第几个结果从1开始可传入负数获取倒数第几个为None返回所有
:param relative: WebPage用的表示是否相对定位的参数
:param raise_err: 找不到元素是是否抛出异常为None时根据全局设置

View File

@ -10,9 +10,10 @@ from typing import Union, Tuple, List, Any, Optional
from .chromium_base import ChromiumBase
from .chromium_page import ChromiumPage
from .chromium_tab import ChromiumTab, WebPageTab
from .chromium_tab import ChromiumTab
from .web_page import WebPage
from .._elements.chromium_element import ChromiumElement
from .._functions.elements import ChromiumElementsList
from .._units.listener import FrameListener
from .._units.rect import FrameRect
from .._units.scroller import FrameScroller
@ -214,7 +215,6 @@ class ChromiumFrame(ChromiumBase):
timeout: float = None,
index: Optional[int] = 1,
relative: bool = False,
raise_err: bool = None) \
-> Union[ChromiumElement, ChromiumFrame, None, List[Union[ChromiumElement, ChromiumFrame]]]: ...
raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, None, ChromiumElementsList]: ...
def _is_inner_frame(self) -> bool: ...

View File

@ -9,14 +9,15 @@ from pathlib import Path
from threading import Lock
from time import sleep, perf_counter
from requests import get
from requests import Session
from .._base.browser import Browser
from .._configs.chromium_options import ChromiumOptions
from .._functions.browser import connect_browser
from .._functions.settings import Settings
from .._functions.tools import PortFinder
from .._pages.chromium_base import ChromiumBase, get_mhtml, get_pdf, Timeout
from .._functions.web import save_page
from .._pages.chromium_base import ChromiumBase, Timeout
from .._pages.chromium_tab import ChromiumTab
from .._units.setter import ChromiumPageSetter
from .._units.waiter import PageWaiter
@ -27,13 +28,12 @@ class ChromiumPage(ChromiumBase):
"""用于管理浏览器的类"""
_PAGES = {}
def __new__(cls, addr_or_opts=None, tab_id=None, timeout=None, addr_driver_opts=None):
def __new__(cls, addr_or_opts=None, tab_id=None, timeout=None):
"""
:param addr_or_opts: 浏览器地址:端口ChromiumOptions对象或端口数字int
:param tab_id: 要控制的标签页id不指定默认为激活的
:param timeout: 超时时间
"""
addr_or_opts = addr_or_opts or addr_driver_opts
opt = handle_options(addr_or_opts)
is_exist, browser_id = run_browser(opt)
if browser_id in cls._PAGES:
@ -49,7 +49,7 @@ class ChromiumPage(ChromiumBase):
cls._PAGES[browser_id] = r
return r
def __init__(self, addr_or_opts=None, tab_id=None, timeout=None, addr_driver_opts=None):
def __init__(self, addr_or_opts=None, tab_id=None, timeout=None):
"""
:param addr_or_opts: 浏览器地址:端口ChromiumOptions对象或端口数字int
:param tab_id: 要控制的标签页id不指定默认为激活的
@ -76,9 +76,13 @@ class ChromiumPage(ChromiumBase):
if self._is_exist and self._chromium_options._headless is False and 'headless' in r['userAgent'].lower():
self._browser.quit(3)
connect_browser(self._chromium_options)
ws = get(f'http://{self._chromium_options.address}/json/version', headers={'Connection': 'close'})
ws = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
self._browser = Browser(self._chromium_options.address, ws, self)
s = Session()
s.trust_env = False
ws = s.get(f'http://{self._chromium_options.address}/json/version', headers={'Connection': 'close'})
bid = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
self._browser = Browser(self._chromium_options.address, bid, self)
ws.close()
s.close()
def _d_set_runtime_settings(self):
"""设置运行时用到的属性"""
@ -154,7 +158,7 @@ class ChromiumPage(ChromiumBase):
:param kwargs: pdf生成参数
:return: as_pdf为True时返回bytes否则返回文件文本
"""
return get_pdf(self, path, name, kwargs) if as_pdf else get_mhtml(self, path, name)
return save_page(self, path, name, as_pdf, kwargs)
def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', as_id=False):
"""获取一个标签页对象id_or_num不为None时后面几个参数无效
@ -272,30 +276,6 @@ class ChromiumPage(ChromiumBase):
def __repr__(self):
return f'<ChromiumPage browser_id={self.browser.id} tab_id={self.tab_id}>'
# ----------即将废弃-----------
def close_other_tabs(self, tabs_or_ids=None):
"""关闭传入的标签页以外标签页,默认保留当前页。可传入多个
:param tabs_or_ids: 要保留的标签页对象或id可传入列表或元组为None时保存当前页
:return: None
"""
self.close_tabs(tabs_or_ids, True)
@property
def tabs(self):
"""返回所有标签页id组成的列表"""
return self.browser.tab_ids
def find_tabs(self, title=None, url=None, tab_type=None, single=True):
"""查找符合条件的tab返回它们组成的列表
:param title: 要匹配title的文本
:param url: 要匹配url的文本
:param tab_type: tab类型可用列表输入多个
:param single: 是否返回首个结果的id为False返回所有信息
:return: tab id或tab列表
"""
r = self._browser.find_tabs(title, url, tab_type)
return r[0]['id'] if r and single else r
def handle_options(addr_or_opts):
"""设置浏览器启动属性
@ -336,12 +316,16 @@ def run_browser(chromium_options):
"""连接浏览器"""
is_exist = connect_browser(chromium_options)
try:
ws = get(f'http://{chromium_options.address}/json/version', headers={'Connection': 'close'})
s = Session()
s.trust_env = False
ws = s.get(f'http://{chromium_options.address}/json/version', headers={'Connection': 'close'})
if not ws:
raise BrowserConnectError('\n浏览器连接失败如使用全局代理须设置不代理127.0.0.1地址。')
browser_id = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
ws.close()
s.close()
except KeyError:
raise BrowserConnectError('浏览器版本太旧,请升级。')
raise BrowserConnectError('浏览器版本太旧或此浏览器不支持接管')
except:
raise BrowserConnectError('\n浏览器连接失败如使用全局代理须设置不代理127.0.0.1地址。')
return is_exist, browser_id

View File

@ -11,8 +11,8 @@ from time import sleep
from .._base.base import BasePage
from .._configs.session_options import SessionOptions
from .._functions.settings import Settings
from .._functions.web import set_session_cookies, set_browser_cookies
from .._pages.chromium_base import ChromiumBase, get_mhtml, get_pdf
from .._functions.web import set_session_cookies, set_browser_cookies, save_page
from .._pages.chromium_base import ChromiumBase
from .._pages.session_page import SessionPage
from .._units.setter import TabSetter, WebPageTabSetter
from .._units.waiter import TabWaiter
@ -91,7 +91,7 @@ class ChromiumTab(ChromiumBase):
:param kwargs: pdf生成参数
:return: as_pdf为True时返回bytes否则返回文件文本
"""
return get_pdf(self, path, name, kwargs) if as_pdf else get_mhtml(self, path, name)
return save_page(self, path, name, as_pdf, kwargs)
def __repr__(self):
return f'<ChromiumTab browser_id={self.browser.id} tab_id={self.tab_id}>'
@ -399,7 +399,3 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
def __repr__(self):
return f'<WebPageTab browser_id={self.browser.id} tab_id={self.tab_id}>'
# --------即将废弃-------
def get_cookies(self, as_dict=False, all_domains=False, all_info=False):
return self.cookies(as_dict=as_dict, all_domains=all_domains, all_info=all_info)

View File

@ -6,7 +6,7 @@
@License : BSD 3-Clause.
"""
from pathlib import Path
from typing import Union, Tuple, Any, List, Optional
from typing import Union, Tuple, Any, Optional
from requests import Session, Response
@ -18,6 +18,7 @@ from .web_page import WebPage
from .._base.browser import Browser
from .._elements.chromium_element import ChromiumElement
from .._elements.session_element import SessionElement
from .._functions.elements import SessionElementsList, ChromiumElementsList
from .._units.rect import TabRect
from .._units.setter import TabSetter, WebPageTabSetter
from .._units.waiter import TabWaiter
@ -150,13 +151,13 @@ class WebPageTab(SessionPage, ChromiumTab):
def eles(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> List[Union[ChromiumElement, SessionElement]]: ...
timeout: float = None) -> Union[SessionElementsList, ChromiumElementsList]: ...
def s_ele(self,
locator: Union[Tuple[str, str], str] = None,
index: int = 1) -> SessionElement: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> List[SessionElement]: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ...
def change_mode(self, mode: str = None, go: bool = True, copy_cookies: bool = True) -> None: ...
@ -199,5 +200,4 @@ class WebPageTab(SessionPage, ChromiumTab):
index: Optional[int] = 1,
relative: bool = False,
raise_err: bool = None) \
-> Union[ChromiumElement, SessionElement, ChromiumFrame, List[SessionElement], List[
Union[ChromiumElement, ChromiumFrame]]]: ...
-> Union[ChromiumElement, SessionElement, ChromiumFrame, SessionElementsList, ChromiumElementsList]: ...

View File

@ -354,10 +354,6 @@ class SessionPage(BasePage):
def __repr__(self):
return f'<SessionPage url={self.url}>'
# ---------即将废弃---------
def get_cookies(self, as_dict=False, all_domains=False, all_info=False):
return self.cookies(as_dict=as_dict, all_domains=all_domains, all_info=all_info)
def check_headers(kwargs, headers, arg):
"""检查kwargs或headers中是否有arg所示属性"""

View File

@ -6,7 +6,7 @@
@License : BSD 3-Clause.
"""
from pathlib import Path
from typing import Any, Union, Tuple, List, Optional
from typing import Any, Union, Tuple, Optional
from requests import Session, Response
from requests.structures import CaseInsensitiveDict
@ -14,6 +14,7 @@ from requests.structures import CaseInsensitiveDict
from .._base.base import BasePage
from .._configs.session_options import SessionOptions
from .._elements.session_element import SessionElement
from .._functions.elements import SessionElementsList
from .._units.setter import SessionPageSetter
@ -97,21 +98,20 @@ class SessionPage(BasePage):
def eles(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> List[SessionElement]: ...
timeout: float = None) -> SessionElementsList: ...
def s_ele(self,
locator: Union[Tuple[str, str], str, SessionElement] = None,
index: int = 1) -> SessionElement: ...
def s_eles(self, loc: Union[Tuple[str, str], str]) -> List[SessionElement]: ...
def s_eles(self, loc: Union[Tuple[str, str], str]) -> SessionElementsList: ...
def _find_elements(self,
locator: Union[Tuple[str, str], str, SessionElement],
timeout: float = None,
index: Optional[int] = 1,
relative: bool = True,
raise_err: bool = None) \
-> Union[SessionElement, List[SessionElement]]: ...
raise_err: bool = None) -> Union[SessionElement, SessionElementsList]: ...
def cookies(self,
as_dict: bool = False,

View File

@ -17,15 +17,14 @@ from .._units.setter import WebPageSetter
class WebPage(SessionPage, ChromiumPage, BasePage):
"""整合浏览器和request的页面类"""
def __new__(cls, mode='d', timeout=None, chromium_options=None, session_or_options=None, driver_or_options=None):
def __new__(cls, mode='d', timeout=None, chromium_options=None, session_or_options=None):
"""初始化函数
:param mode: 'd' 's'即driver模式和session模式
:param timeout: 超时时间d模式时为寻找元素时间s模式时为连接时间默认10秒
:param chromium_options: Driver对象只使用s模式时应传入False
:param session_or_options: Session对象或SessionOptions对象只使用d模式时应传入False
"""
opts = chromium_options or driver_or_options
return super().__new__(cls, opts)
return super().__new__(cls, chromium_options)
def __init__(self, mode='d', timeout=None, chromium_options=None, session_or_options=None, driver_or_options=None):
"""初始化函数
@ -402,7 +401,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None):
"""返回页面中符合条件的元素、属性或节点文本,默认返回第一个
:param locator: 元素的定位信息可以是元素对象loc元组或查询字符串
:param timeout: 查找元素超时时间d模式专用
:param timeout: 查找元素超时时间d模式专用
:param index: 第几个结果从1开始可传入负数获取倒数第几个为None返回所有
:param relative: WebPage用的表示是否相对定位的参数
:param raise_err: 找不到元素是是否抛出异常为None时根据全局设置
@ -415,7 +414,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
def quit(self, timeout=5, force=True):
"""关闭浏览器和Session
:param timeout: 等待浏览器关闭超时时间
:param timeout: 等待浏览器关闭超时时间
:param force: 关闭超时是否强制终止进程
:return: None
"""
@ -431,7 +430,3 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
def __repr__(self):
return f'<WebPage browser_id={self.browser.id} tab_id={self.tab_id}>'
# -------即将废弃--------
def get_cookies(self, as_dict=False, all_domains=False, all_info=False):
return self.cookies(as_dict=as_dict, all_domains=all_domains, all_info=all_info)

View File

@ -19,6 +19,7 @@ from .._configs.chromium_options import ChromiumOptions
from .._configs.session_options import SessionOptions
from .._elements.chromium_element import ChromiumElement
from .._elements.session_element import SessionElement
from .._functions.elements import SessionElementsList, ChromiumElementsList
from .._units.setter import WebPageSetter
@ -108,13 +109,13 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
def eles(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> List[Union[ChromiumElement, SessionElement]]: ...
timeout: float = None) -> Union[SessionElementsList, ChromiumElementsList]: ...
def s_ele(self,
locator: Union[Tuple[str, str], str] = None,
index: int = 1) -> SessionElement: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> List[SessionElement]: ...
def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ...
def change_mode(self, mode: str = None, go: bool = True, copy_cookies: bool = True) -> None: ...
@ -185,8 +186,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
index: Optional[int] = 1,
relative: bool = False,
raise_err: bool = None) \
-> Union[ChromiumElement, SessionElement, ChromiumFrame, List[SessionElement],
List[Union[ChromiumElement, ChromiumFrame]]]: ...
-> Union[ChromiumElement, SessionElement, ChromiumFrame, SessionElementsList, ChromiumElementsList]: ...
def _set_start_options(self,
dr_opt: Union[Driver, bool, None],

View File

@ -24,6 +24,7 @@ class Actions:
self.modifier = 0 # 修饰符Alt=1, Ctrl=2, Meta/Command=4, Shift=8
self.curr_x = 0 # 视口坐标
self.curr_y = 0
self._holding = 'left'
def move_to(self, ele_or_loc, offset_x=0, offset_y=0, duration=.5):
"""鼠标移动到元素中点,或页面上的某个绝对坐标。可设置偏移量
@ -86,7 +87,7 @@ class Actions:
t = perf_counter()
self.curr_x = x
self.curr_y = y
self._dr.run('Input.dispatchMouseEvent', type='mouseMoved',
self._dr.run('Input.dispatchMouseEvent', type='mouseMoved', button=self._holding,
x=self.curr_x, y=self.curr_y, modifiers=self.modifier)
ss = .02 - perf_counter() + t
if ss > 0:
@ -140,7 +141,7 @@ class Actions:
:return: self
"""
if on_ele:
self.move_to(on_ele, duration=0)
self.move_to(on_ele, duration=.2)
self._release('left')
return self
@ -158,7 +159,7 @@ class Actions:
:return: self
"""
if on_ele:
self.move_to(on_ele, duration=0)
self.move_to(on_ele, duration=.2)
self._release('right')
return self
@ -176,7 +177,7 @@ class Actions:
:return: self
"""
if on_ele:
self.move_to(on_ele, duration=0)
self.move_to(on_ele, duration=.2)
self._release('middle')
return self
@ -188,9 +189,10 @@ class Actions:
:return: self
"""
if on_ele:
self.move_to(on_ele, duration=0)
self.move_to(on_ele, duration=.2)
self._dr.run('Input.dispatchMouseEvent', type='mousePressed', button=button, clickCount=count,
x=self.curr_x, y=self.curr_y, modifiers=self.modifier)
self._holding = button
return self
def _release(self, button):
@ -200,17 +202,18 @@ class Actions:
"""
self._dr.run('Input.dispatchMouseEvent', type='mouseReleased', button=button, clickCount=1,
x=self.curr_x, y=self.curr_y, modifiers=self.modifier)
self._holding = 'left'
return self
def scroll(self, delta_x=0, delta_y=0, on_ele=None):
def scroll(self, delta_y=0, delta_x=0, on_ele=None):
"""滚动鼠标滚轮,可先移动到元素上
:param delta_x: 滚轮变化值x
:param delta_y: 滚轮变化值y
:param delta_x: 滚轮变化值x
:param on_ele: ChromiumElement元素
:return: self
"""
if on_ele:
self.move_to(on_ele, duration=0)
self.move_to(on_ele, duration=.2)
self._dr.run('Input.dispatchMouseEvent', type='mouseWheel', x=self.curr_x, y=self.curr_y,
deltaX=delta_x, deltaY=delta_y, modifiers=self.modifier)
return self

View File

@ -50,11 +50,12 @@ class Actions:
self.modifier: int = ...
self.curr_x: int = ...
self.curr_y: int = ...
self._holding: str = ...
def move_to(self, ele_or_loc: Union[ChromiumElement, Tuple[int, int], str],
offset_x: int = 0, offset_y: int = 0, duration: float = .5) -> Actions: ...
def move_to(self, ele_or_loc: Union[ChromiumElement, Tuple[float, float], str],
offset_x: float = 0, offset_y: float = 0, duration: float = .5) -> Actions: ...
def move(self, offset_x: int = 0, offset_y: int = 0, duration: float = .5) -> Actions: ...
def move(self, offset_x: float = 0, offset_y: float = 0, duration: float = .5) -> Actions: ...
def click(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
@ -81,7 +82,7 @@ class Actions:
def _release(self, button: str) -> Actions: ...
def scroll(self, delta_x: int = 0, delta_y: int = 0,
def scroll(self, delta_y: int = 0, delta_x: int = 0,
on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
def up(self, pixel: int) -> Actions: ...

View File

@ -87,8 +87,8 @@ class Clicker(object):
x = rect[1][0] - (rect[1][0] - rect[0][0]) / 2
y = rect[0][0] + 3
try:
r = self._ele.owner.run_cdp('DOM.getNodeForLocation', x=x, y=y, includeUserAgentShadowDOM=True,
ignorePointerEventsNone=True)
r = self._ele.owner.run_cdp('DOM.getNodeForLocation', x=int(x), y=int(y),
includeUserAgentShadowDOM=True, ignorePointerEventsNone=True)
if r['backendNodeId'] != self._ele._backend_id:
vx, vy = self._ele.rect.viewport_midpoint
else:
@ -113,11 +113,19 @@ class Clicker(object):
x, y = self._ele.rect.viewport_click_point
self._click(x, y, 'right')
def middle(self):
"""中键单击"""
def middle(self, get_tab=True):
"""中键单击默认返回新出现的tab对象
:param get_tab: 是否返回新tab对象为False则返回None
:return: Tab对象或None
"""
self._ele.owner.scroll.to_see(self._ele)
x, y = self._ele.rect.viewport_click_point
self._click(x, y, 'middle')
if get_tab:
tid = self._ele.page.wait.new_tab()
if not tid:
raise RuntimeError('没有出现新标签页。')
return self._ele.page.get_tab(tid)
def at(self, offset_x=None, offset_y=None, button='left', count=1):
"""带偏移量点击本元素相对于左上角坐标。不传入x或y值时点击元素中间点
@ -196,19 +204,5 @@ class Clicker(object):
"""
self._ele.owner.run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=client_x,
y=client_y, button=button, clickCount=count, _ignore=AlertExistsError)
# sleep(.05)
self._ele.owner.run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=client_x,
y=client_y, button=button, _ignore=AlertExistsError)
# -------------即将废弃--------------
def twice(self):
"""双击元素"""
self.at(count=2)
def multiple(self, times=2):
"""多次点击
:param times: 默认双击
:return: None
"""
self.at(count=times)

View File

@ -23,7 +23,7 @@ class Clicker(object):
def right(self) -> None: ...
def middle(self) -> None: ...
def middle(self, get_tab: bool = True) -> Union[ChromiumTab, WebPageTab, None]: ...
def at(self,
offset_x: float = None,
@ -39,10 +39,10 @@ class Clicker(object):
suffix: str = None,
new_tab: bool = False,
by_js: bool = False,
timeout:float=None) -> DownloadMission: ...
timeout: float = None) -> DownloadMission: ...
def to_upload(self, file_paths: Union[str, Path, list, tuple], by_js: bool = False) -> None: ...
def for_new_tab(self, by_js:bool=False)->Union[ChromiumTab, WebPageTab]:...
def for_new_tab(self, by_js: bool = False) -> Union[ChromiumTab, WebPageTab]: ...
def _click(self, client_x: float, client_y: float, button: str = 'left', count: int = 1) -> None: ...

View File

@ -292,7 +292,7 @@ class DownloadMission(object):
def wait(self, show=True, timeout=None, cancel_if_timeout=True):
"""等待任务结束
:param show: 是否显示下载信息
:param timeout: 超时时间为None则无限等待
:param timeout: 超时时间为None则无限等待
:param cancel_if_timeout: 超时时是否取消任务
:return: 等待成功返回完整路径否则返回False
"""

View File

@ -119,7 +119,7 @@ class Listener(object):
def wait(self, count=1, timeout=None, fit_count=True, raise_err=None):
"""等待符合要求的数据包到达指定数量
:param count: 需要捕捉的数据包数量
:param timeout: 超时时间为None无限等待
:param timeout: 超时时间为None无限等待
:param fit_count: 是否必须满足总数要求发生超时为True返回False为False返回已捕捉到的数据包
:param raise_err: 超时时是否抛出错误为None时根据Settings设置
:return: count为1时返回数据包对象大于1时返回列表超时且fit_count为True时返回False
@ -128,7 +128,7 @@ class Listener(object):
raise RuntimeError('监听未启动或已暂停。')
if not timeout:
while self._caught.qsize() < count:
sleep(.05)
sleep(.03)
fail = False
else:
@ -140,6 +140,7 @@ class Listener(object):
if self._caught.qsize() >= count:
fail = False
break
sleep(.03)
if fail:
if fit_count or not self._caught.qsize():
@ -158,7 +159,7 @@ class Listener(object):
def steps(self, count=None, timeout=None, gap=1):
"""用于单步操作,可实现每收到若干个数据包执行一步操作(如翻页)
:param count: 需捕获的数据包总数为None表示无限
:param timeout: 每个数据包等待时间为None表示无限
:param timeout: 每个数据包等待时间为None表示无限
:param gap: 每接收到多少个数据包返回一次数据
:return: 用于在接收到监听目标时触发动作的可迭代对象
"""
@ -177,7 +178,7 @@ class Listener(object):
caught += gap
if caught >= count:
return
sleep(.05)
sleep(.03)
def stop(self):
"""停止监听,清空已监听到的列表"""
@ -218,7 +219,7 @@ class Listener(object):
def wait_silent(self, timeout=None, targets_only=False, limit=0):
"""等待所有请求结束
:param timeout: 超时为None时无限等待
:param timeout: 超时时间为None时无限等待
:param targets_only: 是否只等待targets指定的请求结束
:param limit: 剩下多少个连接时视为结束
:return: 返回是否等待成功
@ -250,13 +251,13 @@ class Listener(object):
self._target_id = target_id
self._address = address
self._owner = owner
debug = False
# debug = False
if self._driver:
debug = self._driver._debug
# debug = self._driver._debug
self._driver.stop()
if self.listening:
self._driver = Driver(self._target_id, 'page', self._address)
self._driver._debug = debug
# self._driver._debug = debug
self._driver.run('Network.enable')
self._set_callback()
@ -479,7 +480,7 @@ class DataPacket(object):
def wait_extra_info(self, timeout=None):
"""等待额外的信息加载完成
:param timeout: 超时时间None为无限等待
:param timeout: 超时时间None为无限等待
:return: 是否等待成功
"""
if timeout is None:

View File

@ -16,7 +16,7 @@ class ElementRect(object):
@property
def corners(self):
"""返回元素四个角坐标,顺序:上、右上、右下、左下没有大小的元素抛出NoRectError"""
"""返回元素四个角坐标,顺序:上、右上、右下、左下没有大小的元素抛出NoRectError"""
vr = self._get_viewport_rect('border')
r = self._ele.owner.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
sx = r['pageX']
@ -25,7 +25,7 @@ class ElementRect(object):
@property
def viewport_corners(self):
"""返回元素四个角视口坐标,顺序:上、右上、右下、左下没有大小的元素抛出NoRectError"""
"""返回元素四个角视口坐标,顺序:上、右上、右下、左下没有大小的元素抛出NoRectError"""
r = self._get_viewport_rect('border')
return (r[0], r[1]), (r[2], r[3]), (r[4], r[5]), (r[6], r[7])
@ -225,10 +225,10 @@ class FrameRect(object):
@property
def corners(self):
"""返回元素四个角坐标,顺序:上、右上、右下、左下"""
"""返回元素四个角坐标,顺序:上、右上、右下、左下"""
return self._frame.frame_ele.rect.corners
@property
def viewport_corners(self):
"""返回元素四个角视口坐标,顺序:上、右上、右下、左下"""
"""返回元素四个角视口坐标,顺序:上、右上、右下、左下"""
return self._frame.frame_ele.rect.viewport_corners

View File

@ -23,7 +23,7 @@ class SelectElement(object):
def __call__(self, text_or_index, timeout=None):
"""选定下拉列表中子元素
:param text_or_index: 根据文本值选或序号择选项若允许多选传入list或tuple可多选
:param timeout: 超时时间不输入默认实用页面超时时间
:param timeout: 超时时间不输入默认实用页面超时时间
:return: None
"""
para_type = 'index' if isinstance(text_or_index, int) else 'text'
@ -82,7 +82,7 @@ class SelectElement(object):
def by_text(self, text, timeout=None):
"""此方法用于根据text值选择项。当元素是多选列表时可以接收list或tuple
:param text: text属性值传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
"""
return self._select(text, 'text', False, timeout)

View File

@ -14,7 +14,7 @@ from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesS
from .._functions.settings import Settings
from .._functions.tools import show_or_hide_browser
from .._functions.web import format_headers
from ..errors import ElementLostError
from ..errors import ElementLostError, JavaScriptError
class BasePageSetter(object):
@ -173,13 +173,6 @@ class ChromiumBaseSetter(BasePageSetter):
self._owner.run_cdp('Network.enable')
self._owner.run_cdp('Network.setBlockedURLs', urls=urls)
# --------------即将废弃---------------
@property
def load_strategy(self):
"""返回用于设置页面加载策略的对象"""
return LoadMode(self._owner)
class TabSetter(ChromiumBaseSetter):
def __init__(self, owner):
@ -494,6 +487,17 @@ class ChromiumElementSetter(object):
value = value.replace('"', r'\"')
self._ele.run_js(f'this.{name}="{value}";')
def style(self, name, value):
"""设置元素style样式
:param name: 样式名称
:param value: 样式值
:return: None
"""
try:
self._ele.run_js(f'this.style.{name}="{value}";')
except JavaScriptError:
raise ValueError(f'设置失败,请检查属性名{name}')
def innerHTML(self, html):
"""设置元素innerHTML
:param html: html文本

View File

@ -172,6 +172,8 @@ class ChromiumElementSetter(object):
def property(self, name: str, value: str) -> None: ...
def style(self, name: str, value: str) -> None: ...
def innerHTML(self, html: str) -> None: ...
def value(self, value: str) -> None: ...

View File

@ -18,7 +18,7 @@ class ElementStates(object):
@property
def is_selected(self):
"""返回元素是否被选择"""
"""返回列表元素是否被选择"""
return self._ele.run_js('return this.selected;')
@property
@ -42,9 +42,9 @@ class ElementStates(object):
def is_alive(self):
"""返回元素是否仍在DOM中"""
try:
self._ele.attrs
return True
except Exception:
return self._ele.owner.run_cdp('DOM.describeNode',
backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0
except ElementLostError:
return False
@property
@ -71,6 +71,11 @@ class ElementStates(object):
except CDPError:
return False
@property
def is_clickable(self):
"""返回元素是否可被模拟点击,从是否有大小、是否可用、是否显示、是否响应点击判断,不判断是否被遮挡"""
return self.has_rect and self.is_enabled and self.is_displayed and self._ele.style('pointer-events') != 'none'
@property
def has_rect(self):
"""返回元素是否拥有位置和大小没有返回False有返回四个角在页面中坐标组成的列表"""
@ -96,9 +101,9 @@ class ShadowRootStates(object):
def is_alive(self):
"""返回元素是否仍在DOM中"""
try:
self._ele.owner.run_cdp('DOM.describeNode', backendNodeId=self._ele._backend_id)
return True
except Exception:
return self._ele.owner.run_cdp('DOM.describeNode',
backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0
except ElementLostError:
return False

View File

@ -41,7 +41,10 @@ class ElementStates(object):
def is_covered(self) -> Union[Literal[False], int]: ...
@property
def has_rect(self) -> Union[bool, List[Tuple[float, float]]]: ...
def is_clickable(self) -> bool: ...
@property
def has_rect(self) -> Union[Literal[False], List[Tuple[float, float]]]: ...
class ShadowRootStates(object):

View File

@ -36,7 +36,7 @@ class BaseWaiter(OriginWaiter):
def ele_deleted(self, loc_or_ele, timeout=None, raise_err=None):
"""等待元素从DOM中删除
:param loc_or_ele: 要等待的元素可以是已有元素定位符
:param timeout: 超时时间默认读取页面超时时间
:param timeout: 超时时间默认读取页面超时时间
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -46,7 +46,7 @@ class BaseWaiter(OriginWaiter):
def ele_displayed(self, loc_or_ele, timeout=None, raise_err=None):
"""等待元素变成显示状态
:param loc_or_ele: 要等待的元素可以是已有元素定位符
:param timeout: 超时时间默认读取页面超时时间
:param timeout: 超时时间默认读取页面超时时间
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -65,7 +65,7 @@ class BaseWaiter(OriginWaiter):
def ele_hidden(self, loc_or_ele, timeout=None, raise_err=None):
"""等待元素变成隐藏状态
:param loc_or_ele: 要等待的元素可以是已有元素定位符
:param timeout: 超时时间默认读取页面超时时间
:param timeout: 超时时间默认读取页面超时时间
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -84,7 +84,7 @@ class BaseWaiter(OriginWaiter):
def eles_loaded(self, locators, timeout=None, any_one=False, raise_err=None):
"""等待元素加载到DOM可等待全部或任意一个
:param locators: 要等待的元素输入定位符用list输入多个
:param timeout: 超时时间默认读取页面超时时间
:param timeout: 超时时间默认读取页面超时时间
:param any_one: 是否等待到一个就返回
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 成功返回True失败返回False
@ -119,9 +119,10 @@ class BaseWaiter(OriginWaiter):
locators = ((get_loc(locators)[1],) if (isinstance(locators, str) or isinstance(locators, tuple)
and locators[0] in by and len(locators) == 2)
else [get_loc(l)[1] for l in locators])
method = any if any_one else all
timeout = self._driver.timeout if timeout is None else timeout
end_time = perf_counter() + timeout
method = any if any_one else all
while perf_counter() < end_time:
if method([_find(l, self._driver.driver) for l in locators]):
return True
@ -133,7 +134,7 @@ class BaseWaiter(OriginWaiter):
def load_start(self, timeout=None, raise_err=None):
"""等待页面开始加载
:param timeout: 超时时间为None时使用页面timeout属性
:param timeout: 超时时间为None时使用页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -141,7 +142,7 @@ class BaseWaiter(OriginWaiter):
def doc_loaded(self, timeout=None, raise_err=None):
"""等待页面加载完成
:param timeout: 超时时间为None时使用页面timeout属性
:param timeout: 超时时间为None时使用页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -158,7 +159,7 @@ class BaseWaiter(OriginWaiter):
def download_begin(self, timeout=None, cancel_it=False):
"""等待浏览器下载开始,可将其拦截
:param timeout: 超时时间None使用页面对象超时时间
:param timeout: 超时时间None使用页面对象超时时间
:param cancel_it: 是否取消该任务
:return: 成功返回任务对象失败返回False
"""
@ -184,7 +185,7 @@ class BaseWaiter(OriginWaiter):
"""等待url变成包含或不包含指定文本
:param text: 用于识别的文本
:param exclude: 是否排除为True时当url不包含text指定文本时返回True
:param timeout: 超时时间
:param timeout: 超时时间
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -194,7 +195,7 @@ class BaseWaiter(OriginWaiter):
"""等待title变成包含或不包含指定文本
:param text: 用于识别的文本
:param exclude: 是否排除为True时当title不包含text指定文本时返回True
:param timeout: 超时时间
:param timeout: 超时时间
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -205,7 +206,7 @@ class BaseWaiter(OriginWaiter):
:param arg: 要被匹配的属性
:param text: 用于识别的文本
:param exclude: 是否排除为True时当属性不包含text指定文本时返回True
:param timeout: 超时时间
:param timeout: 超时时间
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -231,7 +232,7 @@ class BaseWaiter(OriginWaiter):
def _loading(self, timeout=None, start=True, gap=.01, raise_err=None):
"""等待页面开始加载或加载完成
:param timeout: 超时时间为None时使用页面timeout属性
:param timeout: 超时时间为None时使用页面timeout属性
:param start: 等待开始还是结束
:param gap: 间隔秒数
:param raise_err: 等待失败时是否报错为None时根据Settings设置
@ -251,45 +252,12 @@ class BaseWaiter(OriginWaiter):
else:
return False
# -----------即将废弃-----------
def data_packets(self, count=1, timeout=None, fix_count: bool = True):
"""等待符合要求的数据包到达指定数量
:param count: 需要捕捉的数据包数量
:param timeout: 超时时间为None无限等待
:param fix_count: 是否必须满足总数要求发生超时为True返回False为False返回已捕捉到的数据包
:return: count为1时返回数据包对象大于1时返回列表超时且fix_count为True时返回False"""
return self._driver.listen.wait(count, timeout, fix_count)
def load_complete(self, timeout=None, raise_err=None):
"""等待页面加载完成
:param timeout: 超时时间为None时使用页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
return self._loading(timeout=timeout, start=False, raise_err=raise_err)
def ele_loaded(self, locator, timeout=None, raise_err=None):
"""等待元素加载到DOM
:param locator: 要等待的元素输入定位符
:param timeout: 超时时间默认读取页面超时时间
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 成功返回元素对象失败返回False
"""
ele = self._driver._ele(locator, raise_err=False, timeout=timeout)
if ele:
return ele
if raise_err is True or Settings.raise_when_wait_failed is True:
raise WaitTimeoutError(f'等待元素加载失败(等待{timeout}秒)。')
else:
return False
class TabWaiter(BaseWaiter):
def downloads_done(self, timeout=None, cancel_if_timeout=True):
"""等待所有浏览器下载任务结束
:param timeout: 超时时间为None时无限等待
:param timeout: 超时时间为None时无限等待
:param cancel_if_timeout: 超时时是否取消剩余任务
:return: 是否等待成功
"""
@ -329,7 +297,7 @@ class PageWaiter(TabWaiter):
def new_tab(self, timeout=None, raise_err=None):
"""等待新标签页出现
:param timeout: 等待超时时间为None则使用页面对象timeout属性
:param timeout: 超时时间为None则使用页面对象timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 等到新标签页返回其id否则返回False
"""
@ -348,7 +316,7 @@ class PageWaiter(TabWaiter):
def all_downloads_done(self, timeout=None, cancel_if_timeout=True):
"""等待所有浏览器下载任务结束
:param timeout: 超时时间为None时无限等待
:param timeout: 超时时间为None时无限等待
:param cancel_if_timeout: 超时时是否取消剩余任务
:return: 是否等待成功
"""
@ -388,7 +356,7 @@ class ElementWaiter(OriginWaiter):
def deleted(self, timeout=None, raise_err=None):
"""等待元素从dom删除
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -396,7 +364,7 @@ class ElementWaiter(OriginWaiter):
def displayed(self, timeout=None, raise_err=None):
"""等待元素从dom显示
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -404,7 +372,7 @@ class ElementWaiter(OriginWaiter):
def hidden(self, timeout=None, raise_err=None):
"""等待元素从dom隐藏
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -412,15 +380,15 @@ class ElementWaiter(OriginWaiter):
def covered(self, timeout=None, raise_err=None):
"""等待当前元素被遮盖
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
:return: 成功返回覆盖元素id返回False
"""
return self._wait_state('is_covered', True, timeout, raise_err, err_text='等待元素被覆盖失败。')
def not_covered(self, timeout=None, raise_err=None):
"""等待当前元素不被遮盖
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -428,7 +396,7 @@ class ElementWaiter(OriginWaiter):
def enabled(self, timeout=None, raise_err=None):
"""等待当前元素变成可用
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -436,7 +404,7 @@ class ElementWaiter(OriginWaiter):
def disabled(self, timeout=None, raise_err=None):
"""等待当前元素变成不可用
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -444,7 +412,7 @@ class ElementWaiter(OriginWaiter):
def disabled_or_deleted(self, timeout=None, raise_err=None):
"""等待当前元素变成不可用或从DOM移除
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -461,10 +429,10 @@ class ElementWaiter(OriginWaiter):
else:
return False
def stop_moving(self, gap=.1, timeout=None, raise_err=None):
def stop_moving(self, timeout=None, gap=.1, raise_err=None):
"""等待当前元素停止运动
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param gap: 检测间隔时间
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
@ -494,33 +462,51 @@ class ElementWaiter(OriginWaiter):
else:
return False
def has_rect(self, timeout=None, raise_err=None):
"""等待当前元素有大小及位置属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
def clickable(self, wait_moved=True, timeout=None, raise_err=None):
"""等待当前元素可被点击
:param wait_moved: 是否等待元素运动结束
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功
"""
t1 = perf_counter()
r = self._wait_state('is_clickable', True, timeout, raise_err, err_text='等待元素可点击失败(等{}秒)。')
r = self.stop_moving(timeout=perf_counter() - t1) if wait_moved and r else r
if raise_err and not r:
raise WaitTimeoutError(f'等待元素可点击失败(等{timeout}秒)。')
return r
def has_rect(self, timeout=None, raise_err=None):
"""等待当前元素有大小及位置属性
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 成功返回元素四角坐标左上 右上 右下 左下失败返回False
"""
return self._wait_state('has_rect', True, timeout, raise_err, err_text='等待元素拥有大小及位置失败(等{}秒)。')
def _wait_state(self, attr, mode=False, timeout=None, raise_err=None, err_text=None):
"""等待元素某个元素状态到达指定状态
:param attr: 状态名称
:param mode: True或False
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param mode: 等待True还是False
:param timeout: 超时时间为None使用元素所在页面timeout属性
:param raise_err: 等待失败时是否报错为None时根据Settings设置
:param err_text: 抛出错误时显示的信息
:return: 是否等待成功
"""
err_text = err_text or '等待元素状态改变失败(等待{}秒)。'
a = self._ele.states.__getattribute__(attr)
if (a and mode) or (not a and not mode):
return True if isinstance(a, bool) else a
if timeout is None:
timeout = self._owner.timeout
end_time = perf_counter() + timeout
while perf_counter() < end_time:
a = self._ele.states.__getattribute__(attr)
if (a and mode) or (not a and not mode):
return True
return True if isinstance(a, bool) else a
sleep(.05)
err_text = err_text or '等待元素状态改变失败(等待{}秒)。'
if raise_err is True or Settings.raise_when_wait_failed is True:
raise WaitTimeoutError(err_text.format(timeout))
else:

View File

@ -5,7 +5,7 @@
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
"""
from typing import Union, Tuple
from typing import Union, Tuple, Literal, List
from .downloader import DownloadMission
from .._elements.chromium_element import ChromiumElement
@ -91,7 +91,7 @@ class ElementWaiter(OriginWaiter):
def hidden(self, timeout: float = None, raise_err: bool = None) -> bool: ...
def covered(self, timeout: float = None, raise_err: bool = None) -> bool: ...
def covered(self, timeout: float = None, raise_err: bool = None) -> Union[Literal[False], int]: ...
def not_covered(self, timeout: float = None, raise_err: bool = None) -> bool: ...
@ -99,11 +99,15 @@ class ElementWaiter(OriginWaiter):
def disabled(self, timeout: float = None, raise_err: bool = None) -> bool: ...
def has_rect(self, timeout: float = None, raise_err: bool = None) -> bool: ...
def clickable(self, wait_moved: bool = True, timeout: float = None, raise_err: bool = None) -> bool: ...
def has_rect(self,
timeout: float = None,
raise_err: bool = None) -> Union[Literal[False], List[Tuple[float, float]]]: ...
def disabled_or_deleted(self, timeout: float = None, raise_err: bool = None) -> bool: ...
def stop_moving(self, gap: float = .1, timeout: float = None, raise_err: bool = None) -> bool: ...
def stop_moving(self, timeout: float = None, gap: float = .1, raise_err: bool = None) -> bool: ...
def _wait_state(self,
attr: str,

View File

@ -7,6 +7,7 @@
"""
from ._elements.session_element import make_session_ele
from ._functions.by import By
from ._functions.elements import get_eles
from ._functions.keys import Keys
from ._functions.settings import Settings
from ._functions.tools import wait_until, configs_to_here
@ -15,7 +16,7 @@ from ._pages.chromium_page import ChromiumPage
from ._units.actions import Actions
__all__ = ['make_session_ele', 'Actions', 'Keys', 'By', 'Settings', 'wait_until', 'configs_to_here', 'get_blob',
'tree', 'from_selenium', 'from_playwright']
'tree', 'from_selenium', 'from_playwright', 'get_eles']
def from_selenium(driver):