Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
g1879 2023-11-08 20:16:37 +08:00
commit 834d2ef00d
33 changed files with 1163 additions and 988 deletions

View File

@ -5,8 +5,8 @@
"""
from time import sleep
from .chromium_driver import BrowserDriver
from .._units.download_manager import BrowserDownloadManager
from .chromium_driver import BrowserDriver, ChromiumDriver
from .._units.download_manager import DownloadManager
class Browser(object):
@ -35,9 +35,11 @@ class Browser(object):
self.page = page
self.address = address
self._driver = BrowserDriver(browser_id, 'browser', address)
self._driver = BrowserDriver(browser_id, 'browser', address, self)
self.id = browser_id
self._frames = {}
self._drivers = {}
# self._drivers = {t: ChromiumDriver(t, 'page', address) for t in self.tabs}
self._connected = False
self._process_id = None
@ -49,18 +51,33 @@ class Browser(object):
self.run_cdp('Target.setDiscoverTargets', discover=True)
self._driver.set_listener('Target.targetDestroyed', self._onTargetDestroyed)
self._driver.set_listener('Target.targetCreated', self._onTargetCreated)
def _get_driver(self, tab_id):
"""获取对应tab id的ChromiumDriver
:param tab_id: 标签页id
:return: ChromiumDriver对象
"""
return self._drivers.pop(tab_id, ChromiumDriver(tab_id, 'page', self.address))
def _onTargetCreated(self, **kwargs):
"""标签页创建时执行"""
if kwargs['targetInfo']['type'] == 'page' and not kwargs['targetInfo']['url'].startswith('devtools://'):
self._drivers[kwargs['targetInfo']['targetId']] = ChromiumDriver(kwargs['targetInfo']['targetId'], 'page',
self.address)
def _onTargetDestroyed(self, **kwargs):
"""标签页关闭时执行"""
tab_id = kwargs['targetId']
self._dl_mgr.clear_tab_info(tab_id)
for item in [(k, i) for k, i in self._frames.items() if i == tab_id]:
self._frames.pop(item[0])
for key in [k for k, i in self._frames.items() if i == tab_id]:
self._frames.pop(key, None)
self._drivers.pop(tab_id, None)
def connect_to_page(self):
"""执行与page相关的逻辑"""
if not self._connected:
self._dl_mgr = BrowserDownloadManager(self)
self._dl_mgr = DownloadManager(self)
self._connected = True
def run_cdp(self, cmd, **cmd_args):
@ -150,4 +167,5 @@ class Browser(object):
break
sleep(.2)
Browser.BROWSERS.pop(self.id)
def _on_quit(self):
Browser.BROWSERS.pop(self.id, None)

View File

@ -5,9 +5,9 @@
"""
from typing import List, Optional, Union
from .chromium_driver import BrowserDriver
from .chromium_driver import BrowserDriver, ChromiumDriver
from .._pages.chromium_page import ChromiumPage
from .._units.download_manager import BrowserDownloadManager
from .._units.download_manager import DownloadManager
class Browser(object):
@ -17,14 +17,17 @@ class Browser(object):
id: str = ...
address: str = ...
_frames: dict = ...
_drivers: dict = ...
_process_id: Optional[int] = ...
_dl_mgr: BrowserDownloadManager = ...
_dl_mgr: DownloadManager = ...
_connected: bool = ...
def __new__(cls, address: str, browser_id: str, page: ChromiumPage): ...
def __init__(self, address: str, browser_id: str, page: ChromiumPage): ...
def _get_driver(self, tab_id: str)->ChromiumDriver: ...
def run_cdp(self, cmd, **cmd_args) -> dict: ...
@property
@ -50,6 +53,10 @@ class Browser(object):
def connect_to_page(self) -> None: ...
def _onTargetCreated(self, **kwargs) -> None: ...
def _onTargetDestroyed(self, **kwargs) -> None: ...
def quit(self) -> None: ...
def _on_quit(self) -> None: ...

View File

@ -211,17 +211,18 @@ class ChromiumDriver(object):
class BrowserDriver(ChromiumDriver):
BROWSERS = {}
def __new__(cls, tab_id, tab_type, address):
def __new__(cls, tab_id, tab_type, address, browser):
if tab_id in cls.BROWSERS:
return cls.BROWSERS[tab_id]
return object.__new__(cls)
def __init__(self, tab_id, tab_type, address):
def __init__(self, tab_id, tab_type, address, browser):
if hasattr(self, '_created'):
return
self._created = True
BrowserDriver.BROWSERS[tab_id] = self
super().__init__(tab_id, tab_type, address)
self.browser = browser
def __repr__(self):
return f"<BrowserDriver {self.id}>"
@ -230,3 +231,7 @@ class BrowserDriver(ChromiumDriver):
r = get(url, headers={'Connection': 'close'})
r.close()
return r
def stop(self):
super().stop()
self.browser._on_quit()

View File

@ -10,6 +10,8 @@ from typing import Union, Callable, Dict, Optional
from requests import Response
from websocket import WebSocket
from .browser import Browser
class GenericAttr(object):
def __init__(self, name: str, tab: ChromiumDriver): ...
@ -58,5 +60,10 @@ class ChromiumDriver(object):
class BrowserDriver(ChromiumDriver):
BROWSERS: Dict[str, ChromiumDriver] = ...
browser: Browser = ...
def __new__(cls, tab_id: str, tab_type: str, address: str, browser: Browser): ...
def __init__(self, tab_id: str, tab_type: str, address: str, browser: Browser): ...
def get(self, url) -> Response: ...

View File

@ -169,7 +169,8 @@ def test_connect(ip, port, timeout=30):
raise BrowserConnectError(f'\n{ip}:{port}浏览器无法链接。\n请确认:\n1、该端口为浏览器\n'
f'2、已添加--remote-allow-origins=*和--remote-debugging-port={port}启动项\n'
f'3、用户文件夹没有和已打开的浏览器冲突\n'
f'4、如为无界面系统请使用headless模式\n'
f'4、如为无界面系统请添加--headless=new参数\n'
f'5、如果是Linux系统可能还要添加--no-sandbox启动参数\n'
f'可使用ChromiumOptions设置端口和用户文件夹路径。')

View File

