diff --git a/DrissionPage/_base/chromium.py b/DrissionPage/_base/chromium.py index 096a88e..b14606e 100644 --- a/DrissionPage/_base/chromium.py +++ b/DrissionPage/_base/chromium.py @@ -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: diff --git a/DrissionPage/_base/chromium.pyi b/DrissionPage/_base/chromium.pyi index b3b9ee8..778dc4d 100644 --- a/DrissionPage/_base/chromium.pyi +++ b/DrissionPage/_base/chromium.pyi @@ -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 """ diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index 29b00b1..17a3b76 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -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 diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 7ddfdcf..b6250db 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -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) diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index d87f54b..626b0b1 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -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 diff --git a/DrissionPage/_functions/browser.py b/DrissionPage/_functions/browser.py index 9f8bcd3..ca9107f 100644 --- a/DrissionPage/_functions/browser.py +++ b/DrissionPage/_functions/browser.py @@ -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 diff --git a/DrissionPage/_functions/settings.py b/DrissionPage/_functions/settings.py index 0645efc..53838e5 100644 --- a/DrissionPage/_functions/settings.py +++ b/DrissionPage/_functions/settings.py @@ -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('\\', '/') diff --git a/DrissionPage/_functions/settings.pyi b/DrissionPage/_functions/settings.pyi index 5e79bd9..bca1de0 100644 --- a/DrissionPage/_functions/settings.pyi +++ b/DrissionPage/_functions/settings.pyi @@ -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 = ... diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index 00d1847..c5941de 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -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) diff --git a/DrissionPage/_pages/chromium_tab.py b/DrissionPage/_pages/chromium_tab.py index 878ca4d..6d4b62a 100644 --- a/DrissionPage/_pages/chromium_tab.py +++ b/DrissionPage/_pages/chromium_tab.py @@ -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): diff --git a/DrissionPage/_pages/mix_tab.py b/DrissionPage/_pages/mix_tab.py index 3e3332b..73cddb6 100644 --- a/DrissionPage/_pages/mix_tab.py +++ b/DrissionPage/_pages/mix_tab.py @@ -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: diff --git a/DrissionPage/_pages/web_page.py b/DrissionPage/_pages/web_page.py index a8b8c6a..b2fd8d1 100644 --- a/DrissionPage/_pages/web_page.py +++ b/DrissionPage/_pages/web_page.py @@ -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: diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index 1985d61..3739817 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -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) diff --git a/DrissionPage/_units/downloader.py b/DrissionPage/_units/downloader.py index 5693275..6f310f8 100644 --- a/DrissionPage/_units/downloader.py +++ b/DrissionPage/_units/downloader.py @@ -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 diff --git a/DrissionPage/_units/downloader.pyi b/DrissionPage/_units/downloader.pyi index 85965c9..b1e803d 100644 --- a/DrissionPage/_units/downloader.pyi +++ b/DrissionPage/_units/downloader.pyi @@ -141,6 +141,7 @@ class TabDownloadSettings(object): class DownloadMission(object): tab_id: str = ... + from_tab: Optional[str] = ... _mgr: DownloadManager = ... url: str = ... id: str = ... diff --git a/DrissionPage/_units/selector.py b/DrissionPage/_units/selector.py index d6d3cfe..b6bbebd 100644 --- a/DrissionPage/_units/selector.py +++ b/DrissionPage/_units/selector.py @@ -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}));') diff --git a/DrissionPage/_units/selector.pyi b/DrissionPage/_units/selector.pyi index ff7bcc5..574fbad 100644 --- a/DrissionPage/_units/selector.pyi +++ b/DrissionPage/_units/selector.pyi @@ -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: 元素对象 """ ... 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: 元素对象 """ ... def by_locator(self, locator: Union[Tuple[str, str], str], - timeout: float = None) -> bool: + timeout: float = None) -> ChromiumElement: """用定位符选择指定的项 :param locator: 定位符 :param timeout: 超时时间 - :return: 是否选择成功 + :return: 元素对象 """ ... 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: 元素对象 """ ... 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: 元素对象 """ ... def cancel_by_option(self, - option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]]) -> None: + option: Union[ChromiumElement, List[ChromiumElement], + Tuple[ChromiumElement]]) -> ChromiumElement: """取消选中单个或多个