一些修改(+)

增加Settings.browser_connect_timeout属性;
优化关闭标签页逻辑;
修复下载路径设置问题;
点击产生的新标签页下载任务可用原标签页等待;
remove_attr()返回元素自身;
select各种方法返回元素本身,找不到项时报错;
This commit is contained in:
g1879 2024-10-21 07:10:10 +08:00
parent 8d37aa079e
commit 4407a0daa1
19 changed files with 130 additions and 114 deletions

View File

@ -65,7 +65,6 @@ class Chromium(object):
self._drivers = {}
self._all_drivers = {}
self._newest_tab_id = None
self._tab_to_close = set()
self._set = None
self._wait = None
@ -204,18 +203,19 @@ class Chromium(object):
all_tabs = set(self.tab_ids)
if others:
tabs = all_tabs - tabs
if len(all_tabs - tabs) <= 0:
self.quit()
return
for tab in tabs:
self._close_tab(tab_id=tab)
def _close_tab(self, tab_id):
# self._onTargetDestroyed(targetId=tab)
self._tab_to_close.add(tab_id)
self._driver.run('Target.closeTarget', targetId=tab_id)
while tab_id in self._tab_to_close:
sleep(.2)
if len(all_tabs - tabs) > 0:
for tab in tabs:
self._close_tab(tab=tab)
else:
self.quit()
def _close_tab(self, tab):
if isinstance(tab, str):
tab = self.get_tab(tab)
tab._run_cdp('Target.closeTarget', targetId=tab.tab_id)
while tab.driver.is_running and tab.tab_id in self._all_drivers:
sleep(.01)
def activate_tab(self, id_ind_tab):
if isinstance(id_ind_tab, int):
@ -396,16 +396,14 @@ class Chromium(object):
pass
def _onTargetDestroyed(self, **kwargs):
with self._lock:
tab_id = kwargs['targetId']
self._dl_mgr.clear_tab_info(tab_id)
for key in [k for k, i in self._frames.items() if i == tab_id]:
self._frames.pop(key, None)
for d in self._all_drivers.get(tab_id, tuple()):
d.stop()
self._drivers.pop(tab_id, None)
self._all_drivers.pop(tab_id, None)
self._tab_to_close.discard(tab_id)
tab_id = kwargs['targetId']
self._dl_mgr.clear_tab_info(tab_id)
for key in [k for k, i in self._frames.items() if i == tab_id]:
self._frames.pop(key, None)
for d in self._all_drivers.get(tab_id, tuple()):
d.stop()
self._drivers.pop(tab_id, None)
self._all_drivers.pop(tab_id, None)
def _on_disconnect(self):
if not self._disconnect_flag:

View File