@ -13,12 +13,17 @@ from .._commons.constants import FRAME_ELEMENT, NoneElement, Settings
from .._commons.keys import keys_to_typing, keyDescriptionForString, keyDefinitions
from .._commons.locator import get_loc
from .._commons.tools import get_usable_path
from .._commons.web import make_absolute_link, get_ele_txt, format_html, is_js_func, location_in_viewport, offset_scroll
from .._commons.web import make_absolute_link, get_ele_txt, format_html, is_js_func, offset_scroll
from .._units.clicker import Clicker
from .._units.element_states import ElementStates, ShadowRootStates
from .._units.ids import Ids, ElementIds
from .._units.locations import Locations
from .._units.scroller import ElementScroller
from .._units.select_element import SelectElement
from .._units.setter import ChromiumElementSetter
from .._units.waiter import ChromiumElementWaiter
from ..errors import ContextLossError, ElementLossError, JavaScriptError, ElementNotFoundError, \
CDPError, NoResourceError, NoRectError, AlertExistsError
from .._units.waiter import ElementWaiter
from ..errors import (ContextLossError, ElementLossError, JavaScriptError, ElementNotFoundError,
CDPError, NoResourceError, AlertExistsError)
class ChromiumElement(DrissionElement):
@ -61,7 +66,7 @@ class ChromiumElement(DrissionElement):
else:
raise ElementLossError
self._ids = ChromiumElementIds(self)
self._ids = ElementIds(self)
doc = self.run_js('return this.ownerDocument;')
self._doc_id = doc['objectId'] if doc else None
@ -138,7 +143,7 @@ class ChromiumElement(DrissionElement):
def states(self):
"""返回用于获取元素状态的对象"""
if self._states is None:
self._states = ChromiumElementStates(self)
self._states = ElementStates(self)
return self._states
@property
@ -178,7 +183,7 @@ class ChromiumElement(DrissionElement):
def scroll(self):
"""用于滚动滚动条的对象"""
if self._scroll is None:
self._scroll = ChromiumElementScroll(self)
self._scroll = ElementScroller(self)
return self._scroll
@property
@ -192,7 +197,7 @@ class ChromiumElement(DrissionElement):
def wait(self):
"""返回用于等待的对象"""
if self._wait is None:
self._wait = ChromiumElementWaiter(self.page, self)
self._wait = ElementWaiter(self.page, self)
return self._wait
@property
@ -202,7 +207,7 @@ class ChromiumElement(DrissionElement):
if self.tag != 'select':
self._select = False
else:
self._select = ChromiumSelect(self)
self._select = SelectElement(self)
return self._select
@ -1078,33 +1083,6 @@ class ChromiumShadowRoot(BaseElement):
return r['backendNodeId']
class Ids(object):
def __init__(self, ele):
self._ele = ele
@property
def node_id(self):
"""返回元素cdp中的node id"""
return self._ele._node_id
@property
def obj_id(self):
"""返回元素js中的object id"""
return self._ele._obj_id
@property
def backend_id(self):
"""返回backend id"""
return self._ele._backend_id
class ChromiumElementIds(Ids):
@property
def doc_id(self):
"""返回所在document的object id"""
return self._ele._doc_id
def find_in_chromium_ele(ele, loc, single=True, timeout=None, relative=True):
"""在chromium元素中查找
:param ele: ChromiumElement对象
@ -1436,536 +1414,6 @@ def send_key(ele, modifier, key):
ele.page.run_cdp('Input.dispatchKeyEvent', **data)
class ChromiumElementStates(object):
def __init__(self, ele):
"""
:param ele: ChromiumElement
"""
self._ele = ele
@property
def is_selected(self):
"""返回元素是否被选择"""
return self._ele.run_js('return this.selected;')
@property
def is_checked(self):
"""返回元素是否被选择"""
return self._ele.run_js('return this.checked;')
@property
def is_displayed(self):
"""返回元素是否显示"""
return not (self._ele.style('visibility') == 'hidden'
or self._ele.run_js('return this.offsetParent === null;')
or self._ele.style('display') == 'none')
@property
def is_enabled(self):
"""返回元素是否可用"""
return not self._ele.run_js('return this.disabled;')
@property
def is_alive(self):
"""返回元素是否仍在DOM中"""
try:
d = self._ele.attrs
return True
except Exception:
return False
@property
def is_in_viewport(self):
"""返回元素是否出现在视口中以元素click_point为判断"""
x, y = self._ele.locations.click_point
return location_in_viewport(self._ele.page, x, y) if x else False
@property
def is_whole_in_viewport(self):
"""返回元素是否整个都在视口内"""
x1, y1 = self._ele.location
w, h = self._ele.size
x2, y2 = x1 + w, y1 + h
return location_in_viewport(self._ele.page, x1, y1) and location_in_viewport(self._ele.page, x2, y2)
@property
def is_covered(self):
"""返回元素是否被覆盖,与是否在视口中无关"""
lx, ly = self._ele.locations.click_point
try:
r = self._ele.page.run_cdp('DOM.getNodeForLocation', x=lx, y=ly)
except CDPError:
return False
if r.get('backendNodeId') != self._ele.ids.backend_id:
return True
return False
@property
def has_rect(self):
"""返回元素是否拥有位置和大小没有返回False有返回大小元组"""
try:
return self._ele.size
except NoRectError:
return False
class ShadowRootStates(object):
def __init__(self, ele):
"""
:param ele: ChromiumElement
"""
self._ele = ele
@property
def is_enabled(self):
"""返回元素是否可用"""
return not self._ele.run_js('return this.disabled;')
@property
def is_alive(self):
"""返回元素是否仍在DOM中"""
try:
self._ele.page.run_cdp('DOM.describeNode', backendNodeId=self._ele.ids.backend_id)
return True
except Exception:
return False
class Locations(object):
def __init__(self, ele):
"""
:param ele: ChromiumElement
"""
self._ele = ele
@property
def location(self):
"""返回元素左上角的绝对坐标"""
cl = self.viewport_location
return self._get_page_coord(cl[0], cl[1])
@property
def midpoint(self):
"""返回元素中间点的绝对坐标"""
cl = self.viewport_midpoint
return self._get_page_coord(cl[0], cl[1])
@property
def click_point(self):
"""返回元素接受点击的点的绝对坐标"""
cl = self.viewport_click_point
return self._get_page_coord(cl[0], cl[1])
@property
def viewport_location(self):
"""返回元素左上角在视口中的坐标"""
m = self._get_viewport_rect('border')
return int(m[0]), int(m[1])
@property
def viewport_midpoint(self):
"""返回元素中间点在视口中的坐标"""
m = self._get_viewport_rect('border')
return int(m[0] + (m[2] - m[0]) // 2), int(m[3] + (m[5] - m[3]) // 2)
@property
def viewport_click_point(self):
"""返回元素接受点击的点视口坐标"""
m = self._get_viewport_rect('padding')
return int(self.viewport_midpoint[0]), int(m[1]) + 1
@property
def screen_location(self):
"""返回元素左上角在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.page.rect.viewport_location
ex, ey = self.viewport_location
pr = self._ele.page.run_js('return window.devicePixelRatio;')
return int((vx + ex) * pr), int((ey + vy) * pr)
@property
def screen_midpoint(self):
"""返回元素中点在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.page.rect.viewport_location
ex, ey = self.viewport_midpoint
pr = self._ele.page.run_js('return window.devicePixelRatio;')
return int((vx + ex) * pr), int((ey + vy) * pr)
@property
def screen_click_point(self):
"""返回元素中点在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.page.rect.viewport_location
ex, ey = self.viewport_click_point
pr = self._ele.page.run_js('return window.devicePixelRatio;')
return int((vx + ex) * pr), int((ey + vy) * pr)
def _get_viewport_rect(self, quad):
"""按照类型返回在可视窗口中的范围
:param quad: 方框类型margin border padding
:return: 四个角坐标大小为0时返回None
"""
return self._ele.page.run_cdp('DOM.getBoxModel', backendNodeId=self._ele.ids.backend_id)['model'][quad]
def _get_page_coord(self, x, y):
"""根据视口坐标获取绝对坐标"""
# js = 'return document.documentElement.scrollLeft+" "+document.documentElement.scrollTop;'
# xy = self._ele.run_js(js)
# sx, sy = xy.split(' ')
r = self._ele.page.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
sx = r['pageX']
sy = r['pageY']
return x + sx, y + sy
class ChromiumScroll(object):
"""用于滚动的对象"""
def __init__(self, ele):
"""
:param ele: 元素对象
"""
self._driver = ele
self.t1 = self.t2 = 'this'
self._wait_complete = False
def _run_js(self, js):
js = js.format(self.t1, self.t2, self.t2)
self._driver.run_js(js)
self._wait_scrolled()
def to_top(self):
"""滚动到顶端,水平位置不变"""
self._run_js('{}.scrollTo({}.scrollLeft, 0);')
def to_bottom(self):
"""滚动到底端,水平位置不变"""
self._run_js('{}.scrollTo({}.scrollLeft, {}.scrollHeight);')
def to_half(self):
"""滚动到垂直中间位置,水平位置不变"""
self._run_js('{}.scrollTo({}.scrollLeft, {}.scrollHeight/2);')
def to_rightmost(self):
"""滚动到最右边,垂直位置不变"""
self._run_js('{}.scrollTo({}.scrollWidth, {}.scrollTop);')
def to_leftmost(self):
"""滚动到最左边,垂直位置不变"""
self._run_js('{}.scrollTo(0, {}.scrollTop);')
def to_location(self, x, y):
"""滚动到指定位置
:param x: 水平距离
:param y: 垂直距离
:return: None
"""
self._run_js(f'{{}}.scrollTo({x}, {y});')
def up(self, pixel=300):
"""向上滚动若干像素,水平位置不变
:param pixel: 滚动的像素
:return: None
"""
pixel = -pixel
self._run_js(f'{{}}.scrollBy(0, {pixel});')
def down(self, pixel=300):
"""向下滚动若干像素,水平位置不变
:param pixel: 滚动的像素
:return: None
"""
self._run_js(f'{{}}.scrollBy(0, {pixel});')
def left(self, pixel=300):
"""向左滚动若干像素,垂直位置不变
:param pixel: 滚动的像素
:return: None
"""
pixel = -pixel
self._run_js(f'{{}}.scrollBy({pixel}, 0);')
def right(self, pixel=300):
"""向右滚动若干像素,垂直位置不变
:param pixel: 滚动的像素
:return: None
"""
self._run_js(f'{{}}.scrollBy({pixel}, 0);')
def _wait_scrolled(self):
if not self._wait_complete:
return
page = self._driver.page if isinstance(self._driver, ChromiumElement) else self._driver
r = page.run_cdp('Page.getLayoutMetrics')
x = r['layoutViewport']['pageX']
y = r['layoutViewport']['pageY']
end_time = perf_counter() + self._driver.page.timeout
while perf_counter() < end_time:
sleep(.1)
r = page.run_cdp('Page.getLayoutMetrics')
x1 = r['layoutViewport']['pageX']
y1 = r['layoutViewport']['pageY']
if x == x1 and y == y1:
break
x = x1
y = y1
class ChromiumElementScroll(ChromiumScroll):
def to_see(self, center=None):
"""滚动页面直到元素可见
:param center: 是否尽量滚动到页面正中为None时如果被遮挡则滚动到页面正中
:return: None
"""
self._driver.page.scroll.to_see(self._driver, center=center)
def to_center(self):
"""元素尽量滚动到视口中间"""
self._driver.page.scroll.to_see(self._driver, center=True)
class ChromiumSelect(object):
"""ChromiumSelect 类专门用于处理 d 模式下 select 标签"""
def __init__(self, ele):
"""
:param ele: select 元素对象
"""
if ele.tag != 'select':
raise TypeError("select方法只能在<select>元素使用。")
self._ele = ele
def __call__(self, text_or_index, timeout=None):
"""选定下拉列表中子元素
:param text_or_index: 根据文本值选或序号择选项若允许多选传入list或tuple可多选
:param timeout: 超时时间不输入默认实用页面超时时间
:return: None
"""
para_type = 'index' if isinstance(text_or_index, int) else 'text'
timeout = timeout if timeout is not None else self._ele.page.timeout
return self._select(text_or_index, para_type, timeout=timeout)
@property
def is_multi(self):
"""返回是否多选表单"""
return self._ele.attr('multiple') is not None
@property
def options(self):
"""返回所有选项元素组成的列表"""
return self._ele.eles('xpath://option')
@property
def selected_option(self):
"""返回第一个被选中的option元素
:return: ChromiumElement对象或None
"""
ele = self._ele.run_js('return this.options[this.selectedIndex];')
return ele
@property
def selected_options(self):
"""返回所有被选中的option元素列表
:return: ChromiumElement对象组成的列表
"""
return [x for x in self.options if x.states.is_selected]
def all(self):
"""全选"""
if not self.is_multi:
raise TypeError("只能在多选菜单执行此操作。")
return self._by_loc('tag:option', 1, False)
def invert(self):
"""反选"""
if not self.is_multi:
raise TypeError("只能对多项选框执行反选。")
change = False
for i in self.options:
change = True
mode = 'false' if i.states.is_selected else 'true'
i.run_js(f'this.selected={mode};')
if change:
self._dispatch_change()
def clear(self):
"""清除所有已选项"""
if not self.is_multi:
raise TypeError("只能在多选菜单执行此操作。")
return self._by_loc('tag:option', 1, True)
def by_text(self, text, timeout=None):
"""此方法用于根据text值选择项。当元素是多选列表时可以接收list或tuple
:param text: text属性值传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
"""
return self._select(text, 'text', False, timeout)
def by_value(self, value, timeout=None):
"""此方法用于根据value值选择项。当元素是多选列表时可以接收list或tuple
:param value: value属性值传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
"""
return self._select(value, 'value', False, timeout)
def by_index(self, index, timeout=None):
"""此方法用于根据index值选择项。当元素是多选列表时可以接收list或tuple
:param index: 序号0开始传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
"""
return self._select(index, 'index', False, timeout)
def by_loc(self, loc, timeout=None):
"""用定位符选择指定的项
:param loc: 定位符
:param timeout: 超时时间
:return: 是否选择成功
"""
return self._by_loc(loc, timeout)
def cancel_by_text(self, text, timeout=None):
"""此方法用于根据text值取消选择项。当元素是多选列表时可以接收list或tuple
:param text: 文本传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
"""
return self._select(text, 'text', True, timeout)
def cancel_by_value(self, value, timeout=None):
"""此方法用于根据value值取消选择项。当元素是多选列表时可以接收list或tuple
:param value: value属性值传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
"""
return self._select(value, 'value', True, timeout)
def cancel_by_index(self, index, timeout=None):
"""此方法用于根据index值取消选择项。当元素是多选列表时可以接收list或tuple
:param index: 序号0开始传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
"""
return self._select(index, 'index', True, timeout)
def cancel_by_loc(self, loc, timeout=None):
"""用定位符取消选择指定的项
:param loc: 定位符
:param timeout: 超时时间
:return: 是否选择成功
"""
return self._by_loc(loc, timeout, True)
def _by_loc(self, loc, timeout=None, cancel=False):
"""用定位符取消选择指定的项
:param loc: 定位符
:param timeout: 超时时间
:param cancel: 是否取消选择
:return: 是否选择成功
"""
eles = self._ele.eles(loc, timeout)
if not eles:
return False
mode = 'false' if cancel else 'true'
if self.is_multi:
for ele in eles:
ele.run_js(f'this.selected={mode};')
self._dispatch_change()
return True
eles[0].run_js(f'this.selected={mode};')
self._dispatch_change()
return True
def _select(self, condition, para_type='text', cancel=False, timeout=None):
"""选定或取消选定下拉列表中子元素
:param condition: 根据文本值选或序号择选项若允许多选传入list或tuple可多选
:param para_type: 参数类型可选 'text''value''index'
:param cancel: 是否取消选择
:return: 是否选择成功
"""
if not self.is_multi and isinstance(condition, (list, tuple)):
raise TypeError('单选列表只能传入str格式。')
mode = 'false' if cancel else 'true'
timeout = timeout if timeout is not None else self._ele.page.timeout
condition = set(condition) if isinstance(condition, (list, tuple)) else {condition}
if para_type in ('text', 'value'):
return self._text_value([str(i) for i in condition], para_type, mode, timeout)
elif para_type == 'index':
return self._index(condition, mode, timeout)
def _text_value(self, condition, para_type, mode, timeout):
"""执行text和value搜索
:param condition: 条件set
:param para_type: 参数类型可选 'text''value'
:param mode: 'true' 'false'
:param timeout: 超时时间
:return: 是否选择成功
"""
ok = False
text_len = len(condition)
eles = []
end_time = perf_counter() + timeout
while perf_counter() < end_time:
if para_type == 'text':
eles = [i for i in self.options if i.text in condition]
elif para_type == 'value':
eles = [i for i in self.options if i.attr('value') in condition]
if len(eles) >= text_len:
ok = True
break
if ok:
for i in eles:
i.run_js(f'this.selected={mode};')
self._dispatch_change()
return True
return False
def _index(self, condition, mode, timeout):
"""执行index搜索
:param condition: 条件set
:param mode: 'true' 'false'
:param timeout: 超时时间
:return: 是否选择成功
"""
ok = False
condition = [int(i) for i in condition]
text_len = max(condition)
end_time = perf_counter() + timeout
while perf_counter() < end_time:
if len(self.options) >= text_len:
ok = True
break
if ok:
eles = self.options
for i in condition:
eles[i - 1].run_js(f'this.selected={mode};')
self._dispatch_change()
return True
return False
def _dispatch_change(self):
"""触发修改动作"""
self._ele.run_js('this.dispatchEvent(new UIEvent("change"));')
class Pseudo(object):
def __init__(self, ele):
"""

View File

@ -6,7 +6,6 @@
from pathlib import Path
from typing import Union, Tuple, List, Any
from .._units.clicker import Clicker
from .._base.base import DrissionElement, BaseElement
from .._commons.constants import NoneElement
from .._elements.session_element import SessionElement
@ -14,8 +13,14 @@ from .._pages.chromium_base import ChromiumBase
from .._pages.chromium_frame import ChromiumFrame
from .._pages.chromium_page import ChromiumPage
from .._pages.web_page import WebPage
from .._units.clicker import Clicker
from .._units.element_states import ShadowRootStates, ElementStates
from .._units.ids import Ids, ElementIds
from .._units.locations import Locations
from .._units.scroller import ElementScroller
from .._units.select_element import SelectElement
from .._units.setter import ChromiumElementSetter
from .._units.waiter import ChromiumElementWaiter
from .._units.waiter import ElementWaiter
class ChromiumElement(DrissionElement):
@ -29,14 +34,14 @@ class ChromiumElement(DrissionElement):
self._obj_id: str = ...
self._backend_id: str = ...
self._doc_id: str = ...
self._ids: ChromiumElementIds = ...
self._scroll: ChromiumElementScroll = ...
self._ids: ElementIds = ...
self._scroll: ElementScroller = ...
self._clicker: Clicker = ...
self._select: ChromiumSelect = ...
self._wait: ChromiumElementWaiter = ...
self._select: SelectElement = ...
self._wait: ElementWaiter = ...
self._locations: Locations = ...
self._set: ChromiumElementSetter = ...
self._states: ChromiumElementStates = ...
self._states: ElementStates = ...
self._pseudo: Pseudo = ...
def __repr__(self) -> str: ...
@ -65,7 +70,7 @@ class ChromiumElement(DrissionElement):
# -----------------d模式独有属性-------------------
@property
def ids(self) -> ChromiumElementIds: ...
def ids(self) -> ElementIds: ...
@property
def size(self) -> Tuple[int, int]: ...
@ -74,7 +79,7 @@ class ChromiumElement(DrissionElement):
def set(self) -> ChromiumElementSetter: ...
@property
def states(self) -> ChromiumElementStates: ...
def states(self) -> ElementStates: ...
@property
def location(self) -> Tuple[int, int]: ...
@ -92,7 +97,7 @@ class ChromiumElement(DrissionElement):
def sr(self) -> Union[None, ChromiumShadowRoot]: ...
@property
def scroll(self) -> ChromiumElementScroll: ...
def scroll(self) -> ElementScroller: ...
@property
def click(self) -> Clicker: ...
@ -145,10 +150,10 @@ class ChromiumElement(DrissionElement):
ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ...
@property
def wait(self) -> ChromiumElementWaiter: ...
def wait(self) -> ElementWaiter: ...
@property
def select(self) -> ChromiumSelect: ...
def select(self) -> SelectElement: ...
def check(self, uncheck: bool = False) -> None: ...
@ -213,38 +218,6 @@ class ChromiumElement(DrissionElement):
def _get_ele_path(self, mode: str) -> str: ...
class ChromiumElementStates(object):
def __init__(self, ele: ChromiumElement):
self._ele: ChromiumElement = ...
@property
def is_selected(self) -> bool: ...
@property
def is_checked(self) -> bool: ...
@property
def is_displayed(self) -> bool: ...
@property
def is_enabled(self) -> bool: ...
@property
def is_alive(self) -> bool: ...
@property
def is_in_viewport(self) -> bool: ...
@property
def is_whole_in_viewport(self) -> bool: ...
@property
def is_covered(self) -> bool: ...
@property
def has_rect(self) -> Union[bool, Tuple[int, int]]: ...
class ChromiumShadowRoot(BaseElement):
def __init__(self,
@ -330,25 +303,6 @@ class ChromiumShadowRoot(BaseElement):
def _get_backend_id(self, node_id: str) -> str: ...
class Ids(object):
def __init__(self, ele: Union[ChromiumElement, ChromiumShadowRoot]):
self._ele: Union[ChromiumElement, ChromiumShadowRoot] = ...
@property
def node_id(self) -> str: ...
@property
def obj_id(self) -> str: ...
@property
def backend_id(self) -> str: ...
class ChromiumElementIds(Ids):
@property
def doc_id(self) -> str: ...
def find_in_chromium_ele(ele: ChromiumElement,
loc: Union[str, Tuple[str, str]],
single: bool = True,
@ -393,150 +347,6 @@ def send_enter(ele: ChromiumElement) -> None: ...
def send_key(ele: ChromiumElement, modifier: int, key: str) -> None: ...
class ShadowRootStates(object):
def __init__(self, ele: ChromiumShadowRoot):
"""
:param ele: ChromiumElement
"""
self._ele: ChromiumShadowRoot = ...
@property
def is_enabled(self) -> bool: ...
@property
def is_alive(self) -> bool: ...
class Locations(object):
def __init__(self, ele: ChromiumElement):
self._ele: ChromiumElement = ...
@property
def location(self) -> Tuple[int, int]: ...
@property
def midpoint(self) -> Tuple[int, int]: ...
@property
def click_point(self) -> Tuple[int, int]: ...
@property
def viewport_location(self) -> Tuple[int, int]: ...
@property
def viewport_midpoint(self) -> Tuple[int, int]: ...
@property
def viewport_click_point(self) -> Tuple[int, int]: ...
@property
def screen_location(self) -> Tuple[int, int]: ...
@property
def screen_midpoint(self) -> Tuple[int, int]: ...
@property
def screen_click_point(self) -> Tuple[int, int]: ...
def _get_viewport_rect(self, quad: str) -> Union[list, None]: ...
def _get_page_coord(self, x: int, y: int) -> Tuple[int, int]: ...
class ChromiumScroll(object):
def __init__(self, page_or_ele: Union[ChromiumBase, ChromiumElement, ChromiumFrame]):
self.t1: str = ...
self.t2: str = ...
self._driver: Union[ChromiumPage, ChromiumElement, ChromiumFrame] = ...
self._wait_complete: bool = ...
def _run_js(self, js: str): ...
def to_top(self) -> None: ...
def to_bottom(self) -> None: ...
def to_half(self) -> None: ...
def to_rightmost(self) -> None: ...
def to_leftmost(self) -> None: ...
def to_location(self, x: int, y: int) -> None: ...
def up(self, pixel: int = 300) -> None: ...
def down(self, pixel: int = 300) -> None: ...
def left(self, pixel: int = 300) -> None: ...
def right(self, pixel: int = 300) -> None: ...
def _wait_scrolled(self) -> None: ...
class ChromiumElementScroll(ChromiumScroll):
def to_see(self, center: Union[bool, None] = None) -> None: ...
def to_center(self) -> None: ...
class ChromiumSelect(object):
def __init__(self, ele: ChromiumElement):
self._ele: ChromiumElement = ...
def __call__(self, text_or_index: Union[str, int, list, tuple], timeout: float = None) -> bool: ...
@property
def is_multi(self) -> bool: ...
@property
def options(self) -> List[ChromiumElement]: ...
@property
def selected_option(self) -> Union[ChromiumElement, None]: ...
@property
def selected_options(self) -> List[ChromiumElement]: ...
def clear(self) -> None: ...
def all(self) -> None: ...
def by_text(self, text: Union[str, list, tuple], timeout: float = None) -> bool: ...
def by_value(self, value: Union[str, list, tuple], timeout: float = None) -> bool: ...
def by_index(self, index: Union[int, list, tuple], timeout: float = None) -> bool: ...
def by_loc(self, loc: Union[str, Tuple[str, str]], timeout: float = None) -> bool: ...
def cancel_by_text(self, text: Union[str, list, tuple], timeout: float = None) -> bool: ...
def cancel_by_value(self, value: Union[str, list, tuple], timeout: float = None) -> bool: ...
def cancel_by_index(self, index: Union[int, list, tuple], timeout: float = None) -> bool: ...
def cancel_by_loc(self, loc: Union[str, Tuple[str, str]], timeout: float = None) -> bool: ...
def invert(self) -> None: ...
def _by_loc(self, loc: Union[str, Tuple[str, str]], timeout: float = None, cancel: bool = False) -> bool: ...
def _select(self,
condition: Union[str, int, list, tuple] = None,
para_type: str = 'text',
cancel: bool = False,
timeout: float = None) -> bool: ...
def _text_value(self, condition: Union[list, set], para_type: str, mode: str, timeout: float) -> bool: ...
def _index(self, condition: set, mode: str, timeout: float) -> bool: ...
def _dispatch_change(self) -> None: ...
class Pseudo(object):
def __init__(self, ele: ChromiumElement):
self._ele: ChromiumElement = ...

View File

@ -10,19 +10,19 @@ from re import findall
from threading import Thread
from time import perf_counter, sleep
from .._units.scroller import PageScroller
from .._base.base import BasePage
from .._base.chromium_driver import ChromiumDriver
from .._commons.constants import ERROR, NoneElement
from .._commons.locator import get_loc
from .._commons.tools import get_usable_path
from .._commons.web import location_in_viewport
from .._elements.chromium_element import ChromiumScroll, ChromiumElement, run_js, make_chromium_ele
from .._elements.chromium_element import ChromiumElement, run_js, make_chromium_ele
from .._elements.session_element import make_session_ele
from .._units.action_chains import ActionChains
from .._units.network_listener import NetworkListener
from .._units.screencast import Screencast
from .._units.setter import ChromiumBaseSetter
from .._units.waiter import ChromiumBaseWaiter
from .._units.waiter import BaseWaiter
from ..errors import (ContextLossError, ElementLossError, CDPError, TabClosedError, NoRectError, AlertExistsError,
GetDocumentError)
@ -95,7 +95,7 @@ class ChromiumBase(BasePage):
:return: None
"""
self._is_loading = True
self._driver = ChromiumDriver(tab_id=tab_id, tab_type='page', address=self.address)
self._driver = self.browser._get_driver(tab_id)
self._alert = Alert()
self._driver.set_listener('Page.javascriptDialogOpening', self._on_alert_open)
self._driver.set_listener('Page.javascriptDialogClosed', self._on_alert_close)
@ -104,6 +104,11 @@ class ChromiumBase(BasePage):
self._driver.call_method('Page.enable')
self._driver.call_method('Emulation.setFocusEmulationEnabled', enabled=True)
r = self.run_cdp('Page.getFrameTree')
for i in findall(r"'id': '(.*?)'", str(r)):
self.browser._frames[i] = self.tab_id
self._frame_id = r['frameTree']['frame']['id']
self._driver.set_listener('Page.frameStartedLoading', self._onFrameStartedLoading)
self._driver.set_listener('Page.frameNavigated', self._onFrameNavigated)
self._driver.set_listener('Page.domContentEventFired', self._onDomContentEventFired)
@ -112,11 +117,6 @@ class ChromiumBase(BasePage):
self._driver.set_listener('Page.frameAttached', self._onFrameAttached)
self._driver.set_listener('Page.frameDetached', self._onFrameDetached)
r = self.run_cdp('Page.getFrameTree')
for i in findall(r"'id': '(.*?)'", str(r)):
self.browser._frames[i] = self.tab_id
self._frame_id = r['frameTree']['frame']['id']
def _get_document(self):
if self._is_reading:
return
@ -140,10 +140,7 @@ class ChromiumBase(BasePage):
self._is_reading = False
def _onFrameDetached(self, **kwargs):
try:
self.browser._frames.pop(kwargs['frameId'])
except KeyError:
pass
self.browser._frames.pop(kwargs['frameId'], None)
def _onFrameAttached(self, **kwargs):
self.browser._frames[kwargs['frameId']] = self.tab_id
@ -329,7 +326,7 @@ class ChromiumBase(BasePage):
"""返回用于滚动滚动条的对象"""
self.wait.load_complete()
if self._scroll is None:
self._scroll = ChromiumPageScroll(self)
self._scroll = PageScroller(self)
return self._scroll
@property
@ -346,7 +343,7 @@ class ChromiumBase(BasePage):
def wait(self):
"""返回用于等待的对象"""
if self._wait is None:
self._wait = ChromiumBaseWaiter(self)
self._wait = BaseWaiter(self)
return self._wait
@property
@ -974,46 +971,6 @@ class ChromiumBase(BasePage):
return str(path.absolute())
class ChromiumPageScroll(ChromiumScroll):
def __init__(self, page):
"""
:param page: 页面对象
"""
super().__init__(page)
self.t1 = 'window'
self.t2 = 'document.documentElement'
def to_see(self, loc_or_ele, center=None):
"""滚动页面直到元素可见
:param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串
:param center: 是否尽量滚动到页面正中为None时如果被遮挡则滚动到页面正中
:return: None
"""
ele = self._driver._ele(loc_or_ele)
self._to_see(ele, center)
def _to_see(self, ele, center):
"""执行滚动页面直到元素可见
:param ele: 元素对象
:param center: 是否尽量滚动到页面正中为None时如果被遮挡则滚动到页面正中
:return: None
"""
txt = 'true' if center else 'false'
ele.run_js(f'this.scrollIntoViewIfNeeded({txt});')
if center or (center is not False and ele.states.is_covered):
ele.run_js('''function getWindowScrollTop() {var scroll_top = 0;
if (document.documentElement && document.documentElement.scrollTop) {
scroll_top = document.documentElement.scrollTop;
} else if (document.body) {scroll_top = document.body.scrollTop;}
return scroll_top;}
const { top, height } = this.getBoundingClientRect();
const elCenter = top + height / 2;
const center = window.innerHeight / 2;
window.scrollTo({top: getWindowScrollTop() - (center - elCenter),
behavior: 'instant'});''')
self._wait_scrolled()
class Timeout(object):
"""用于保存d模式timeout信息的类"""

