mirror of
https://gitee.com/g1879/DrissionPage.git
synced 2024-12-10 04:00:23 +08:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
834d2ef00d
@ -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)
|
||||
|
@ -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: ...
|
||||
|
@ -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()
|
||||
|
@ -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: ...
|
||||
|
@ -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设置端口和用户文件夹路径。')
|
||||
|
||||
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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 = ...
|
||||
|
@ -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信息的类"""
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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: ...
|
||||
|
@ -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):
|
||||
|
@ -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: ...
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
104
DrissionPage/_units/element_states.py
Normal file
104
DrissionPage/_units/element_states.py
Normal 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
|
54
DrissionPage/_units/element_states.pyi
Normal file
54
DrissionPage/_units/element_states.pyi
Normal 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: ...
|
57
DrissionPage/_units/ids.py
Normal file
57
DrissionPage/_units/ids.py
Normal 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
|
45
DrissionPage/_units/ids.pyi
Normal file
45
DrissionPage/_units/ids.pyi
Normal 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: ...
|
103
DrissionPage/_units/locations.py
Normal file
103
DrissionPage/_units/locations.py
Normal 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
|
50
DrissionPage/_units/locations.pyi
Normal file
50
DrissionPage/_units/locations.pyi
Normal 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]: ...
|
171
DrissionPage/_units/scroller.py
Normal file
171
DrissionPage/_units/scroller.py
Normal 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)
|
73
DrissionPage/_units/scroller.pyi
Normal file
73
DrissionPage/_units/scroller.pyi
Normal 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)
|
244
DrissionPage/_units/select_element.py
Normal file
244
DrissionPage/_units/select_element.py
Normal 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"));')
|
63
DrissionPage/_units/select_element.pyi
Normal file
63
DrissionPage/_units/select_element.pyi
Normal 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: ...
|
@ -5,7 +5,7 @@
|
||||
"""
|
||||
|
||||
|
||||
class ChromiumTabRect(object):
|
||||
class TabRect(object):
|
||||
def __init__(self, page):
|
||||
self._page = page
|
||||
|
||||
|
@ -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] = ...
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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): ...
|
||||
|
@ -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']
|
||||
|
Loading…
x
Reference in New Issue
Block a user