@ -12,7 +12,7 @@ from .driver import BrowserDriver, Driver
from .._configs.chromium_options import ChromiumOptions
from .._configs.session_options import SessionOptions
from .._functions.cookies import CookiesList
from .._pages.chromium_base import Timeout
from .._pages.chromium_base import Timeout, ChromiumBase
from .._pages.chromium_tab import ChromiumTab
from .._pages.mix_tab import MixTab
from .._units.downloader import DownloadManager
@ -52,7 +52,6 @@ class Chromium(object):
_none_ele_return_value: bool = ...
_none_ele_value: Any = ...
_newest_tab_id: Optional[str] = ...
_tab_to_close: set = ...
def __new__(cls,
addr_or_opts: Union[str, int, ChromiumOptions] = None,
@ -194,9 +193,9 @@ class Chromium(object):
"""
...
def _close_tab(self, tab_id: str):
def _close_tab(self, tab: Union[ChromiumBase, str]):
"""关闭一个标签页
:param tab_id: 标签页id
:param tab: 标签页对象或id
:return: None
"""

View File

@ -336,7 +336,7 @@ class ChromiumOptions(object):
return self
def set_address(self, address):
address = address.replace('localhost', '127.0.0.1').lstrip('http://').lstrip('https://')
address = address.replace('localhost', '127.0.0.1').lstrip('htps:/')
self._address = address
return self

View File

@ -204,6 +204,8 @@ class ChromiumElement(DrissionElement):
if (is_checked and uncheck) or (not is_checked and not uncheck):
self.click()
return self
def parent(self, level_or_loc=1, index=1, timeout=0):
return super().parent(level_or_loc, index, timeout=timeout)
@ -399,6 +401,7 @@ class ChromiumElement(DrissionElement):
def remove_attr(self, name):
self._run_js(f'this.removeAttribute("{name}");')
return self
def property(self, name):
try:
@ -555,7 +558,7 @@ class ChromiumElement(DrissionElement):
vals = ''.join([str(i) for i in vals])
self.set.property('value', str(vals))
self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
return
return self
self.wait.clickable(wait_moved=False, timeout=.5)
if clear and vals not in ('\n', '\ue007'):
@ -575,7 +578,7 @@ class ChromiumElement(DrissionElement):
if by_js:
self._run_js("this.value='';")
self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
return
return self
self._input_focus()
self.input(('\ue009', 'a', '\ue017'), clear=False)

View File

@ -377,7 +377,7 @@ class ChromiumElement(DrissionElement):
"""
...
def remove_attr(self, name: str) -> None:
def remove_attr(self, name: str) -> ChromiumElement:
"""删除元素一个attribute属性
:param name: 属性名
:return: None

View File

@ -15,6 +15,7 @@ from time import perf_counter, sleep
from requests import Session
from .settings import Settings
from .tools import port_is_using
from .._configs.options_manage import OptionsManager
from ..errors import BrowserConnectError
@ -168,8 +169,8 @@ def set_flags(opt):
dump(states_dict, f)
def test_connect(ip, port, timeout=30):
end_time = perf_counter() + timeout
def test_connect(ip, port):
end_time = perf_counter() + Settings.browser_connect_timeout
s = Session()
s.trust_env = False
s.keep_alive = False

View File

@ -14,6 +14,7 @@ class Settings(object):
raise_when_wait_failed = False
singleton_tab_obj = True
cdp_timeout = 30
browser_connect_timeout = 30
auto_handle_alert = None
_suffixes_list = str(Path(__file__).parent.absolute() / 'suffixes.dat').replace('\\', '/')

View File

@ -15,6 +15,7 @@ class Settings(object):
raise_when_wait_failed: bool = ...
singleton_tab_obj: bool = ...
cdp_timeout: float = ...
browser_connect_timeout: float = ...
auto_handle_alert: Optional[bool] = ...
_suffixes_list: str = ...

View File

@ -118,7 +118,7 @@ class ChromiumPage(ChromiumBase):
self.browser.activate_tab(id_ind_tab)
def close(self):
self.browser._close_tab(self.tab_id)
self.browser._close_tab(self)
def close_tabs(self, tabs_or_ids, others=False):
self.browser.close_tabs(tabs_or_ids=tabs_or_ids, others=others)

View File

@ -54,7 +54,7 @@ class ChromiumTab(ChromiumBase):
if others:
self.browser.close_tabs(self.tab_id, others=True)
else:
self.browser._close_tab(self.tab_id)
self.browser._close_tab(self)
@property
def set(self):

View File

@ -182,7 +182,7 @@ class MixTab(SessionPage, ChromiumTab, BasePage):
if others:
self.browser.close_tabs(self.tab_id, others=True)
else:
self.browser._close_tab(self.tab_id)
self.browser._close_tab(self)
if session and self._session:
self._session.close()
if self._response is not None:

View File

@ -232,7 +232,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
def close(self):
if self._has_driver:
self.browser._close_tab(self.tab_id)
self.browser._close_tab(self)
if self._session:
self._session.close()
if self._response is not None:

View File

@ -125,7 +125,18 @@ class Clicker(object):
if not self._ele.tab._browser._dl_mgr._running:
self._ele.tab._browser.set.download_path('.')
if new_tab or self._ele.tab._type.endswith('Page'):
if self._ele.tab._type.endswith('Page'):
obj = browser = self._ele.tab._browser
tid = 'browser'
# t_settings = TabDownloadSettings(self._ele.owner.tab_id)
# b_settings = TabDownloadSettings('browser')
# b_settings.rename = t_settings.rename
# b_settings.suffix = t_settings.suffix
# t_settings.rename = None
# t_settings.suffix = None
tmp_save_path = str(Path(save_path).absolute()) if save_path else self._ele.owner._tab.download_path
elif new_tab:
obj = browser = self._ele.tab._browser
tid = 'browser'
t_settings = TabDownloadSettings(self._ele.owner.tab_id)
@ -150,11 +161,15 @@ class Clicker(object):
browser._dl_mgr.set_flag(tid, True)
self.left(by_js=by_js)
r = wait_mission(browser, tid, timeout)
m = wait_mission(browser, tid, timeout)
if tmp_save_path:
r.path = tmp_save_path
return r
if m:
if tmp_save_path:
m.path = tmp_save_path
if new_tab:
self._ele.owner.browser._dl_mgr._tab_missions.setdefault(self._ele.owner.tab_id, []).append(m)
m.from_tab = self._ele.owner
return m
def to_upload(self, file_paths, by_js=False):
self._ele.owner.set.upload_files(file_paths)

View File

@ -25,7 +25,7 @@ class DownloadManager(object):
t.when_file_exists = 'rename'
self._missions = {} # {guid: DownloadMission}
self._tab_missions = {} # {tab_id: DownloadMission}
self._tab_missions = {} # {tab_id: [DownloadMission, ...]}
self._flags = {} # {tab_id: [bool, DownloadMission]}
self._running = False
@ -67,8 +67,11 @@ class DownloadManager(object):
if mission.state not in ('canceled', 'skipped'):
mission.state = state
mission.final_path = final_path
if mission.tab_id in self._tab_missions and mission.id in self._tab_missions[mission.tab_id]:
self._tab_missions[mission.tab_id].remove(mission.id)
if mission.tab_id in self._tab_missions and mission in self._tab_missions[mission.tab_id]:
self._tab_missions[mission.tab_id].remove(mission)
if (mission.from_tab and mission.from_tab in self._tab_missions
and mission in self._tab_missions[mission.from_tab]):
self._tab_missions[mission.from_tab].remove(mission)
self._missions.pop(mission.id, None)
mission._is_done = True
@ -96,8 +99,9 @@ class DownloadManager(object):
def _onDownloadWillBegin(self, **kwargs):
guid = kwargs['guid']
tab_id = self._browser._frames.get(kwargs['frameId'], 'browser')
tab = tab_id if tab_id in TabDownloadSettings.TABS else 'browser'
settings = TabDownloadSettings(tab_id if tab_id in TabDownloadSettings.TABS else 'browser')
settings = TabDownloadSettings(tab)
if settings.rename:
if settings.suffix is not None:
name = f'{settings.rename}.{settings.suffix}' if settings.suffix else settings.rename
@ -132,15 +136,15 @@ class DownloadManager(object):
m = DownloadMission(self, tab_id, guid, settings.path, name, kwargs['url'], self._browser.download_path)
self._missions[guid] = m
if self.get_flag(tab_id) is False: # 取消该任务
if self.get_flag(tab) is False: # 取消该任务
self.cancel(m)
elif skip:
self.skip(m)
else:
self._tab_missions.setdefault(tab_id, []).append(m)
if self.get_flag(tab_id) is not None:
self._flags[tab_id] = m
if self.get_flag(tab) is not None:
self._flags[tab] = m
def _onDownloadProgress(self, **kwargs):
if kwargs['guid'] in self._missions:
@ -204,6 +208,7 @@ class DownloadMission(object):
self._mgr = mgr
self.url = url
self.tab_id = tab_id
self.from_tab = None
self.id = _id
self.path = path
self.name = name

View File

@ -141,6 +141,7 @@ class TabDownloadSettings(object):
class DownloadMission(object):
tab_id: str = ...
from_tab: Optional[str] = ...
_mgr: DownloadManager = ...
url: str = ...
id: str = ...

View File

@ -32,8 +32,7 @@ class SelectElement(object):
@property
def selected_option(self):
ele = self._ele._run_js('return this.options[this.selectedIndex];')
return ele
return self._ele._run_js('return this.options[this.selectedIndex];')
@property
def selected_options(self):
@ -54,6 +53,7 @@ class SelectElement(object):
i._run_js(f'this.selected={mode};')
if change:
self._dispatch_change()
return self._ele
def clear(self):
if not self.is_multi:
@ -73,7 +73,7 @@ class SelectElement(object):
return self._by_loc(locator, timeout)
def by_option(self, option):
self._select_options(option, 'true')
return self._select_options(option, 'true')
def cancel_by_text(self, text, timeout=None):
return self._select(text, 'text', True, timeout)
@ -88,19 +88,17 @@ class SelectElement(object):
return self._by_loc(locator, timeout, True)
def cancel_by_option(self, option):
self._select_options(option, 'false')
return self._select_options(option, 'false')
def _by_loc(self, loc, timeout=None, cancel=False):
eles = self._ele.eles(loc, timeout)
if not eles:
return False
raise RuntimeError('没有找到指定选项。')
mode = 'false' if cancel else 'true'
if self.is_multi:
self._select_options(eles, mode)
else:
self._select_options(eles[0], mode)
return True
if not self.is_multi:
eles = eles[0]
return self._select_options(eles, mode)
def _select(self, condition, para_type='text', cancel=False, timeout=None):
if not self.is_multi and isinstance(condition, (list, tuple)):
@ -117,7 +115,6 @@ class SelectElement(object):
return self._index(condition, mode, timeout)
def _text_value(self, condition, para_type, mode, timeout):
ok = False
text_len = len(condition)
eles = []
end_time = perf_counter() + timeout
@ -128,34 +125,22 @@ class SelectElement(object):
eles = [i for i in self.options if i.attr('value') in condition]
if len(eles) >= text_len:
ok = True
break
return self._select_options(eles, mode)
sleep(.01)
if ok:
self._select_options(eles, mode)
return True
return False
raise RuntimeError('没有找到指定选项。')
def _index(self, condition, mode, timeout):
ok = False
condition = [int(i) for i in condition]
text_len = abs(max(condition, key=abs))
end_time = perf_counter() + timeout
while perf_counter() < end_time:
if len(self.options) >= text_len:
ok = True
break
eles = self.options
eles = [eles[i - 1] if i > 0 else eles[i] for i in condition]
return self._select_options(eles, mode)
sleep(.01)
if ok:
eles = self.options
eles = [eles[i - 1] if i > 0 else eles[i] for i in condition]
self._select_options(eles, mode)
return True
return False
raise RuntimeError('没有找到指定选项。')
def _select_options(self, option, mode):
if isinstance(option, (list, tuple, set)):
@ -167,6 +152,7 @@ class SelectElement(object):
else:
option._run_js(f'this.selected={mode};')
self._dispatch_change()
return self._ele
def _dispatch_change(self):
self._ele._run_js('this.dispatchEvent(new CustomEvent("change", {bubbles: true}));')

View File

@ -20,11 +20,11 @@ class SelectElement(object):
def __call__(self,
text_or_index: Union[str, int, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""选定下拉列表中子元素
:param text_or_index: 根据文本值选或序号择选项若允许多选传入list或tuple可多选
:param timeout: 超时时间不输入默认实用页面超时时间
:return: None
:return: <select>元素对象
"""
...
@ -48,122 +48,124 @@ class SelectElement(object):
"""返回所有被选中的<option>元素列表"""
...
def all(self) -> None:
def all(self) -> ChromiumElement:
"""全选"""
...
def invert(self) -> None:
def invert(self) -> ChromiumElement:
"""反选"""
...
def clear(self) -> None:
def clear(self) -> ChromiumElement:
"""清除所有已选项"""
...
def by_text(self,
text: Union[str, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据text值选择项。当元素是多选列表时可以接收list或tuple
:param text: text属性值传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def by_value(self,
value: Union[str, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据value值选择项。当元素是多选列表时可以接收list或tuple
:param value: value属性值传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def by_index(self,
index: Union[int, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据index值选择项。当元素是多选列表时可以接收list或tuple
:param index: 序号从1开始可传入负数获取倒数第几个传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def by_locator(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""用定位符选择指定的项
:param locator: 定位符
:param timeout: 超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def by_option(self, option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]]) -> None:
def by_option(self,
option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]]) -> ChromiumElement:
"""选中单个或多个<option>元素
:param option: <option>元素或它们组成的列表
:return: None
:return: <select>元素对象
"""
...
def cancel_by_text(self,
text: Union[str, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据text值取消选择项。当元素是多选列表时可以接收list或tuple
:param text: 文本传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
:return: <select>元素对象
"""
...
def cancel_by_value(self,
value: Union[str, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据value值取消选择项。当元素是多选列表时可以接收list或tuple
:param value: value属性值传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
:return: <select>元素对象
"""
...
def cancel_by_index(self,
index: Union[int, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据index值取消选择项。当元素是多选列表时可以接收list或tuple
:param index: 序号从1开始可传入负数获取倒数第几个传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
:return: <select>元素对象
"""
...
def cancel_by_locator(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""用定位符取消选择指定的项
:param locator: 定位符
:param timeout: 超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def cancel_by_option(self,
option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]]) -> None:
option: Union[ChromiumElement, List[ChromiumElement],
Tuple[ChromiumElement]]) -> ChromiumElement:
"""取消选中单个或多个<option>元素
:param option: <option>元素或它们组成的列表
:return: None
:return: <select>元素对象
"""
...
def _by_loc(self,
loc: Union[str, Tuple[str, str]],
timeout: float = None,
cancel: bool = False) -> bool:
cancel: bool = False) -> ChromiumElement:
"""用定位符取消选择指定的项
:param loc: 定位符
:param timeout: 超时时间
:param cancel: 是否取消选择
:return: 是否选择成功
:return: <select>元素对象
"""
...
@ -171,12 +173,12 @@ class SelectElement(object):
condition: Union[str, int, list, tuple] = None,
para_type: str = 'text',
cancel: bool = False,
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""选定或取消选定下拉列表中子元素
:param condition: 根据文本值选或序号择选项若允许多选传入list或tuple可多选
:param para_type: 参数类型可选 'text''value''index'
:param cancel: 是否取消选择
:return: 是否选择成功
:return: <select>元素对象
"""
...
@ -184,31 +186,32 @@ class SelectElement(object):
condition: Union[list, set],
para_type: str,
mode: str,
timeout: float) -> bool:
timeout: float) -> ChromiumElement:
"""执行text和value搜索
:param condition: 条件set
:param para_type: 参数类型可选 'text''value'
:param mode: 'true' 'false'
:param timeout: 超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def _index(self, condition: set, mode: str, timeout: float) -> bool:
def _index(self, condition: set, mode: str, timeout: float) -> ChromiumElement:
"""执行index搜索
:param condition: 条件set
:param mode: 'true' 'false'
:param timeout: 超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def _select_options(self, option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]],
mode: str) -> None:
def _select_options(self,
option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]],
mode: str) -> ChromiumElement:
"""选中或取消某个选项
:param option: options元素对象
:param mode: 选中还是取消
:return: None
:return: <select>元素对象
"""
...

View File

@ -271,7 +271,7 @@ class ChromiumPageSetter(TabSetter):
self._owner._DownloadKit.set.save_path(path)
def download_file_name(self, name=None, suffix=None):
super().download_file_name(name=name, suffix=suffix)
# super().download_file_name(name=name, suffix=suffix)
self._owner.browser.set.download_file_name(name, suffix)

View File

@ -270,6 +270,9 @@ class ChromiumPageWaiter(TabWaiter):
def new_tab(self, timeout=None, raise_err=None):
return self._owner.browser.wait.new_tab(timeout=timeout, raise_err=raise_err)
def download_begin(self, timeout=None, cancel_it=False):
return self._owner.browser.wait.download_begin(timeout=timeout, cancel_it=cancel_it)
def all_downloads_done(self, timeout=None, cancel_if_timeout=True):
return self._owner.browser.wait.downloads_done(timeout=timeout, cancel_if_timeout=cancel_if_timeout)