View File

@ -10,15 +10,16 @@ from .._base.base import BasePage
from .._base.browser import Browser
from .._base.chromium_driver import ChromiumDriver
from .._commons.constants import NoneElement
from .._elements.chromium_element import ChromiumElement, ChromiumScroll
from .._elements.chromium_element import ChromiumElement
from .._elements.session_element import SessionElement
from .._pages.chromium_frame import ChromiumFrame
from .._pages.chromium_page import ChromiumPage
from .._units.action_chains import ActionChains
from .._units.network_listener import NetworkListener
from .._units.screencast import Screencast
from .._units.scroller import Scroller, PageScroller
from .._units.setter import ChromiumBaseSetter
from .._units.waiter import ChromiumBaseWaiter
from .._units.waiter import BaseWaiter
class ChromiumBase(BasePage):
@ -37,12 +38,12 @@ class ChromiumBase(BasePage):
self._first_run: bool = ...
self._is_loading: bool = ...
self._page_load_strategy: str = ...
self._scroll: ChromiumScroll = ...
self._scroll: Scroller = ...
self._url: str = ...
self._root_id: str = ...
self._debug: bool = ...
self._upload_list: list = ...
self._wait: ChromiumBaseWaiter = ...
self._wait: BaseWaiter = ...
self._set: ChromiumBaseSetter = ...
self._screencast: Screencast = ...
self._actions: ActionChains = ...
@ -137,7 +138,7 @@ class ChromiumBase(BasePage):
def user_agent(self) -> str: ...
@property
def scroll(self) -> ChromiumPageScroll: ...
def scroll(self) -> PageScroller: ...
@property
def timeouts(self) -> Timeout: ...
@ -146,7 +147,7 @@ class ChromiumBase(BasePage):
def upload_list(self) -> list: ...
@property
def wait(self) -> ChromiumBaseWaiter: ...
def wait(self) -> BaseWaiter: ...
@property
def set(self) -> ChromiumBaseSetter: ...
@ -247,14 +248,6 @@ class ChromiumBase(BasePage):
timeout: float = None) -> Union[bool, None]: ...
class ChromiumPageScroll(ChromiumScroll):
def __init__(self, page: ChromiumBase): ...
def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement], center: Union[bool, None] = None) -> None: ...
def _to_see(self, ele: ChromiumElement, center: Union[bool, None]) -> None: ...
class Timeout(object):
def __init__(self, page: ChromiumBase, implicit=None, page_load=None, script=None):

View File

@ -8,10 +8,10 @@ from re import search, findall
from threading import Thread
from time import sleep, perf_counter
from requests import get
from .._elements.chromium_element import ChromiumElement
from .._pages.chromium_base import ChromiumBase, ChromiumPageScroll
from .._pages.chromium_base import ChromiumBase
from .._units.ids import FrameIds
from .._units.scroller import FrameScroller
from .._units.setter import ChromiumFrameSetter
from .._units.waiter import FrameWaiter
from ..errors import ContextLossError, ElementLossError, GetDocumentError
@ -40,7 +40,7 @@ class ChromiumFrame(ChromiumBase):
self._backend_id = ele.ids.backend_id
self._frame_ele = ele
self._states = None
self._ids = ChromiumFrameIds(self)
self._ids = FrameIds(self)
if self._is_inner_frame():
self._is_diff_domain = False
@ -329,7 +329,7 @@ class ChromiumFrame(ChromiumBase):
@property
def scroll(self):
"""返回用于等待的对象"""
return ChromiumFrameScroll(self)
return FrameScroller(self)
@property
def set(self):
@ -658,47 +658,3 @@ class ChromiumFrame(ChromiumBase):
while self.is_alive:
sleep(1)
self.driver.stop()
class ChromiumFrameIds(object):
def __init__(self, frame):
self._frame = frame
@property
def tab_id(self):
"""返回当前标签页id"""
return self._frame._tab_id
@property
def backend_id(self):
"""返回cdp中的node id"""
return self._frame._backend_id
@property
def obj_id(self):
"""返回frame元素的object id"""
return self._frame.frame_ele.ids.obj_id
@property
def node_id(self):
"""返回cdp中的node id"""
return self._frame.frame_ele.ids.node_id
class ChromiumFrameScroll(ChromiumPageScroll):
def __init__(self, frame):
"""
:param frame: ChromiumFrame对象
"""
self._driver = frame.doc_ele
self.t1 = self.t2 = 'this.documentElement'
self._wait_complete = False
def to_see(self, loc_or_ele, center=None):
"""滚动页面直到元素可见
:param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串
:param center: 是否尽量滚动到页面正中为None时如果被遮挡则滚动到页面正中
:return: None
"""
ele = loc_or_ele if isinstance(loc_or_ele, ChromiumElement) else self._driver._ele(loc_or_ele)
self._to_see(ele, center)

View File

@ -6,11 +6,15 @@
from pathlib import Path
from typing import Union, Tuple, List, Any
from .chromium_base import ChromiumBase, ChromiumPageScroll
from .chromium_base import ChromiumBase
from .chromium_page import ChromiumPage
from .chromium_tab import ChromiumTab
from .web_page import WebPage
from .._elements.chromium_element import ChromiumElement, Locations, ChromiumElementStates
from .._elements.chromium_element import ChromiumElement
from .._units.element_states import ElementStates
from .._units.ids import FrameIds
from .._units.locations import Locations
from .._units.scroller import FrameScroller
from .._units.setter import ChromiumFrameSetter
from .._units.waiter import FrameWaiter
@ -28,8 +32,8 @@ class ChromiumFrame(ChromiumBase):
self._doc_ele: ChromiumElement = ...
self._is_diff_domain: bool = ...
self.doc_ele: ChromiumElement = ...
self._states: ChromiumElementStates = ...
self._ids: ChromiumFrameIds = ...
self._states: ElementStates = ...
self._ids: FrameIds = ...
def __call__(self,
loc_or_str: Union[Tuple[str, str], str],
@ -57,7 +61,7 @@ class ChromiumFrame(ChromiumBase):
def page(self) -> Union[ChromiumPage, WebPage]: ...
@property
def ids(self) -> ChromiumFrameIds: ...
def ids(self) -> FrameIds: ...
@property
def frame_ele(self) -> ChromiumElement: ...
@ -111,13 +115,13 @@ class ChromiumFrame(ChromiumBase):
def is_alive(self) -> bool: ...
@property
def scroll(self) -> ChromiumFrameScroll: ...
def scroll(self) -> FrameScroller: ...
@property
def set(self) -> ChromiumFrameSetter: ...
@property
def states(self) -> ChromiumElementStates: ...
def states(self) -> ElementStates: ...
@property
def wait(self) -> FrameWaiter: ...
@ -197,26 +201,3 @@ class ChromiumFrame(ChromiumBase):
timeout: float = None) -> Union[bool, None]: ...
def _is_inner_frame(self) -> bool: ...
class ChromiumFrameIds(object):
def __init__(self, frame: ChromiumFrame):
self._frame: ChromiumFrame = ...
@property
def tab_id(self) -> str: ...
@property
def backend_id(self) -> str: ...
@property
def obj_id(self) -> str: ...
@property
def node_id(self) -> str: ...
class ChromiumFrameScroll(ChromiumPageScroll):
def __init__(self, frame: ChromiumFrame) -> None: ...
def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement], center: Union[None, bool] = None) -> None: ...

View File

@ -14,8 +14,8 @@ from .._configs.chromium_options import ChromiumOptions
from .._pages.chromium_base import ChromiumBase, Timeout
from .._pages.chromium_tab import ChromiumTab
from .._units.setter import ChromiumPageSetter
from .._units.tab_rect import ChromiumTabRect
from .._units.waiter import ChromiumPageWaiter
from .._units.tab_rect import TabRect
from .._units.waiter import PageWaiter
from ..errors import BrowserConnectError
@ -127,14 +127,14 @@ class ChromiumPage(ChromiumBase):
@property
def rect(self):
if self._rect is None:
self._rect = ChromiumTabRect(self)
self._rect = TabRect(self)
return self._rect
@property
def wait(self):
"""返回用于等待的对象"""
if self._wait is None:
self._wait = ChromiumPageWaiter(self)
self._wait = PageWaiter(self)
return self._wait
def get_tab(self, tab_id=None):

View File

@ -10,8 +10,8 @@ from .._configs.chromium_options import ChromiumOptions
from .._pages.chromium_base import ChromiumBase
from .._pages.chromium_tab import ChromiumTab
from .._units.setter import ChromiumPageSetter
from .._units.tab_rect import ChromiumTabRect
from .._units.waiter import ChromiumPageWaiter
from .._units.tab_rect import TabRect
from .._units.waiter import PageWaiter
class ChromiumPage(ChromiumBase):
@ -23,7 +23,7 @@ class ChromiumPage(ChromiumBase):
self._driver_options: ChromiumOptions = ...
self._main_tab: str = ...
self._browser: Browser = ...
self._rect: Optional[ChromiumTabRect] = ...
self._rect: Optional[TabRect] = ...
def _handle_options(self, addr_or_opts: Union[str, ChromiumOptions]) -> str: ...
@ -41,10 +41,10 @@ class ChromiumPage(ChromiumBase):
def tabs(self) -> List[str]: ...
@property
def rect(self) -> ChromiumTabRect: ...
def rect(self) -> TabRect: ...
@property
def wait(self) -> ChromiumPageWaiter: ...
def wait(self) -> PageWaiter: ...
@property
def main_tab(self) -> str: ...

View File

@ -10,8 +10,8 @@ from .._commons.web import set_session_cookies, set_browser_cookies
from .._pages.chromium_base import ChromiumBase
from .._pages.session_page import SessionPage
from .._units.setter import TabSetter, WebPageTabSetter
from .._units.tab_rect import ChromiumTabRect
from .._units.waiter import ChromiumTabWaiter
from .._units.tab_rect import TabRect
from .._units.waiter import TabWaiter
class ChromiumTab(ChromiumBase):
@ -48,7 +48,7 @@ class ChromiumTab(ChromiumBase):
def rect(self):
"""返回获取窗口坐标和大小的对象"""
if self._rect is None:
self._rect = ChromiumTabRect(self)
self._rect = TabRect(self)
return self._rect
@property
@ -62,7 +62,7 @@ class ChromiumTab(ChromiumBase):
def wait(self):
"""返回用于等待的对象"""
if self._wait is None:
self._wait = ChromiumTabWaiter(self)
self._wait = TabWaiter(self)
return self._wait

View File

@ -17,7 +17,7 @@ from .._base.browser import Browser
from .._elements.chromium_element import ChromiumElement
from .._elements.session_element import SessionElement
from .._units.setter import TabSetter, WebPageTabSetter
from .._units.waiter import ChromiumTabWaiter
from .._units.waiter import TabWaiter
class ChromiumTab(ChromiumBase):
@ -41,7 +41,7 @@ class ChromiumTab(ChromiumBase):
def set(self) -> TabSetter: ...
@property
def wait(self) -> ChromiumTabWaiter: ...
def wait(self) -> TabWaiter: ...
class WebPageTab(SessionPage, ChromiumTab):

View File

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

View File

@ -11,7 +11,7 @@ from time import sleep, perf_counter
from .._commons.tools import get_usable_path
class BrowserDownloadManager(object):
class DownloadManager(object):
def __init__(self, browser):
"""

View File

@ -5,7 +5,7 @@ from .._base.browser import Browser
from .._pages.chromium_page import ChromiumPage
class BrowserDownloadManager(object):
class DownloadManager(object):
_browser: Browser = ...
_page: ChromiumPage = ...
_missions: Dict[str, DownloadMission] = ...
@ -56,7 +56,7 @@ class TabDownloadSettings(object):
class DownloadMission(object):
tab_id: str = ...
_mgr: BrowserDownloadManager = ...
_mgr: DownloadManager = ...
url: str = ...
id: str = ...
path: str = ...
@ -68,7 +68,7 @@ class DownloadMission(object):
save_path: str = ...
_is_done: bool = ...
def __init__(self, mgr: BrowserDownloadManager, tab_id: str, _id: str, path: str, name: str, url: str,
def __init__(self, mgr: DownloadManager, tab_id: str, _id: str, path: str, name: str, url: str,
save_path: str): ...
@property

View File

@ -0,0 +1,104 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
"""
from .._commons.web import location_in_viewport
from ..errors import CDPError, NoRectError
class ElementStates(object):
def __init__(self, ele):
"""
:param ele: ChromiumElement
"""
self._ele = ele
@property
def is_selected(self):
"""返回元素是否被选择"""
return self._ele.run_js('return this.selected;')
@property
def is_checked(self):
"""返回元素是否被选择"""
return self._ele.run_js('return this.checked;')
@property
def is_displayed(self):
"""返回元素是否显示"""
return not (self._ele.style('visibility') == 'hidden'
or self._ele.run_js('return this.offsetParent === null;')
or self._ele.style('display') == 'none')
@property
def is_enabled(self):
"""返回元素是否可用"""
return not self._ele.run_js('return this.disabled;')
@property
def is_alive(self):
"""返回元素是否仍在DOM中"""
try:
d = self._ele.attrs
return True
except Exception:
return False
@property
def is_in_viewport(self):
"""返回元素是否出现在视口中以元素click_point为判断"""
x, y = self._ele.locations.click_point
return location_in_viewport(self._ele.page, x, y) if x else False
@property
def is_whole_in_viewport(self):
"""返回元素是否整个都在视口内"""
x1, y1 = self._ele.location
w, h = self._ele.size
x2, y2 = x1 + w, y1 + h
return location_in_viewport(self._ele.page, x1, y1) and location_in_viewport(self._ele.page, x2, y2)
@property
def is_covered(self):
"""返回元素是否被覆盖,与是否在视口中无关"""
lx, ly = self._ele.locations.click_point
try:
r = self._ele.page.run_cdp('DOM.getNodeForLocation', x=lx, y=ly)
except CDPError:
return False
if r.get('backendNodeId') != self._ele.ids.backend_id:
return True
return False
@property
def has_rect(self):
"""返回元素是否拥有位置和大小没有返回False有返回四个角在页面中坐标组成的列表"""
try:
return self._ele.locations.rect
except NoRectError:
return False
class ShadowRootStates(object):
def __init__(self, ele):
"""
:param ele: ChromiumElement
"""
self._ele = ele
@property
def is_enabled(self):
"""返回元素是否可用"""
return not self._ele.run_js('return this.disabled;')
@property
def is_alive(self):
"""返回元素是否仍在DOM中"""
try:
self._ele.page.run_cdp('DOM.describeNode', backendNodeId=self._ele.ids.backend_id)
return True
except Exception:
return False

View File

@ -0,0 +1,54 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
"""
from typing import Union, Tuple, List
from .._elements.chromium_element import ChromiumShadowRoot, ChromiumElement
class ElementStates(object):
def __init__(self, ele: ChromiumElement):
self._ele: ChromiumElement = ...
@property
def is_selected(self) -> bool: ...
@property
def is_checked(self) -> bool: ...
@property
def is_displayed(self) -> bool: ...
@property
def is_enabled(self) -> bool: ...
@property
def is_alive(self) -> bool: ...
@property
def is_in_viewport(self) -> bool: ...
@property
def is_whole_in_viewport(self) -> bool: ...
@property
def is_covered(self) -> bool: ...
@property
def has_rect(self) -> Union[bool, List[Tuple[float, float]]]: ...
class ShadowRootStates(object):
def __init__(self, ele: ChromiumShadowRoot):
"""
:param ele: ChromiumElement
"""
self._ele: ChromiumShadowRoot = ...
@property
def is_enabled(self) -> bool: ...
@property
def is_alive(self) -> bool: ...

View File

@ -0,0 +1,57 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
"""
class Ids(object):
def __init__(self, ele):
self._ele = ele
@property
def node_id(self):
"""返回元素cdp中的node id"""
return self._ele._node_id
@property
def obj_id(self):
"""返回元素js中的object id"""
return self._ele._obj_id
@property
def backend_id(self):
"""返回backend id"""
return self._ele._backend_id
class ElementIds(Ids):
@property
def doc_id(self):
"""返回所在document的object id"""
return self._ele._doc_id
class FrameIds(object):
def __init__(self, frame):
self._frame = frame
@property
def tab_id(self):
"""返回当前标签页id"""
return self._frame._tab_id
@property
def backend_id(self):
"""返回cdp中的node id"""
return self._frame._backend_id
@property
def obj_id(self):
"""返回frame元素的object id"""
return self._frame.frame_ele.ids.obj_id
@property
def node_id(self):
"""返回cdp中的node id"""
return self._frame.frame_ele.ids.node_id

View File

@ -0,0 +1,45 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
"""
from typing import Union
from .._pages.chromium_frame import ChromiumFrame
from .._elements.chromium_element import ChromiumElement, ChromiumShadowRoot
class Ids(object):
def __init__(self, ele: Union[ChromiumElement, ChromiumShadowRoot]):
self._ele: Union[ChromiumElement, ChromiumShadowRoot] = ...
@property
def node_id(self) -> str: ...
@property
def obj_id(self) -> str: ...
@property
def backend_id(self) -> str: ...
class ElementIds(Ids):
@property
def doc_id(self) -> str: ...
class FrameIds(object):
def __init__(self, frame: ChromiumFrame):
self._frame: ChromiumFrame = ...
@property
def tab_id(self) -> str: ...
@property
def backend_id(self) -> str: ...
@property
def obj_id(self) -> str: ...
@property
def node_id(self) -> str: ...

View File

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

View File

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

View File

@ -0,0 +1,171 @@
# -*- coding:utf-8 -*-
from time import sleep, perf_counter
class Scroller(object):
"""用于滚动的对象"""
def __init__(self, ele):
"""
:param ele: 元素对象
"""
self._driver = ele
self.t1 = self.t2 = 'this'
self._wait_complete = False
def _run_js(self, js):
js = js.format(self.t1, self.t2, self.t2)
self._driver.run_js(js)
self._wait_scrolled()
def to_top(self):
"""滚动到顶端,水平位置不变"""
self._run_js('{}.scrollTo({}.scrollLeft, 0);')
def to_bottom(self):
"""滚动到底端,水平位置不变"""
self._run_js('{}.scrollTo({}.scrollLeft, {}.scrollHeight);')
def to_half(self):
"""滚动到垂直中间位置,水平位置不变"""
self._run_js('{}.scrollTo({}.scrollLeft, {}.scrollHeight/2);')
def to_rightmost(self):
"""滚动到最右边,垂直位置不变"""
self._run_js('{}.scrollTo({}.scrollWidth, {}.scrollTop);')
def to_leftmost(self):
"""滚动到最左边,垂直位置不变"""
self._run_js('{}.scrollTo(0, {}.scrollTop);')
def to_location(self, x, y):
"""滚动到指定位置
:param x: 水平距离
:param y: 垂直距离
:return: None
"""
self._run_js(f'{{}}.scrollTo({x}, {y});')
def up(self, pixel=300):
"""向上滚动若干像素,水平位置不变
:param pixel: 滚动的像素
:return: None
"""
pixel = -pixel
self._run_js(f'{{}}.scrollBy(0, {pixel});')
def down(self, pixel=300):
"""向下滚动若干像素,水平位置不变
:param pixel: 滚动的像素
:return: None
"""
self._run_js(f'{{}}.scrollBy(0, {pixel});')
def left(self, pixel=300):
"""向左滚动若干像素,垂直位置不变
:param pixel: 滚动的像素
:return: None
"""
pixel = -pixel
self._run_js(f'{{}}.scrollBy({pixel}, 0);')
def right(self, pixel=300):
"""向右滚动若干像素,垂直位置不变
:param pixel: 滚动的像素
:return: None
"""
self._run_js(f'{{}}.scrollBy({pixel}, 0);')
def _wait_scrolled(self):
if not self._wait_complete:
return
page = self._driver.page if 'ChromiumElement' in str(type(self._driver)) else self._driver
r = page.run_cdp('Page.getLayoutMetrics')
x = r['layoutViewport']['pageX']
y = r['layoutViewport']['pageY']
end_time = perf_counter() + self._driver.page.timeout
while perf_counter() < end_time:
sleep(.1)
r = page.run_cdp('Page.getLayoutMetrics')
x1 = r['layoutViewport']['pageX']
y1 = r['layoutViewport']['pageY']
if x == x1 and y == y1:
break
x = x1
y = y1
class ElementScroller(Scroller):
def to_see(self, center=None):
"""滚动页面直到元素可见
:param center: 是否尽量滚动到页面正中为None时如果被遮挡则滚动到页面正中
:return: None
"""
self._driver.page.scroll.to_see(self._driver, center=center)
def to_center(self):
"""元素尽量滚动到视口中间"""
self._driver.page.scroll.to_see(self._driver, center=True)
class PageScroller(Scroller):
def __init__(self, page):
"""
:param page: 页面对象
"""
super().__init__(page)
self.t1 = 'window'
self.t2 = 'document.documentElement'
def to_see(self, loc_or_ele, center=None):
"""滚动页面直到元素可见
:param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串
:param center: 是否尽量滚动到页面正中为None时如果被遮挡则滚动到页面正中
:return: None
"""
ele = self._driver._ele(loc_or_ele)
self._to_see(ele, center)
def _to_see(self, ele, center):
"""执行滚动页面直到元素可见
:param ele: 元素对象
:param center: 是否尽量滚动到页面正中为None时如果被遮挡则滚动到页面正中
:return: None
"""
txt = 'true' if center else 'false'
ele.run_js(f'this.scrollIntoViewIfNeeded({txt});')
if center or (center is not False and ele.states.is_covered):
ele.run_js('''function getWindowScrollTop() {var scroll_top = 0;
if (document.documentElement && document.documentElement.scrollTop) {
scroll_top = document.documentElement.scrollTop;
} else if (document.body) {scroll_top = document.body.scrollTop;}
return scroll_top;}
const { top, height } = this.getBoundingClientRect();
const elCenter = top + height / 2;
const center = window.innerHeight / 2;
window.scrollTo({top: getWindowScrollTop() - (center - elCenter),
behavior: 'instant'});''')
self._wait_scrolled()
class FrameScroller(PageScroller):
def __init__(self, frame):
"""
:param frame: ChromiumFrame对象
"""
self._driver = frame.doc_ele
self.t1 = self.t2 = 'this.documentElement'
self._wait_complete = False
def to_see(self, loc_or_ele, center=None):
"""滚动页面直到元素可见
:param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串
:param center: 是否尽量滚动到页面正中为None时如果被遮挡则滚动到页面正中
:return: None
"""
ele = loc_or_ele if 'ChromiumElement' in str(type(loc_or_ele)) else self._driver._ele(loc_or_ele)
self._to_see(ele, center)

View File

@ -0,0 +1,73 @@
# -*- coding:utf-8 -*-
from typing import Union
from .._elements.chromium_element import ChromiumElement
from .._pages.chromium_base import ChromiumBase
from .._pages.chromium_frame import ChromiumFrame
from .._pages.chromium_page import ChromiumPage
class Scroller(object):
def __init__(self, page_or_ele: Union[ChromiumBase, ChromiumElement, ChromiumFrame]):
self.t1: str = ...
self.t2: str = ...
self._driver: Union[ChromiumPage, ChromiumElement, ChromiumFrame] = ...
self._wait_complete: bool = ...
def _run_js(self, js: str): ...
def to_top(self) -> None: ...
def to_bottom(self) -> None: ...
def to_half(self) -> None: ...
def to_rightmost(self) -> None: ...
def to_leftmost(self) -> None: ...
def to_location(self, x: int, y: int) -> None: ...
def up(self, pixel: int = 300) -> None: ...
def down(self, pixel: int = 300) -> None: ...
def left(self, pixel: int = 300) -> None: ...
def right(self, pixel: int = 300) -> None: ...
def _wait_scrolled(self) -> None: ...
class ElementScroller(Scroller):
def to_see(self, center: Union[bool, None] = None) -> None: ...
def to_center(self) -> None: ...
class PageScroller(Scroller):
def __init__(self, page: ChromiumBase): ...
def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement], center: Union[bool, None] = None) -> None: ...
def _to_see(self, ele: ChromiumElement, center: Union[bool, None]) -> None: ...
class FrameScroller(PageScroller):
def __init__(self, frame):
"""
:param frame: ChromiumFrame对象
"""
self._driver = frame.doc_ele
self.t1 = self.t2 = 'this.documentElement'
self._wait_complete = False
def to_see(self, loc_or_ele, center=None):
"""滚动页面直到元素可见
:param loc_or_ele: 元素的定位信息可以是loc元组或查询字符串
:param center: 是否尽量滚动到页面正中为None时如果被遮挡则滚动到页面正中
:return: None
"""
ele = loc_or_ele if isinstance(loc_or_ele, ChromiumElement) else self._driver._ele(loc_or_ele)
self._to_see(ele, center)

View File

@ -0,0 +1,244 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
"""
from time import perf_counter
class SelectElement(object):
"""用于处理 select 标签"""
def __init__(self, ele):
"""
:param ele: select 元素对象
"""
if ele.tag != 'select':
raise TypeError("select方法只能在<select>元素使用。")
self._ele = ele
def __call__(self, text_or_index, timeout=None):
"""选定下拉列表中子元素
:param text_or_index: 根据文本值选或序号择选项若允许多选传入list或tuple可多选
:param timeout: 超时时间不输入默认实用页面超时时间
:return: None
"""
para_type = 'index' if isinstance(text_or_index, int) else 'text'
timeout = timeout if timeout is not None else self._ele.page.timeout
return self._select(text_or_index, para_type, timeout=timeout)
@property
def is_multi(self):
"""返回是否多选表单"""
return self._ele.attr('multiple') is not None
@property
def options(self):
"""返回所有选项元素组成的列表"""
return self._ele.eles('xpath://option')
@property
def selected_option(self):
"""返回第一个被选中的option元素
:return: ChromiumElement对象或None
"""
ele = self._ele.run_js('return this.options[this.selectedIndex];')
return ele
@property
def selected_options(self):
"""返回所有被选中的option元素列表
:return: ChromiumElement对象组成的列表
"""
return [x for x in self.options if x.states.is_selected]
def all(self):
"""全选"""
if not self.is_multi:
raise TypeError("只能在多选菜单执行此操作。")
return self._by_loc('tag:option', 1, False)
def invert(self):
"""反选"""
if not self.is_multi:
raise TypeError("只能对多项选框执行反选。")
change = False
for i in self.options:
change = True
mode = 'false' if i.states.is_selected else 'true'
i.run_js(f'this.selected={mode};')
if change:
self._dispatch_change()
def clear(self):
"""清除所有已选项"""
if not self.is_multi:
raise TypeError("只能在多选菜单执行此操作。")
return self._by_loc('tag:option', 1, True)
def by_text(self, text, timeout=None):
"""此方法用于根据text值选择项。当元素是多选列表时可以接收list或tuple
:param text: text属性值传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
"""
return self._select(text, 'text', False, timeout)
def by_value(self, value, timeout=None):
"""此方法用于根据value值选择项。当元素是多选列表时可以接收list或tuple
:param value: value属性值传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
"""
return self._select(value, 'value', False, timeout)
def by_index(self, index, timeout=None):
"""此方法用于根据index值选择项。当元素是多选列表时可以接收list或tuple
:param index: 序号0开始传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
"""
return self._select(index, 'index', False, timeout)
def by_loc(self, loc, timeout=None):
"""用定位符选择指定的项
:param loc: 定位符
:param timeout: 超时时间
:return: 是否选择成功
"""
return self._by_loc(loc, timeout)
def cancel_by_text(self, text, timeout=None):
"""此方法用于根据text值取消选择项。当元素是多选列表时可以接收list或tuple
:param text: 文本传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
"""
return self._select(text, 'text', True, timeout)
def cancel_by_value(self, value, timeout=None):
"""此方法用于根据value值取消选择项。当元素是多选列表时可以接收list或tuple
:param value: value属性值传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
"""
return self._select(value, 'value', True, timeout)
def cancel_by_index(self, index, timeout=None):
"""此方法用于根据index值取消选择项。当元素是多选列表时可以接收list或tuple
:param index: 序号0开始传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
"""
return self._select(index, 'index', True, timeout)
def cancel_by_loc(self, loc, timeout=None):
"""用定位符取消选择指定的项
:param loc: 定位符
:param timeout: 超时时间
:return: 是否选择成功
"""
return self._by_loc(loc, timeout, True)
def _by_loc(self, loc, timeout=None, cancel=False):
"""用定位符取消选择指定的项
:param loc: 定位符
:param timeout: 超时时间
:param cancel: 是否取消选择
:return: 是否选择成功
"""
eles = self._ele.eles(loc, timeout)
if not eles:
return False
mode = 'false' if cancel else 'true'
if self.is_multi:
for ele in eles:
ele.run_js(f'this.selected={mode};')
self._dispatch_change()
return True
eles[0].run_js(f'this.selected={mode};')
self._dispatch_change()
return True
def _select(self, condition, para_type='text', cancel=False, timeout=None):
"""选定或取消选定下拉列表中子元素
:param condition: 根据文本值选或序号择选项若允许多选传入list或tuple可多选
:param para_type: 参数类型可选 'text''value''index'
:param cancel: 是否取消选择
:return: 是否选择成功
"""
if not self.is_multi and isinstance(condition, (list, tuple)):
raise TypeError('单选列表只能传入str格式。')
mode = 'false' if cancel else 'true'
timeout = timeout if timeout is not None else self._ele.page.timeout
condition = set(condition) if isinstance(condition, (list, tuple)) else {condition}
if para_type in ('text', 'value'):
return self._text_value([str(i) for i in condition], para_type, mode, timeout)
elif para_type == 'index':
return self._index(condition, mode, timeout)
def _text_value(self, condition, para_type, mode, timeout):
"""执行text和value搜索
:param condition: 条件set
:param para_type: 参数类型可选 'text''value'
:param mode: 'true' 'false'
:param timeout: 超时时间
:return: 是否选择成功
"""
ok = False
text_len = len(condition)
eles = []
end_time = perf_counter() + timeout
while perf_counter() < end_time:
if para_type == 'text':
eles = [i for i in self.options if i.text in condition]
elif para_type == 'value':
eles = [i for i in self.options if i.attr('value') in condition]
if len(eles) >= text_len:
ok = True
break
if ok:
for i in eles:
i.run_js(f'this.selected={mode};')
self._dispatch_change()
return True
return False
def _index(self, condition, mode, timeout):
"""执行index搜索
:param condition: 条件set
:param mode: 'true' 'false'
:param timeout: 超时时间
:return: 是否选择成功
"""
ok = False
condition = [int(i) for i in condition]
text_len = max(condition)
end_time = perf_counter() + timeout
while perf_counter() < end_time:
if len(self.options) >= text_len:
ok = True
break
if ok:
eles = self.options
for i in condition:
eles[i - 1].run_js(f'this.selected={mode};')
self._dispatch_change()
return True
return False
def _dispatch_change(self):
"""触发修改动作"""
self._ele.run_js('this.dispatchEvent(new UIEvent("change"));')

View File

@ -0,0 +1,63 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
"""
from typing import Union, Tuple, List
from .._elements.chromium_element import ChromiumElement
class SelectElement(object):
def __init__(self, ele: ChromiumElement):
self._ele: ChromiumElement = ...
def __call__(self, text_or_index: Union[str, int, list, tuple], timeout: float = None) -> bool: ...
@property
def is_multi(self) -> bool: ...
@property
def options(self) -> List[ChromiumElement]: ...
@property
def selected_option(self) -> Union[ChromiumElement, None]: ...
@property
def selected_options(self) -> List[ChromiumElement]: ...
def clear(self) -> None: ...
def all(self) -> None: ...
def by_text(self, text: Union[str, list, tuple], timeout: float = None) -> bool: ...
def by_value(self, value: Union[str, list, tuple], timeout: float = None) -> bool: ...
def by_index(self, index: Union[int, list, tuple], timeout: float = None) -> bool: ...
def by_loc(self, loc: Union[str, Tuple[str, str]], timeout: float = None) -> bool: ...
def cancel_by_text(self, text: Union[str, list, tuple], timeout: float = None) -> bool: ...
def cancel_by_value(self, value: Union[str, list, tuple], timeout: float = None) -> bool: ...
def cancel_by_index(self, index: Union[int, list, tuple], timeout: float = None) -> bool: ...
def cancel_by_loc(self, loc: Union[str, Tuple[str, str]], timeout: float = None) -> bool: ...
def invert(self) -> None: ...
def _by_loc(self, loc: Union[str, Tuple[str, str]], timeout: float = None, cancel: bool = False) -> bool: ...
def _select(self,
condition: Union[str, int, list, tuple] = None,
para_type: str = 'text',
cancel: bool = False,
timeout: float = None) -> bool: ...
def _text_value(self, condition: Union[list, set], para_type: str, mode: str, timeout: float) -> bool: ...
def _index(self, condition: set, mode: str, timeout: float) -> bool: ...
def _dispatch_change(self) -> None: ...

View File

@ -5,7 +5,7 @@
"""
class ChromiumTabRect(object):
class TabRect(object):
def __init__(self, page):
self._page = page

View File

@ -9,7 +9,7 @@ from .._pages.chromium_page import ChromiumPage
from .._pages.chromium_tab import ChromiumTab
class ChromiumTabRect(object):
class TabRect(object):
def __init__(self, page: Union[ChromiumPage, ChromiumTab]):
self._page: Union[ChromiumPage, ChromiumTab] = ...

View File

@ -5,7 +5,7 @@ from .._commons.constants import Settings
from ..errors import WaitTimeoutError, NoRectError
class ChromiumBaseWaiter(object):
class BaseWaiter(object):
def __init__(self, page_or_ele):
"""
:param page_or_ele: 页面对象或元素对象
@ -190,7 +190,7 @@ class ChromiumBaseWaiter(object):
return False
class ChromiumTabWaiter(ChromiumBaseWaiter):
class TabWaiter(BaseWaiter):
def downloads_done(self, timeout=None, cancel_if_timeout=True):
"""等待所有浏览器下载任务结束
@ -219,7 +219,7 @@ class ChromiumTabWaiter(ChromiumBaseWaiter):
return True
class ChromiumPageWaiter(ChromiumTabWaiter):
class PageWaiter(TabWaiter):
def __init__(self, page):
super().__init__(page)
# self._listener = None
@ -270,7 +270,7 @@ class ChromiumPageWaiter(ChromiumTabWaiter):
return True
class ChromiumElementWaiter(object):
class ElementWaiter(object):
"""等待元素在dom中某种状态如删除、显示、隐藏"""
def __init__(self, page, ele):
@ -417,10 +417,10 @@ class ChromiumElementWaiter(object):
return False
class FrameWaiter(ChromiumBaseWaiter, ChromiumElementWaiter):
class FrameWaiter(BaseWaiter, ElementWaiter):
def __init__(self, frame):
"""
:param frame: ChromiumFrame对象
"""
super().__init__(frame)
super(ChromiumBaseWaiter, self).__init__(frame, frame.frame_ele)
super(BaseWaiter, self).__init__(frame, frame.frame_ele)

View File

@ -13,7 +13,7 @@ from .._pages.chromium_frame import ChromiumFrame
from .._pages.chromium_page import ChromiumPage
class ChromiumBaseWaiter(object):
class BaseWaiter(object):
def __init__(self, page: ChromiumBase):
self._driver: ChromiumBase = ...
@ -54,12 +54,12 @@ class ChromiumBaseWaiter(object):
raise_err: bool = None) -> bool: ...
class ChromiumTabWaiter(ChromiumBaseWaiter):
class TabWaiter(BaseWaiter):
def downloads_done(self, timeout: float = None, cancel_if_timeout: bool = True) -> bool: ...
class ChromiumPageWaiter(ChromiumTabWaiter):
class PageWaiter(TabWaiter):
_driver: ChromiumPage = ...
def new_tab(self, timeout: float = None, raise_err: bool = None) -> Union[str, bool]: ...
@ -67,7 +67,7 @@ class ChromiumPageWaiter(ChromiumTabWaiter):
def all_downloads_done(self, timeout: float = None, cancel_if_timeout: bool = True) -> bool: ...
class ChromiumElementWaiter(object):
class ElementWaiter(object):
def __init__(self,
page: ChromiumBase,
ele: ChromiumElement):
@ -97,5 +97,5 @@ class ChromiumElementWaiter(object):
def _wait_state(self, attr: str, mode: bool = False, timeout: float = None, raise_err: bool = None) -> bool: ...
class FrameWaiter(ChromiumBaseWaiter, ChromiumElementWaiter):
class FrameWaiter(BaseWaiter, ElementWaiter):
def __init__(self, frame: ChromiumFrame): ...

View File

@ -9,3 +9,5 @@ from ._commons.keys import Keys
from ._commons.by import By
from ._commons.constants import Settings
from ._commons.tools import wait_until
__all__ = ['make_session_ele', 'ActionChains', 'Keys', 'By', 'Settings', 'wait_until']