[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n"
  },
  {
    "path": "DrissionPage/__init__.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\r\n允许任何人以个人身份使用或分发本项目源代码，但仅限于学习和合法非盈利目的。\r\n个人或组织如未获得版权持有人授权，不得将本项目以源代码或二进制形式用于商业行为。\r\n\r\n使用本项目需满足以下条款，如使用过程中出现违反任意一项条款的情形，授权自动失效。\r\n* 禁止将DrissionPage应用到任何可能违反当地法律规定和道德约束的项目中\r\n* 禁止将DrissionPage用于任何可能有损他人利益的项目中\r\n* 禁止将DrissionPage用于攻击与骚扰行为\r\n* 遵守Robots协议，禁止将DrissionPage用于采集法律或系统Robots协议不允许的数据\r\n\r\n使用DrissionPage发生的一切行为均由使用人自行负责。\r\n因使用DrissionPage进行任何行为所产生的一切纠纷及后果均与版权持有人无关，\r\n版权持有人不承担任何使用DrissionPage带来的风险和损失。\r\n版权持有人不对DrissionPage可能存在的缺陷导致的任何损失负任何责任。\r\n\"\"\"\r\nfrom ._base.chromium import Chromium\r\nfrom ._configs.chromium_options import ChromiumOptions\r\nfrom ._configs.session_options import SessionOptions\r\nfrom ._pages.chromium_page import ChromiumPage\r\nfrom ._pages.session_page import SessionPage\r\nfrom ._pages.web_page import WebPage\r\nfrom .version import __version__\r\n"
  },
  {
    "path": "DrissionPage/__init__.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom ._base.chromium import Chromium\nfrom ._configs.chromium_options import ChromiumOptions\nfrom ._configs.session_options import SessionOptions\nfrom ._pages.chromium_page import ChromiumPage\nfrom ._pages.session_page import SessionPage\nfrom ._pages.web_page import WebPage\nfrom .version import __version__\n\n\n__all__ = ['WebPage', 'ChromiumPage', 'Chromium', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__']\n"
  },
  {
    "path": "DrissionPage/_base/base.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom abc import abstractmethod\r\nfrom copy import copy\r\nfrom pathlib import Path\r\nfrom re import sub\r\nfrom urllib.parse import quote\r\n\r\nfrom DrissionGet import DrissionGet\r\nfrom requests import Session\r\n\r\nfrom .._configs.session_options import SessionOptions\r\nfrom .._elements.none_element import NoneElement\r\nfrom .._functions.elements import get_frame, get_eles\r\nfrom .._functions.locator import get_loc\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._functions.web import format_html\r\nfrom ..errors import ElementNotFoundError, LocatorError\r\n\r\n\r\nclass BaseParser(object):\r\n    def __call__(self, locator):\r\n        return self.ele(locator)\r\n\r\n    def ele(self, locator, index=1, timeout=None):\r\n        return self._ele(locator, timeout, index=index, method='ele()')\r\n\r\n    def eles(self, locator, timeout=None):\r\n        return self._ele(locator, timeout, index=None)\r\n\r\n    def find(self, locators, any_one=True, first_ele=True, timeout=None):\r\n        if 'Session' in self._type:\r\n            timeout = 0\r\n        if timeout is None:\r\n            timeout = self.timeout\r\n        r = get_eles(locators, self, any_one, first_ele, timeout)\r\n        if any_one:\r\n            for ele in r:\r\n                if r[ele]:\r\n                    return ele, r[ele]\r\n            return None, None\r\n        return r\r\n\r\n    # ----------------以下属性或方法待后代实现----------------\r\n    @property\r\n    def html(self):\r\n        return ''\r\n\r\n    def s_ele(self, locator=None):\r\n        pass\r\n\r\n    def s_eles(self, locator):\r\n        pass\r\n\r\n    def _ele(self, locator, timeout=None, index=1, raise_err=None, method=None):\r\n        pass\r\n\r\n    def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):\r\n        pass\r\n\r\n\r\nclass BaseElement(BaseParser):\r\n    def __init__(self, owner=None):\r\n        self.owner = owner\r\n        self._type = 'BaseElement'\r\n\r\n    def get_frame(self, loc_or_ind, timeout=None):\r\n        if not isinstance(loc_or_ind, (int, str, tuple)):\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'loc_or_ind',\r\n                                           ALLOW_TYPE=_S._lang.LOC_OR_IND, CURR_VAL=loc_or_ind))\r\n        return get_frame(self, loc_ind_ele=loc_or_ind, timeout=timeout)\r\n\r\n    def _ele(self, locator, timeout=None, index=1, relative=False, raise_err=None, method=None):\r\n        if hasattr(locator, '_type'):\r\n            return locator\r\n        if timeout is None:\r\n            timeout = self.timeout\r\n        r = self._find_elements(locator, timeout=timeout, index=index, relative=relative, raise_err=raise_err)\r\n        if r or isinstance(r, (list, float, int)):\r\n            return r\r\n        if raise_err is True or (_S.raise_when_ele_not_found and raise_err is None):\r\n            raise ElementNotFoundError(METHOD=method, ARGS={'locator': locator, 'index': index, 'timeout': timeout})\r\n\r\n        r.method = method\r\n        r.args = {'locator': locator, 'index': index, 'timeout': timeout}\r\n        return r\r\n\r\n    @property\r\n    def timeout(self):\r\n        return self.owner.timeout if self.owner else 10\r\n\r\n    @property\r\n    def child_count(self):\r\n        return int(self._ele('xpath:count(./*)'))\r\n\r\n    # ----------------以下属性或方法由后代实现----------------\r\n    @property\r\n    def tag(self):\r\n        return\r\n\r\n    def parent(self, level_or_loc=1):\r\n        pass\r\n\r\n    def next(self, index=1):\r\n        pass\r\n\r\n    def nexts(self):\r\n        pass\r\n\r\n\r\nclass DrissionElement(BaseElement):\r\n\r\n    @property\r\n    def link(self):\r\n        return self.attr('href') or self.attr('src')\r\n\r\n    @property\r\n    def css_path(self):\r\n        return self._get_ele_path(xpath=False)\r\n\r\n    @property\r\n    def xpath(self):\r\n        return self._get_ele_path()\r\n\r\n    @property\r\n    def comments(self):\r\n        return self.eles('xpath:.//comment()')\r\n\r\n    def texts(self, text_node_only=False):\r\n        texts = self.eles('xpath:/text()') if text_node_only else [x if isinstance(x, str) else x.text\r\n                                                                   for x in self.eles('xpath:./text() | *')]\r\n        return [format_html(x.strip(' ').rstrip('\\n')) for x in texts if x and sub('[\\r\\n\\t ]', '', x) != '']\r\n\r\n    def parent(self, level_or_loc=1, index=1, timeout=None):\r\n        if isinstance(level_or_loc, int):\r\n            loc = f'xpath:./ancestor::*[{level_or_loc}]'\r\n\r\n        elif isinstance(level_or_loc, (tuple, str)):\r\n            loc = get_loc(level_or_loc, True)\r\n            if loc[0] == 'css selector':\r\n                raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n            loc = f'xpath:./ancestor::{loc[1].lstrip(\". / \")}[{index}]'\r\n\r\n        else:\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'level_or_loc', ALLOW_TYPE='tuple, int, str',\r\n                                           CURR_VAL=level_or_loc))\r\n\r\n        return self._ele(loc, timeout=timeout, relative=True, raise_err=False, method='parent()')\r\n\r\n    def child(self, locator='', index=1, timeout=None, ele_only=True):\r\n        if isinstance(locator, int):\r\n            index = locator\r\n            locator = ''\r\n        if not locator:\r\n            loc = '*' if ele_only else 'node()'\r\n        else:\r\n            loc = get_loc(locator, True)  # 把定位符转换为xpath\r\n            if loc[0] == 'css selector':\r\n                raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n            loc = loc[1].lstrip('./')\r\n\r\n        node = self._ele(f'xpath:./{loc}', timeout=timeout, index=index, relative=True, raise_err=False)\r\n        return node if node else NoneElement(self.owner, 'child()',\r\n                                             {'locator': locator, 'index': index, 'ele_only': ele_only})\r\n\r\n    def prev(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return self._get_relative('prev()', 'preceding', True, locator, index, timeout, ele_only)\r\n\r\n    def next(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return self._get_relative('next()', 'following', True, locator, index, timeout, ele_only)\r\n\r\n    def before(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return self._get_relative('before()', 'preceding', False, locator, index, timeout, ele_only)\r\n\r\n    def after(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return self._get_relative('after()', 'following', False, locator, index, timeout, ele_only)\r\n\r\n    def children(self, locator='', timeout=None, ele_only=True):\r\n        if not locator:\r\n            loc = '*' if ele_only else 'node()'\r\n        else:\r\n            loc = get_loc(locator, True)  # 把定位符转换为xpath\r\n            if loc[0] == 'css selector':\r\n                raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n            loc = loc[1].lstrip('./')\r\n\r\n        loc = f'xpath:./{loc}'\r\n        nodes = self._ele(loc, timeout=timeout, index=None, relative=True)\r\n        return [e for e in nodes if not (isinstance(e, str) and sub('[ \\n\\t\\r]', '', e) == '')]\r\n\r\n    def prevs(self, locator='', timeout=None, ele_only=True):\r\n        return self._get_relatives(locator=locator, direction='preceding', timeout=timeout, ele_only=ele_only)\r\n\r\n    def nexts(self, locator='', timeout=None, ele_only=True):\r\n        return self._get_relatives(locator=locator, direction='following', timeout=timeout, ele_only=ele_only)\r\n\r\n    def befores(self, locator='', timeout=None, ele_only=True):\r\n        return self._get_relatives(locator=locator, direction='preceding',\r\n                                   brother=False, timeout=timeout, ele_only=ele_only)\r\n\r\n    def afters(self, locator='', timeout=None, ele_only=True):\r\n        return self._get_relatives(locator=locator, direction='following',\r\n                                   brother=False, timeout=timeout, ele_only=ele_only)\r\n\r\n    def _get_relative(self, func, direction, brother, locator='', index=1, timeout=None, ele_only=True):\r\n        if isinstance(locator, int):\r\n            index = locator\r\n            locator = ''\r\n        node = self._get_relatives(index, locator, direction, brother, timeout, ele_only)\r\n        return node if node else NoneElement(self.owner, func,\r\n                                             {'locator': locator, 'index': index, 'ele_only': ele_only})\r\n\r\n    def _get_relatives(self, index=None, locator='', direction='following', brother=True, timeout=.5, ele_only=True):\r\n        brother = '-sibling' if brother else ''\r\n\r\n        if not locator:\r\n            loc = '*' if ele_only else 'node()'\r\n\r\n        else:\r\n            loc = get_loc(locator, True)  # 把定位符转换为xpath\r\n            if loc[0] == 'css selector':\r\n                raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n            loc = loc[1].lstrip('./')\r\n\r\n        loc = f'xpath:./{direction}{brother}::{loc}'\r\n\r\n        if index is not None:\r\n            index = index if direction == 'following' else -index\r\n        nodes = self._ele(loc, timeout=timeout, index=index, relative=True, raise_err=False)\r\n        if isinstance(nodes, list):\r\n            nodes = [e for e in nodes if not (isinstance(e, str) and sub('[ \\n\\t\\r]', '', e) == '')]\r\n        return nodes\r\n\r\n    # ----------------以下属性或方法由后代实现----------------\r\n    @property\r\n    def attrs(self):\r\n        return\r\n\r\n    @property\r\n    def text(self):\r\n        return\r\n\r\n    @property\r\n    def raw_text(self):\r\n        return\r\n\r\n    @abstractmethod\r\n    def attr(self, name):\r\n        return ''\r\n\r\n    def _get_ele_path(self, xpath=True):\r\n        return ''\r\n\r\n    def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):\r\n        pass\r\n\r\n\r\nclass BasePage(BaseParser):\r\n\r\n    def __init__(self):\r\n        self._url = None\r\n        self._url_available = None\r\n        self.retry_times = 3\r\n        self.retry_interval = 2\r\n        self._downloader = None\r\n        self._download_path = None\r\n        self._none_ele_return_value = False\r\n        self._none_ele_value = None\r\n        self._session = None\r\n        self._headers = None\r\n        self._session_options = None\r\n        self._type = 'BasePage'\r\n\r\n    @property\r\n    def title(self):\r\n        ele = self._ele('xpath://title', raise_err=False, method='title')\r\n        return ele.text if ele else None\r\n\r\n    @property\r\n    def url_available(self):\r\n        return self._url_available\r\n\r\n    @property\r\n    def download_path(self):\r\n        return self._download_path\r\n\r\n    @property\r\n    def download(self):\r\n        if self._downloader is None:\r\n            if not self._session:\r\n                self._create_session()\r\n            self._downloader = DrissionGet(driver=self, save_path=self.download_path)\r\n        return self._downloader\r\n\r\n    def _before_connect(self, url, retry, interval):\r\n        is_file = False\r\n        if isinstance(url, Path) or ('://' not in url and ':\\\\\\\\' not in url):\r\n            p = Path(url)\r\n            if p.exists():\r\n                url = str(p.absolute())\r\n                is_file = True\r\n\r\n        self._url = url if is_file else quote(url, safe='-_.~!*\\'\"();:@&=+$,/\\\\?#[]%')\r\n        retry = retry if retry is not None else self.retry_times\r\n        interval = interval if interval is not None else self.retry_interval\r\n        return retry, interval, is_file\r\n\r\n    def _set_session_options(self, session_or_options=None):\r\n        if not session_or_options:\r\n            self._session_options = SessionOptions(session_or_options)\r\n\r\n        elif isinstance(session_or_options, SessionOptions):\r\n            self._session_options = session_or_options\r\n\r\n        elif isinstance(session_or_options, Session):\r\n            self._session_options = SessionOptions()\r\n            self._session = copy(session_or_options)\r\n            self._headers = self._session.headers\r\n            self._session.headers = None\r\n\r\n    def _create_session(self):\r\n        if not self._session_options:\r\n            self._set_session_options()\r\n        self._session, self._headers = self._session_options.make_session()\r\n\r\n    # ----------------以下属性或方法由后代实现----------------\r\n    @property\r\n    def url(self):\r\n        return\r\n\r\n    @property\r\n    def json(self):\r\n        return\r\n\r\n    @property\r\n    def user_agent(self):\r\n        return\r\n\r\n    @abstractmethod\r\n    def get(self, url, show_errmsg=False, retry=None, interval=None):\r\n        pass\r\n\r\n    def _ele(self, locator, timeout=None, index=1, raise_err=None, method=None):\r\n        if not locator:\r\n            raise ElementNotFoundError(METHOD=method, ARGS={'locator': locator, 'index': index, 'timeout': timeout})\r\n        if timeout is None:\r\n            timeout = self.timeout\r\n\r\n        r = self._find_elements(locator, timeout=timeout, index=index, raise_err=raise_err)\r\n        if r or isinstance(r, list):\r\n            return r\r\n        if raise_err is True or (_S.raise_when_ele_not_found and raise_err is None):\r\n            raise ElementNotFoundError(METHOD=method, ARGS={'locator': locator, 'index': index, 'timeout': timeout})\r\n\r\n        r.method = method\r\n        r.args = {'locator': locator, 'index': index, 'timeout': timeout}\r\n        return r\r\n"
  },
  {
    "path": "DrissionPage/_base/base.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom abc import abstractmethod\r\nfrom typing import Union, Tuple, List, Any, Optional, Dict\r\n\r\nfrom DrissionGet import DrissionGet\r\nfrom requests import Session\r\nfrom requests.structures import CaseInsensitiveDict\r\n\r\nfrom .._configs.session_options import SessionOptions\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._elements.none_element import NoneElement\r\nfrom .._elements.session_element import SessionElement\r\nfrom .._functions.elements import SessionElementsList\r\nfrom .._pages.chromium_frame import ChromiumFrame\r\nfrom .._pages.chromium_page import ChromiumPage\r\nfrom .._pages.session_page import SessionPage\r\nfrom .._pages.web_page import WebPage\r\n\r\n\r\nclass BaseParser(object):\r\n    \"\"\"所有页面、元素类的基类\"\"\"\r\n    _type: str\r\n    timeout: float\r\n\r\n    def __call__(self, locator: Union[Tuple[str, str], str], index: int = 1): ...\r\n\r\n    def ele(self,\r\n            locator: Union[Tuple[str, str], str, BaseElement],\r\n            index: int = 1,\r\n            timeout: float = None): ...\r\n\r\n    def eles(self, locator: Union[Tuple[str, str], str], timeout=None): ...\r\n\r\n    def find(self,\r\n             locators: Union[str, List[str], tuple],\r\n             any_one: bool = True,\r\n             first_ele: bool = True,\r\n             timeout: float = None) -> Union[Dict[str, ChromiumElement], Dict[str, SessionElement],\r\n    Dict[str, List[ChromiumElement]], Dict[str, List[SessionElement]], Tuple[str, SessionElement],\r\n    Tuple[str, ChromiumElement]]:\r\n        \"\"\"传入多个定位符，获取多个ele\r\n        :param locators: 定位符组成的列表\r\n        :param any_one: 是否任何一个定位符找到结果即返回\r\n        :param first_ele: 每个定位符是否只获取第一个元素\r\n        :param timeout: 超时时间（秒）\r\n        :return: any_one为True时，返回一个找到的元素定位符和对象组成的元组，格式：(loc, ele)，全都没找到返回(None, None)\r\n                 any_one为False时，返回dict格式，key为定位符，value为找到的元素或列表\r\n        \"\"\"\r\n        ...\r\n\r\n    # ----------------以下属性或方法待后代实现----------------\r\n    @property\r\n    def html(self) -> str: ...\r\n\r\n    def s_ele(self,\r\n              locator: Union[Tuple[str, str], str, BaseElement, None] = None,\r\n              index: int = 1) -> SessionElement: ...\r\n\r\n    def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ...\r\n\r\n    def _ele(self,\r\n             locator: Union[Tuple[str, str], str],\r\n             timeout: float = None,\r\n             index: Optional[int] = 1,\r\n             raise_err: bool = None,\r\n             method: str = None): ...\r\n\r\n    def _find_elements(self,\r\n                       locator: Union[Tuple[str, str], str],\r\n                       timeout: float,\r\n                       index: Optional[int] = 1,\r\n                       relative: bool = False,\r\n                       raise_err: bool = None): ...\r\n\r\n\r\nclass BaseElement(BaseParser):\r\n    \"\"\"各元素类的基类\"\"\"\r\n    owner: BasePage = ...\r\n\r\n    def __init__(self, owner: BasePage = None): ...\r\n\r\n    @property\r\n    def timeout(self) -> float:\r\n        \"\"\"返回其查找元素时超时时间\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def child_count(self) -> int:\r\n        \"\"\"返回直接子元素的个数\"\"\"\r\n        ...\r\n\r\n    # ----------------以下属性或方法由后代实现----------------\r\n    @property\r\n    def tag(self) -> str: ...\r\n\r\n    def parent(self, level_or_loc: Union[tuple, str, int] = 1): ...\r\n\r\n    def prev(self, index: int = 1) -> None: ...\r\n\r\n    def prevs(self) -> None: ...\r\n\r\n    def next(self, index: int = 1): ...\r\n\r\n    def nexts(self): ...\r\n\r\n    def get_frame(self, loc_or_ind, timeout=None) -> ChromiumFrame:\r\n        \"\"\"获取元素中一个frame对象\r\n        :param loc_or_ind: 定位符、iframe序号，序号从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 查找元素超时时间（秒）\r\n        :return: ChromiumFrame对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _ele(self,\r\n             locator: Union[Tuple[str, str], str],\r\n             timeout: float = None,\r\n             index: Optional[int] = 1,\r\n             relative: bool = False,\r\n             raise_err: bool = None,\r\n             method: str = None):\r\n        \"\"\"调用获取元素的方法\r\n        :param locator: 定位符\r\n        :param timeout: 超时时间（秒）\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param relative: 是否相对定位\r\n        :param raise_err: 找不到时是否抛出异常\r\n        :param method: 调用的方法名\r\n        :return: 元素对象或它们组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass DrissionElement(BaseElement):\r\n    \"\"\"ChromiumElement 和 SessionElement的基类，但不是ShadowRoot的基类\"\"\"\r\n\r\n    def __init__(self, owner: BasePage = None): ...\r\n\r\n    @property\r\n    def link(self) -> str:\r\n        \"\"\"返回href或src绝对url\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def css_path(self) -> str:\r\n        \"\"\"返回css path路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def xpath(self) -> str:\r\n        \"\"\"返回xpath路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def comments(self) -> list:\r\n        \"\"\"返回元素注释文本组成的列表\"\"\"\r\n        ...\r\n\r\n    def texts(self, text_node_only: bool = False) -> list:\r\n        \"\"\"返回元素内所有直接子节点的文本，包括元素和文本节点\r\n        :param text_node_only: 是否只返回文本节点\r\n        :return: 文本列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def parent(self,\r\n               level_or_loc: Union[tuple, str, int] = 1,\r\n               index: int = 1,\r\n               timeout: float = None) -> Union[DrissionElement, None]:\r\n        \"\"\"返回上面某一级父元素，可指定层数或用查询语法定位\r\n        :param level_or_loc: 第几级父元素，1开始，或定位符\r\n        :param index: 当level_or_loc传入定位符，使用此参数选择第几个结果，1开始\r\n        :param timeout: 时间（秒）\r\n        :return: 上级元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def child(self,\r\n              locator: Union[Tuple[str, str], str, int] = '',\r\n              index: int = 1,\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]:\r\n        \"\"\"返回直接子元素元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 直接子元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def prev(self,\r\n             locator: Union[Tuple[str, str], str, int] = '',\r\n             index: int = 1,\r\n             timeout: float = None,\r\n             ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]:\r\n        \"\"\"返回前面的一个兄弟元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 兄弟元素\r\n        \"\"\"\r\n        ...\r\n\r\n    def next(self,\r\n             locator: Union[Tuple[str, str], str, int] = '',\r\n             index: int = 1,\r\n             timeout: float = None,\r\n             ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]:\r\n        \"\"\"返回后面的一个兄弟元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 后面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 兄弟元素\r\n        \"\"\"\r\n        ...\r\n\r\n    def before(self,\r\n               locator: Union[Tuple[str, str], str, int] = '',\r\n               index: int = 1,\r\n               timeout: float = None,\r\n               ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]:\r\n        \"\"\"返回前面的一个兄弟元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def after(self,\r\n              locator: Union[Tuple[str, str], str, int] = '',\r\n              index: int = 1,\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]:\r\n        \"\"\"返回后面的一个兄弟元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 后面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素后面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def children(self,\r\n                 locator: Union[Tuple[str, str], str] = '',\r\n                 timeout: float = None,\r\n                 ele_only: bool = True) -> List[Union[DrissionElement, str]]:\r\n        \"\"\"返回直接子元素元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 直接子元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def prevs(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> List[Union[DrissionElement, str]]:\r\n        \"\"\"返回前面全部兄弟元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 兄弟元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def nexts(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> List[Union[DrissionElement, str]]:\r\n        \"\"\"返回后面全部兄弟元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 兄弟元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def befores(self,\r\n                locator: Union[Tuple[str, str], str] = '',\r\n                timeout: float = None,\r\n                ele_only: bool = True) -> List[Union[DrissionElement, str]]:\r\n        \"\"\"返回后面全部兄弟元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def afters(self,\r\n               locator: Union[Tuple[str, str], str] = '',\r\n               timeout: float = None,\r\n               ele_only: bool = True) -> List[Union[DrissionElement, str]]:\r\n        \"\"\"返回前面全部兄弟元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素后面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def _get_relative(self,\r\n                      func: str,\r\n                      direction: str,\r\n                      brother: bool,\r\n                      locator: Union[Tuple[str, str], str] = '',\r\n                      index: int = 1,\r\n                      timeout: float = None,\r\n                      ele_only: bool = True) -> DrissionElement:\r\n        \"\"\"获取一个亲戚元素或节点，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param func: 方法名称\r\n        :param direction: 方向，'following' 或 'preceding'\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def _get_relatives(self,\r\n                       index: int = None,\r\n                       locator: Union[Tuple[str, str], str] = '',\r\n                       direction: str = 'following',\r\n                       brother: bool = True,\r\n                       timeout: float = 0.5,\r\n                       ele_only: bool = True) -> List[Union[DrissionElement, str]]:\r\n        \"\"\"按要求返回兄弟元素或节点组成的列表\r\n        :param index: 获取第几个，该参数不为None时只获取该编号的元素\r\n        :param locator: 用于筛选的查询语法\r\n        :param direction: 'following' 或 'preceding'，查找的方向\r\n        :param brother: 查找范围，在同级查找还是整个dom前后查找\r\n        :param timeout: 查找等待时间（秒）\r\n        :return: 元素对象或字符串\r\n        \"\"\"\r\n        ...\r\n\r\n    # ----------------以下属性或方法由后代实现----------------\r\n    @property\r\n    def attrs(self) -> dict: ...\r\n\r\n    @property\r\n    def text(self) -> str: ...\r\n\r\n    @property\r\n    def raw_text(self) -> str: ...\r\n\r\n    @abstractmethod\r\n    def attr(self, name: str) -> str: ...\r\n\r\n    def _get_ele_path(self, xpath: bool = True) -> str: ...\r\n\r\n\r\nclass BasePage(BaseParser):\r\n    \"\"\"页面类的基类\"\"\"\r\n\r\n    _url_available: Optional[bool] = ...\r\n    retry_times: int = ...\r\n    retry_interval: float = ...\r\n    _download_path: Optional[str] = ...\r\n    _downloader: Optional[DrissionGet] = ...\r\n    _none_ele_return_value: bool = ...\r\n    _none_ele_value: Any = ...\r\n    _page: Union[ChromiumPage, SessionPage, WebPage] = ...\r\n    _session: Optional[Session] = ...\r\n    _headers: Optional[CaseInsensitiveDict] = ...\r\n    _session_options: Optional[SessionOptions] = ...\r\n\r\n    def __init__(self): ...\r\n\r\n    @property\r\n    def title(self) -> Union[str, None]:\r\n        \"\"\"返回网页title\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def url_available(self) -> bool:\r\n        \"\"\"返回当前访问的url有效性\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def download_path(self) -> str:\r\n        \"\"\"返回默认下载路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def download(self) -> DrissionGet:\r\n        \"\"\"返回下载器对象\"\"\"\r\n        ...\r\n\r\n    def _before_connect(self, url: str, retry: int, interval: float) -> tuple:\r\n        \"\"\"连接前的准备\r\n        :param url: 要访问的url\r\n        :param retry: 重试次数\r\n        :param interval: 重试间隔\r\n        :return: 重试次数、间隔、是否文件组成的tuple\r\n        \"\"\"\r\n        ...\r\n\r\n    def _set_session_options(self, session_or_options: Union[Session, SessionOptions] = None) -> None:\r\n        \"\"\"启动配置\r\n        :param session_or_options: Session、SessionOptions对象\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _create_session(self) -> None:\r\n        \"\"\"创建内建Session对象\"\"\"\r\n        ...\r\n\r\n    # ----------------以下属性或方法由后代实现----------------\r\n    @property\r\n    def url(self) -> str: ...\r\n\r\n    @property\r\n    def json(self) -> dict: ...\r\n\r\n    @property\r\n    def user_agent(self) -> str: ...\r\n\r\n    @abstractmethod\r\n    def get(self, url: str, show_errmsg: bool = False, retry: int = None, interval: float = None): ...\r\n\r\n    def _ele(self,\r\n             locator,\r\n             timeout: float = None,\r\n             index: Optional[int] = 1,\r\n             raise_err: bool = None,\r\n             method: str = None):\r\n        \"\"\"调用获取元素的方法\r\n        :param locator: 定位符\r\n        :param timeout: 超时时间（秒）\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param raise_err: 找不到时是否抛出异常\r\n        :param method: 调用的方法名\r\n        :return: 元素对象或它们组成的列表\r\n        \"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_base/chromium.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom pathlib import Path\nfrom re import match\nfrom shutil import rmtree\nfrom threading import Lock\nfrom time import sleep, perf_counter\n\nfrom requests import Session\nfrom websocket import WebSocketBadStatusException\n\nfrom .driver import BrowserDriver, Driver\nfrom .._configs.chromium_options import ChromiumOptions\nfrom .._functions.browser import connect_browser\nfrom .._functions.cookies import CookiesList\nfrom .._functions.settings import Settings as _S\nfrom .._functions.tools import PortFinder, raise_error\nfrom .._pages.chromium_base import Timeout\nfrom .._pages.chromium_tab import ChromiumTab\nfrom .._pages.mix_tab import MixTab\nfrom .._units.downloader import DownloadManager\nfrom .._units.setter import BrowserSetter\nfrom .._units.states import BrowserStates\nfrom .._units.waiter import BrowserWaiter\nfrom ..errors import BrowserConnectError, CDPError, PageDisconnectedError, IncorrectURLError\n\n__ERROR__ = 'error'\n\n\nclass Chromium(object):\n    _BROWSERS = {}\n    _lock = Lock()\n\n    def __new__(cls, addr_or_opts=None, session_options=None):\n        opt = handle_options(addr_or_opts)\n\n        with cls._lock:\n            is_headless, browser_id, is_exists, ws_only, driver = run_browser(opt)\n            if browser_id in cls._BROWSERS:\n                return cls._BROWSERS[browser_id]\n        r = object.__new__(cls)\n        driver.owner = r\n        r._driver = driver\n        r._chromium_options = opt\n        r._is_headless = is_headless\n        r._is_exists = is_exists\n        r._ws_only = ws_only\n        r.id = browser_id\n        cls._BROWSERS[browser_id] = r\n        return r\n\n    def __init__(self, addr_or_opts=None, session_options=None):\n        if hasattr(self, '_created'):\n            return\n        self._created = True\n\n        self._type = 'Chromium'\n        self._frames = {}\n        self._drivers = {}\n        self._all_drivers = {}\n        self._relation = {}\n        self._newest_tab_id = None\n\n        self._set = None\n        self._wait = None\n        self._states = None\n        self._timeouts = Timeout(**self._chromium_options.timeouts)\n        self._load_mode = self._chromium_options.load_mode\n        self._download_path = str(Path(self._chromium_options.download_path).absolute())\n        self._auto_handle_alert = None\n        self._none_ele_return_value = False\n        self._none_ele_value = None\n        self.retry_times = self._chromium_options.retry_times\n        self.retry_interval = self._chromium_options.retry_interval\n        self.address = self._chromium_options.address\n        self._ws_address = (self._chromium_options.ws_address if self._chromium_options.ws_address\n                            else f'ws://{self.address}/devtools/browser/{self.id}')\n        self._disconnect_flag = False\n\n        if (not self._ws_only\n                and (not self._chromium_options._ua_set and self._is_headless != self._chromium_options.is_headless)\n                or (self._is_exists and self._chromium_options._new_env)):\n            self.quit(3, True)\n            connect_browser(self._chromium_options)\n            s = Session()\n            s.trust_env = False\n            s.keep_alive = False\n            ws = s.get(f'http://{self.address}/json/version', headers={'Connection': 'close'})\n            self.id = ws.json()['webSocketDebuggerUrl'].split('/')[-1]\n            self._driver = BrowserDriver(self.id, self._ws_address, self)\n            ws.close()\n            s.close()\n            self._is_exists = False\n            self._frames = {}\n            self._drivers = {}\n            self._all_drivers = {}\n\n        self.version = self._run_cdp('Browser.getVersion')['product']\n\n        self._process_id = None\n        try:\n            r = self._run_cdp('SystemInfo.getProcessInfo')\n            for i in r.get('processInfo', []):\n                if i['type'] == 'browser':\n                    self._process_id = i['id']\n                    break\n        except:\n            pass\n\n        self._run_cdp('Target.setDiscoverTargets', discover=True)\n        self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed)\n        self._driver.set_callback('Target.targetCreated', self._onTargetCreated)\n        self._dl_mgr = DownloadManager(self)\n\n        self._session_options = session_options\n\n    @property\n    def user_data_path(self):\n        return self._chromium_options.user_data_path\n\n    @property\n    def process_id(self):\n        return self._process_id\n\n    @property\n    def timeout(self):\n        return self._timeouts.base\n\n    @property\n    def timeouts(self):\n        return self._timeouts\n\n    @property\n    def load_mode(self):\n        return self._load_mode\n\n    @property\n    def download_path(self):\n        return self._download_path\n\n    @property\n    def set(self):\n        if self._set is None:\n            self._set = BrowserSetter(self)\n        return self._set\n\n    @property\n    def states(self):\n        if self._states is None:\n            self._states = BrowserStates(self)\n        return self._states\n\n    @property\n    def wait(self):\n        if self._wait is None:\n            self._wait = BrowserWaiter(self)\n        return self._wait\n\n    @property\n    def tabs_count(self):\n        j = self._run_cdp('Target.getTargets')['targetInfos']  # 不要改用get，避免卡死\n        return len([i for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')])\n\n    @property\n    def tab_ids(self):\n        if self._ws_only:\n            return [i['targetId'] for i in self._run_cdp('Target.getTargets')['targetInfos']\n                    if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]\n        else:\n            return [i['id'] for i in self._driver.get(f'http://{self.address}/json').json()\n                    if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]\n\n    @property\n    def latest_tab(self):\n        return self._get_tab(id_or_num=self.tab_ids[0], as_id=not _S.singleton_tab_obj)\n\n    def cookies(self, all_info=False):\n        cks = self._run_cdp(f'Storage.getCookies')['cookies']\n        r = cks if all_info else [{'name': c['name'], 'value': c['value'], 'domain': c['domain']} for c in cks]\n        return CookiesList(r)\n\n    def new_tab(self, url=None, new_window=False, background=False, new_context=False):\n        return self._new_tab(True, url=url, new_window=new_window, background=background, new_context=new_context)\n\n    def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page'):\n        t = self._get_tab(id_or_num=id_or_num, title=title, url=url, tab_type=tab_type, mix=True, as_id=False)\n        if t._type != 'MixTab':\n            raise RuntimeError(_S._lang.join(_S._lang.TAB_OBJ_EXISTS))\n        return t\n\n    def get_tabs(self, title=None, url=None, tab_type='page'):\n        return self._get_tabs(title=title, url=url, tab_type=tab_type, mix=True, as_id=False)\n\n    def close_tabs(self, tabs_or_ids, others=False):\n        if isinstance(tabs_or_ids, str):\n            tabs = {tabs_or_ids}\n        elif isinstance(tabs_or_ids, ChromiumTab):\n            tabs = {tabs_or_ids.tab_id}\n        elif isinstance(tabs_or_ids, (list, tuple)):\n            tabs = set(i.tab_id if isinstance(i, ChromiumTab) else i for i in tabs_or_ids)\n        else:\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'tabs_or_ids',\n                                           ALLOW_TYPE=_S._lang.TAB_OR_ID, CURR_VAL=tabs_or_ids))\n\n        all_tabs = set(self.tab_ids)\n        if others:\n            tabs = all_tabs - tabs\n\n        if len(all_tabs - tabs) > 0:\n            for tab in tabs:\n                self._close_tab(tab=tab)\n        else:\n            self.quit()\n\n    def _close_tab(self, tab):\n        if isinstance(tab, str):\n            tab = self.get_tab(tab)\n        tab._run_cdp('Target.closeTarget', targetId=tab.tab_id)\n        while tab.driver.is_running and tab.tab_id in self._all_drivers:\n            sleep(.01)\n\n    def activate_tab(self, id_ind_tab):\n        if isinstance(id_ind_tab, int):\n            id_ind_tab += -1 if id_ind_tab else 1\n            id_ind_tab = self.tab_ids[id_ind_tab]\n        elif isinstance(id_ind_tab, ChromiumTab):\n            id_ind_tab = id_ind_tab.tab_id\n        self._run_cdp('Target.activateTarget', targetId=id_ind_tab)\n\n    def reconnect(self):\n        self._disconnect_flag = True\n        self._driver.stop()\n        BrowserDriver.BROWSERS.pop(self.id)\n        self._driver = BrowserDriver(self.id, self._ws_address, self)\n        self._run_cdp('Target.setDiscoverTargets', discover=True)\n        self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed)\n        self._driver.set_callback('Target.targetCreated', self._onTargetCreated)\n        self._disconnect_flag = False\n\n    def clear_cache(self, cache=True, cookies=True):\n        if cache:\n            self.latest_tab.run_cdp('Network.clearBrowserCache')\n\n        if cookies:\n            self._run_cdp('Storage.clearCookies')\n\n    def quit(self, timeout=5, force=False, del_data=False):\n        try:\n            self._run_cdp('Browser.close')\n        except PageDisconnectedError:\n            pass\n        self._driver.stop()\n\n        drivers = list(self._all_drivers.values())\n        for tab in drivers:\n            for driver in tab:\n                driver.stop()\n        if not self.address.startswith('127.0.0.1'):\n            return\n\n        if force:\n            pids = None\n            try:\n                pids = [pid['id'] for pid in self._run_cdp('SystemInfo.getProcessInfo')['processInfo']]\n            except:\n                pass\n\n            if pids:\n                from psutil import Process\n                for pid in pids:\n                    try:\n                        Process(pid).kill()\n                    except:\n                        pass\n\n                from os import popen\n                from platform import system\n                end_time = perf_counter() + timeout\n                while perf_counter() < end_time:\n                    ok = True\n                    for pid in pids:\n                        txt = f'tasklist | findstr {pid}' if system().lower() == 'windows' else f'ps -ef | grep {pid}'\n                        p = popen(txt)\n                        sleep(.05)\n                        try:\n                            if f'  {pid} ' in p.read():\n                                ok = False\n                                break\n                        except TypeError:\n                            pass\n\n                    if ok:\n                        break\n\n        if del_data and not self._chromium_options.is_auto_port and self._chromium_options.user_data_path:\n            path = Path(self._chromium_options.user_data_path)\n            rmtree(path, True)\n\n    def _new_tab(self, mix=True, url=None, new_window=False, background=False, new_context=False):\n        tab_type = MixTab if mix else ChromiumTab\n        tab = None\n        if new_context:\n            tab = self._run_cdp('Target.createBrowserContext')['browserContextId']\n\n        kwargs = {'url': ''}\n        if new_window:\n            kwargs['newWindow'] = True\n        if background:\n            kwargs['background'] = True\n        if tab:\n            kwargs['browserContextId'] = tab\n\n        if self.states.is_incognito and not new_context:\n            return _new_tab_by_js(self, url, tab_type, new_window)\n        else:\n            try:\n                tab = self._run_cdp('Target.createTarget', **kwargs)['targetId']\n            except CDPError:\n                return _new_tab_by_js(self, url, tab_type, new_window)\n\n        while self.states.is_alive:\n            if tab in self._drivers:\n                break\n            sleep(.01)\n        else:\n            raise BrowserConnectError(_S._lang.BROWSER_DISCONNECTED)\n        tab = tab_type(self, tab)\n        if url:\n            tab.get(url)\n        return tab\n\n    def _get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', mix=True, as_id=False):\n        if id_or_num is not None:\n            if isinstance(id_or_num, int):\n                id_or_num = self.tab_ids[id_or_num - 1 if id_or_num > 0 else id_or_num]\n            elif isinstance(id_or_num, ChromiumTab):\n                return id_or_num.tab_id if as_id else ChromiumTab(self, id_or_num.tab_id)\n            else:\n                j = self._run_cdp('Target.getTargets')['targetInfos']\n                if id_or_num not in [i['targetId'] for i in j]:\n                    raise RuntimeError(_S._lang.join(_S._lang.NO_SUCH_TAB, ARG=id_or_num, ALL_TABS=self.tab_ids))\n\n        elif title == url is None and tab_type == 'page':\n            id_or_num = self.tab_ids[0]\n\n        else:\n            tabs = self._get_tabs(title=title, url=url, tab_type=tab_type, as_id=True)\n            if tabs:\n                id_or_num = tabs[0]\n            else:\n                raise RuntimeError(_S._lang.join(_S._lang.NO_SUCH_TAB,\n                                                 ARGS={'id_or_num': id_or_num, 'title': title, 'url': url,\n                                                       'tab_type': tab_type}))\n\n        if as_id:\n            return id_or_num\n        with self._lock:\n            return MixTab(self, id_or_num) if mix else ChromiumTab(self, id_or_num)\n\n    def _get_tabs(self, title=None, url=None, tab_type='page', mix=True, as_id=False):\n        if self._ws_only:\n            tabs = self._run_cdp('Target.getTargets')['targetInfos']\n            _id = 'targetId'\n        else:\n            tabs = self._driver.get(f'http://{self.address}/json').json()  # 不要改用cdp\n            _id = 'id'\n\n        if isinstance(tab_type, str):\n            tab_type = {tab_type}\n        elif isinstance(tab_type, (list, tuple, set)):\n            tab_type = set(tab_type)\n        elif tab_type is not None:\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'tab_type',\n                                           ALLOW_TYPE='set, list, tuple, str, None', CURR_VAL=tab_type))\n\n        tabs = [i for i in tabs if ((title is None or title in i['title']) and (url is None or url in i['url'])\n                                    and (tab_type is None or i['type'] in tab_type)\n                                    and i['title'] != 'chrome-extension://neajdppkdcdipfabeoofebfddakdcjhd/audio.html')]\n        if as_id:\n            return [tab[_id] for tab in tabs]\n        with self._lock:\n            if mix:\n                return [MixTab(self, tab[_id]) for tab in tabs]\n            else:\n                return [ChromiumTab(self, tab[_id]) for tab in tabs]\n\n    def _run_cdp(self, cmd, **cmd_args):\n        ignore = cmd_args.pop('_ignore', None)\n        r = self._driver.run(cmd, **cmd_args)\n        return r if __ERROR__ not in r else raise_error(r, self, ignore)\n\n    def _get_driver(self, tab_id, owner=None):\n        d = self._drivers.pop(tab_id, None)\n        if not d:\n            if self._ws_only:\n                d = Driver(tab_id, self._ws_address)\n                d.session_id = d.run('Target.attachToTarget', targetId=tab_id, flatten=True)['sessionId']\n            else:\n                d = Driver(tab_id, f'ws://{self.address}/devtools/page/{tab_id}')\n        d.owner = owner\n        self._all_drivers.setdefault(tab_id, set()).add(d)\n        return d\n\n    def _onTargetCreated(self, **kwargs):\n        if (kwargs['targetInfo']['type'] in ('page', 'webview')\n                and kwargs['targetInfo']['targetId'] not in self._all_drivers\n                and not kwargs['targetInfo']['url'].startswith('devtools://')):\n            try:\n                tab_id = kwargs['targetInfo']['targetId']\n                self._frames[tab_id] = tab_id\n                if self._ws_only:\n                    d = Driver(tab_id, self._ws_address)\n                    d.session_id = d.run('Target.attachToTarget', targetId=tab_id, flatten=True)['sessionId']\n                else:\n                    d = Driver(tab_id, f'ws://{self.address}/devtools/page/{tab_id}')\n                self._relation[tab_id] = kwargs['targetInfo'].get('openerId', None)\n                self._drivers[tab_id] = d\n                self._all_drivers.setdefault(tab_id, set()).add(d)\n                self._newest_tab_id = tab_id\n            except WebSocketBadStatusException:\n                pass\n\n    def _onTargetDestroyed(self, **kwargs):\n        tab_id = kwargs['targetId']\n        self._dl_mgr.clear_tab_info(tab_id)\n        for key in [k for k, i in self._frames.items() if i == tab_id]:\n            self._frames.pop(key, None)\n        for d in self._all_drivers.get(tab_id, tuple()):\n            d.stop()\n        self._drivers.pop(tab_id, None)\n        self._all_drivers.pop(tab_id, None)\n        self._relation.pop(tab_id, None)\n\n    def _on_disconnect(self):\n        if not self._disconnect_flag:\n            Chromium._BROWSERS.pop(self.id, None)\n            if self._chromium_options.is_auto_port and self._chromium_options.user_data_path:\n                path = Path(self._chromium_options.user_data_path)\n                end_time = perf_counter() + 7\n                while perf_counter() < end_time:\n                    if not path.exists():\n                        break\n                    try:\n                        rmtree(path)\n                        break\n                    except (PermissionError, FileNotFoundError, OSError):\n                        pass\n                    sleep(.03)\n\n\ndef handle_options(addr_or_opts):\n    if not addr_or_opts:\n        _chromium_options = ChromiumOptions(addr_or_opts)\n        if _chromium_options.is_auto_port:\n            port, path = PortFinder(_chromium_options.tmp_path).get_port(_chromium_options.is_auto_port)\n            _chromium_options._address = f'127.0.0.1:{port}'\n            _chromium_options.set_user_data_path(path)\n\n    elif isinstance(addr_or_opts, ChromiumOptions):\n        if addr_or_opts.is_auto_port:\n            port, path = PortFinder(addr_or_opts.tmp_path).get_port(addr_or_opts.is_auto_port)\n            addr_or_opts._address = f'127.0.0.1:{port}'\n            addr_or_opts.set_user_data_path(path)\n        _chromium_options = addr_or_opts\n\n    elif isinstance(addr_or_opts, str) and ':' in addr_or_opts:\n        _chromium_options = ChromiumOptions()\n        _chromium_options.set_address(addr_or_opts)\n\n    elif isinstance(addr_or_opts, int):\n        _chromium_options = ChromiumOptions()\n        _chromium_options.set_local_port(addr_or_opts)\n\n    else:\n        raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'addr_or_opts',\n                                       ALLOW_TYPE=_S._lang.IP_OR_OPTIONS, CURR_VAL=addr_or_opts))\n\n    return _chromium_options\n\n\ndef run_browser(chromium_options):\n    ws_only = False\n    if chromium_options.ws_address:\n        try:\n            driver = BrowserDriver('', chromium_options.ws_address, None)\n        except EnvironmentError:\n            raise\n        except Exception as e:\n            raise BrowserConnectError(_S._lang.join(_S._lang.BROWSER_CONNECT_ERR2, INFO=str(e)))\n        browser_id = driver.run('Target.getTargets')\n        if 'error' in browser_id:\n            raise BrowserConnectError(_S._lang.join(_S._lang.BROWSER_CONNECT_ERR2, INFO=browser_id['error']))\n        browser_id = browser_id['targetInfos'][0]['browserContextId']\n        is_headless = 'headless' in driver.run('Browser.getVersion')['userAgent'].lower()\n        driver.id = browser_id\n        if 'devtools/browser' not in chromium_options.ws_address:\n            return is_headless, browser_id, True, True, driver\n\n        s = Session()\n        s.trust_env = False\n        s.keep_alive = False\n        try:\n            ws = s.get(f'http://{chromium_options.address}/json/version', headers={'Connection': 'close'}, timeout=2)\n            if not ws:\n                ws_only = True\n            else:\n                ws.close()\n        except:\n            ws_only = True\n        s.close()\n        return is_headless, browser_id, True, ws_only, driver\n\n    is_exists = connect_browser(chromium_options)\n    try:\n        s = Session()\n        s.trust_env = False\n        s.keep_alive = False\n        ws = s.get(f'http://{chromium_options.address}/json/version', headers={'Connection': 'close'}, timeout=2)\n        if not ws and not chromium_options.ws_address:\n            raise BrowserConnectError(_S._lang.BROWSER_CONNECT_ERR2)\n        json = ws.json()\n        browser_id = json['webSocketDebuggerUrl'].split('/')[-1]\n        is_headless = 'headless' in json['User-Agent'].lower()\n        ws.close()\n        s.close()\n    except KeyError:\n        raise BrowserConnectError(_S._lang.BROWSER_NOT_FOR_CONTROL)\n    except:\n        raise BrowserConnectError(_S._lang.BROWSER_CONNECT_ERR2)\n    driver = BrowserDriver('', f'ws://{chromium_options.address}/devtools/browser/{browser_id}', None)\n    browser_id = driver.run('Target.getTargets')\n    if 'error' in browser_id:\n        raise BrowserConnectError(_S._lang.join(_S._lang.BROWSER_CONNECT_ERR2, INFO=browser_id['error']))\n    browser_id = browser_id['targetInfos'][0]['browserContextId']\n\n    return is_headless, browser_id, is_exists, ws_only, driver\n\n\ndef _new_tab_by_js(browser: Chromium, url, tab_type, new_window):\n    mix = tab_type == MixTab\n    tab = browser._get_tab(mix=mix)\n    if url and not match(r'^.*?://.*', url):\n        raise IncorrectURLError(_S._lang.INVALID_URL, url=url)\n    url = f'\"{url}\"' if url else '\"\"'\n    new = 'target=\"_new\"' if new_window else 'target=\"_blank\"'\n    tid = browser._newest_tab_id\n    tab.run_js(f'window.open({url}, {new})')\n    tid = browser.wait.new_tab(curr_tab=tid)\n    return browser._get_tab(tid, mix=mix)\n"
  },
  {
    "path": "DrissionPage/_base/chromium.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom threading import Lock\nfrom typing import List, Optional, Set, Dict, Union, Tuple, Literal, Any\n\nfrom .driver import BrowserDriver, Driver\nfrom .._configs.chromium_options import ChromiumOptions\nfrom .._configs.session_options import SessionOptions\nfrom .._functions.cookies import CookiesList\nfrom .._pages.chromium_base import Timeout, ChromiumBase\nfrom .._pages.chromium_tab import ChromiumTab\nfrom .._pages.mix_tab import MixTab\nfrom .._units.downloader import DownloadManager\nfrom .._units.setter import BrowserSetter\nfrom .._units.states import BrowserStates\nfrom .._units.waiter import BrowserWaiter\n\n\nclass Chromium(object):\n    _BROWSERS: dict = ...\n    _lock: Lock = ...\n\n    id: str = ...\n    address: str = ...\n    _ws_address: str = ...\n    version: str = ...\n    retry_times: int = ...\n    retry_interval: float = ...\n\n    _set: Optional[BrowserSetter] = ...\n    _wait: Optional[BrowserWaiter] = ...\n    _states: Optional[BrowserStates] = ...\n    _chromium_options: ChromiumOptions = ...\n    _session_options: SessionOptions = ...\n    _driver: BrowserDriver = ...\n    _frames: dict = ...\n    _drivers: Dict[str, Driver] = ...\n    _all_drivers: Dict[str, Set[Driver]] = ...\n    _relation: Dict[str, Optional[str]] = ...\n    _process_id: Optional[int] = ...\n    _dl_mgr: DownloadManager = ...\n    _timeouts: Timeout = ...\n    _load_mode: str = ...\n    _download_path: str = ...\n    _auto_handle_alert: Optional[bool] = ...\n    _is_exists: bool = ...\n    _is_headless: bool = ...\n    _ws_only: bool = ...\n    _disconnect_flag: bool = ...\n    _none_ele_return_value: bool = ...\n    _none_ele_value: Any = ...\n    _newest_tab_id: Optional[str] = ...\n\n    def __new__(cls,\n                addr_or_opts: Union[str, int, ChromiumOptions] = None,\n                session_options: Union[SessionOptions, None, False] = None):\n        \"\"\"\n        :param addr_or_opts: 浏览器地址:端口、ws地址、ChromiumOptions对象或端口数字（int）\n        :param session_options: 使用双模Tab时使用的默认Session配置，为None使用ini文件配置，为False不从ini读取\n        \"\"\"\n        ...\n\n    def __init__(self, addr_or_opts: Union[str, int, ChromiumOptions] = None,\n                 session_options: Union[SessionOptions, None, False] = None):\n        \"\"\"\n        :param addr_or_opts: 浏览器地址:端口、ws地址、ChromiumOptions对象或端口数字（int）\n        :param session_options: 使用双模Tab时使用的默认Session配置，为None使用ini文件配置，为False不从ini读取\n        \"\"\"\n        ...\n\n    @property\n    def user_data_path(self) -> str:\n        \"\"\"返回用户文件夹路径\"\"\"\n        ...\n\n    @property\n    def process_id(self) -> Optional[int]:\n        \"\"\"返回浏览器进程id\"\"\"\n        ...\n\n    @property\n    def timeout(self) -> float:\n        \"\"\"返回基础超时设置\"\"\"\n        ...\n\n    @property\n    def timeouts(self) -> Timeout:\n        \"\"\"返回所有超时设置\"\"\"\n        ...\n\n    @property\n    def load_mode(self) -> Literal['none', 'normal', 'eager']:\n        \"\"\"返回页面加载模式，包括 'none', 'normal', 'eager' 三种\"\"\"\n        ...\n\n    @property\n    def download_path(self) -> str:\n        \"\"\"返回默认下载路径\"\"\"\n        ...\n\n    @property\n    def set(self) -> BrowserSetter:\n        \"\"\"返回用于设置的对象\"\"\"\n        ...\n\n    @property\n    def states(self) -> BrowserStates:\n        \"\"\"返回用于获取状态的对象\"\"\"\n        ...\n\n    @property\n    def wait(self) -> BrowserWaiter:\n        \"\"\"返回用于等待的对象\"\"\"\n        ...\n\n    @property\n    def tabs_count(self) -> int:\n        \"\"\"返回标签页数量，只统计page、webview类型\"\"\"\n        ...\n\n    @property\n    def tab_ids(self) -> List[str]:\n        \"\"\"返回所有标签页id组成的列表，只统计page、webview类型\"\"\"\n        ...\n\n    @property\n    def latest_tab(self) -> Union[MixTab, str]:\n        \"\"\"返回最新的标签页，最新标签页指最后创建或最后被激活的\n        当Settings.singleton_tab_obj==True时返回Tab对象，否则返回tab id\"\"\"\n        ...\n\n    def cookies(self, all_info: bool = False) -> CookiesList:\n        \"\"\"以list格式返回所有域名的cookies\n        :param all_info: 是否返回所有内容，False则只返回name, value, domain\n        :return: cookies组成的列表\n        \"\"\"\n        ...\n\n    def new_tab(self,\n                url: str = None,\n                new_window: bool = False,\n                background: bool = False,\n                new_context: bool = False) -> MixTab:\n        \"\"\"新建一个标签页\n        :param url: 新标签页跳转到的网址，为None时新建空标签页\n        :param new_window: 是否在新窗口打开标签页，隐身模式下无效\n        :param background: 是否不激活新标签页，隐身模式和访客模式及new_window为True时无效\n        :param new_context: 是否创建独立环境，隐身模式和访客模式下无效\n        :return: 新标签页对象\n        \"\"\"\n        ...\n\n    def get_tab(self,\n                id_or_num: Union[str, int] = None,\n                title: str = None,\n                url: str = None,\n                tab_type: Union[str, list, tuple] = 'page',\n                as_id: bool = False) -> Union[MixTab, str]:\n        \"\"\"获取一个标签页对象，id_or_num不为None时，后面几个参数无效\n        :param id_or_num: 要获取的标签页id或序号，序号从1开始，可传入负数获取倒数第几个，不是视觉排列顺序，而是激活顺序\n        :param title: 要匹配title的文本，模糊匹配，为None则匹配所有\n        :param url: 要匹配url的文本，模糊匹配，为None则匹配所有\n        :param tab_type: tab类型，可用列表输入多个，如 'page', 'iframe' 等，为None则匹配所有\n        :param as_id: 是否返回标签页id而不是标签页对象\n        :return: Tab对象\n        \"\"\"\n        ...\n\n    def get_tabs(self,\n                 title: str = None,\n                 url: str = None,\n                 tab_type: Union[str, list, tuple] = 'page',\n                 as_id: bool = False) -> List[MixTab, str]:\n        \"\"\"查找符合条件的tab，返回它们组成的列表，title和url是与关系\n        :param title: 要匹配title的文本\n        :param url: 要匹配url的文本\n        :param tab_type: tab类型，可用列表输入多个\n        :param as_id: 是否返回标签页id而不是标签页对象\n        :return: Tab对象列表\n        \"\"\"\n        ...\n\n    def close_tabs(self,\n                   tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]],\n                   Tuple[Union[str, ChromiumTab]]],\n                   others: bool = False) -> None:\n        \"\"\"关闭传入的标签页，可传入多个\n        :param tabs_or_ids: 指定的标签页对象或id，可用列表或元组传入多个\n        :param others: 是否关闭指定标签页之外的\n        :return: None\n        \"\"\"\n        ...\n\n    def _close_tab(self, tab: Union[ChromiumBase, str]):\n        \"\"\"关闭一个标签页\n        :param tab: 标签页对象或id\n        :return: None\n        \"\"\"\n\n    def activate_tab(self, id_ind_tab: Union[int, str, ChromiumTab]) -> None:\n        \"\"\"使一个标签页显示到前端\n        :param id_ind_tab: 标签页id（str）、Tab对象或标签页序号（int），序号从1开始\n        :return: None\n        \"\"\"\n        ...\n\n    def reconnect(self) -> None:\n        \"\"\"断开重连\"\"\"\n        ...\n\n    def clear_cache(self, cache: bool = True, cookies: bool = True) -> None:\n        \"\"\"清除缓存，可选要清除的项\n        :param cache: 是否清除cache\n        :param cookies: 是否清除cookies\n        :return: None\n        \"\"\"\n        ...\n\n    def quit(self, timeout: float = 5, force: bool = False, del_data: bool = False) -> None:\n        \"\"\"关闭浏览器\n        :param timeout: 等待浏览器关闭超时时间（秒）\n        :param force: 是否立刻强制终止进程\n        :param del_data: 是否删除用户文件夹\n        :return: None\n        \"\"\"\n        ...\n\n    def _new_tab(self,\n                 mix: bool = True,\n                 url: str = None,\n                 new_window: bool = False,\n                 background: bool = False,\n                 new_context: bool = False) -> Union[ChromiumTab, MixTab]:\n        \"\"\"新建一个标签页\n        :param mix: 是否创建MixTab\n        :param url: 新标签页跳转到的网址\n        :param new_window: 是否在新窗口打开标签页\n        :param background: 是否不激活新标签页，如new_window为True则无效\n        :param new_context: 是否创建新的上下文\n        :return: 新标签页对象\n        \"\"\"\n        ...\n\n    def _get_tab(self,\n                 id_or_num: Union[str, int] = None,\n                 title: str = None,\n                 url: str = None,\n                 tab_type: Union[str, list, tuple] = 'page',\n                 mix: bool = True,\n                 as_id: bool = False) -> Union[ChromiumTab, str]:\n        \"\"\"获取一个标签页对象，id_or_num不为None时，后面几个参数无效\n        :param id_or_num: 要获取的标签页id或序号，序号从1开始，可传入负数获取倒数第几个，不是视觉排列顺序，而是激活顺序\n        :param title: 要匹配title的文本，模糊匹配，为None则匹配所有\n        :param url: 要匹配url的文本，模糊匹配，为None则匹配所有\n        :param tab_type: tab类型，可用列表输入多个，如 'page', 'iframe' 等，为None则匹配所有\n        :param mix: 是否返回可切换模式的Tab对象\n        :param as_id: 是否返回标签页id而不是标签页对象，mix=False时无效\n        :return: Tab对象\n        \"\"\"\n        ...\n\n    def _get_tabs(self,\n                  title: str = None,\n                  url: str = None,\n                  tab_type: Union[str, list, tuple] = 'page',\n                  mix: bool = True,\n                  as_id: bool = False) -> List[ChromiumTab, str]:\n        \"\"\"查找符合条件的tab，返回它们组成的列表，title和url是与关系\n        :param title: 要匹配title的文本\n        :param url: 要匹配url的文本\n        :param tab_type: tab类型，可用列表输入多个\n        :param mix: 是否返回可切换模式的Tab对象\n        :param as_id: 是否返回标签页id而不是标签页对象，mix=False时无效\n        :return: Tab对象列表\n        \"\"\"\n        ...\n\n    def _run_cdp(self, cmd, **cmd_args) -> dict:\n        \"\"\"执行Chrome DevTools Protocol语句\n        :param cmd: 协议项目\n        :param cmd_args: 参数\n        :return: 执行的结果\n        \"\"\"\n        ...\n\n    def _get_driver(self, tab_id: str, owner=None) -> Driver:\n        \"\"\"新建并返回指定tab id的Driver\n        :param tab_id: 标签页id\n        :param owner: 使用该驱动的对象\n        :return: Driver对象\n        \"\"\"\n        ...\n\n    def _onTargetCreated(self, **kwargs): ...\n\n    def _onTargetDestroyed(self, **kwargs): ...\n\n    def _on_disconnect(self): ...\n\n\ndef handle_options(addr_or_opts):\n    \"\"\"设置浏览器启动属性\n    :param addr_or_opts: 'ip:port'、ChromiumOptions、Driver\n    :return: 返回ChromiumOptions对象\n    \"\"\"\n    ...\n\n\ndef run_browser(chromium_options)->Tuple[bool, str, bool, bool, BrowserDriver]:\n    \"\"\"连接浏览器\n    :param chromium_options: ChromiumOptions对象\n    :return: 返回(是否无头, 浏览器id, 是否接管已存在, 是否只能用ws连接, 浏览器连接驱动)\n    \"\"\"\n    ...\n"
  },
  {
    "path": "DrissionPage/_base/driver.py",
    "content": "# -*- coding: utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom json import dumps, loads, JSONDecodeError\r\nfrom queue import Queue, Empty\r\nfrom threading import Thread\r\nfrom time import perf_counter, sleep\r\n\r\nfrom requests import Session\r\nfrom requests import adapters\r\nfrom websocket import (WebSocketTimeoutException, WebSocketConnectionClosedException, create_connection,\r\n                       WebSocketException, WebSocketBadStatusException)\r\n\r\nfrom .._functions.settings import Settings as _S\r\nfrom ..errors import PageDisconnectedError, BrowserConnectError\r\n\r\nadapters.DEFAULT_RETRIES = 5\r\n\r\n\r\nclass Driver(object):\r\n    def __init__(self, _id, address, owner=None):\r\n        self.id = _id\r\n        self.address = address\r\n        self.owner = owner\r\n        self.alert_flag = False  # 标记alert出现，跳过一条请求后复原\r\n\r\n        self._cur_id = 0\r\n        self._ws = None\r\n\r\n        self._recv_th = Thread(target=self._recv_loop)\r\n        self._handle_event_th = Thread(target=self._handle_event_loop)\r\n        self._recv_th.daemon = True\r\n        self._handle_event_th.daemon = True\r\n        self._handle_immediate_event_th = None\r\n\r\n        self.is_running = False\r\n        self.session_id = None\r\n\r\n        self.event_handlers = {}\r\n        self.immediate_event_handlers = {}\r\n        self.method_results = {}\r\n        self.event_queue = Queue()\r\n        self.immediate_event_queue = Queue()\r\n\r\n        self.start()\r\n\r\n    def _send(self, message, timeout=None):\r\n        self._cur_id += 1\r\n        ws_id = self._cur_id\r\n        message['id'] = ws_id\r\n        message_json = dumps(message)\r\n\r\n        end_time = perf_counter() + timeout if timeout is not None else None\r\n        self.method_results[ws_id] = Queue()\r\n        try:\r\n            self._ws.send(message_json)\r\n            if timeout == 0:\r\n                self.method_results.pop(ws_id, None)\r\n                return {'id': ws_id, 'result': {}}\r\n\r\n        except (OSError, WebSocketConnectionClosedException):\r\n            self.method_results.pop(ws_id, None)\r\n            return {'error': {'message': 'connection disconnected'}, 'type': 'connection_error'}\r\n\r\n        while self.is_running:\r\n            try:\r\n                result = self.method_results[ws_id].get(timeout=.2)\r\n                self.method_results.pop(ws_id, None)\r\n                return result\r\n\r\n            except Empty:\r\n                if self.alert_flag and message['method'].startswith(('Input.', 'Runtime.')):\r\n                    return {'error': {'message': 'alert exists.'}, 'type': 'alert_exists'}\r\n\r\n                if timeout is not None and perf_counter() > end_time:\r\n                    self.method_results.pop(ws_id, None)\r\n                    return {'error': {'message': 'alert exists.'}, 'type': 'alert_exists'} \\\r\n                        if self.alert_flag else {'error': {'message': 'timeout'}, 'type': 'timeout'}\r\n\r\n                continue\r\n\r\n        return {'error': {'message': 'connection disconnected'}, 'type': 'connection_error'}\r\n\r\n    def _recv_loop(self):\r\n        while self.is_running:\r\n            try:\r\n                # self._ws.settimeout(1)\r\n                msg_json = self._ws.recv()\r\n                msg = loads(msg_json)\r\n            except WebSocketTimeoutException:\r\n                continue\r\n            except (WebSocketException, OSError, WebSocketConnectionClosedException, JSONDecodeError):\r\n                self._stop()\r\n                return\r\n\r\n            if 'method' in msg:\r\n                if msg['method'].startswith('Page.javascriptDialog'):\r\n                    self.alert_flag = msg['method'].endswith('Opening')\r\n                function = self.immediate_event_handlers.get(msg['method'])\r\n                if function:\r\n                    self._handle_immediate_event(function, msg['params'])\r\n                else:\r\n                    self.event_queue.put(msg)\r\n\r\n            elif msg.get('id') in self.method_results:\r\n                self.method_results[msg['id']].put(msg)\r\n\r\n    def _handle_event_loop(self):\r\n        while self.is_running:\r\n            try:\r\n                event = self.event_queue.get(timeout=1)\r\n            except Empty:\r\n                continue\r\n\r\n            function = self.event_handlers.get(event['method'])\r\n            if function:\r\n                function(**event['params'])\r\n\r\n            self.event_queue.task_done()\r\n\r\n    def _handle_immediate_event_loop(self):\r\n        while not self.immediate_event_queue.empty():\r\n            function, kwargs = self.immediate_event_queue.get(timeout=1)\r\n            try:\r\n                function(**kwargs)\r\n            except PageDisconnectedError:\r\n                pass\r\n\r\n    def _handle_immediate_event(self, function, kwargs):\r\n        self.immediate_event_queue.put((function, kwargs))\r\n        if self._handle_immediate_event_th is None or not self._handle_immediate_event_th.is_alive():\r\n            self._handle_immediate_event_th = Thread(target=self._handle_immediate_event_loop)\r\n            self._handle_immediate_event_th.daemon = True\r\n            self._handle_immediate_event_th.start()\r\n\r\n    def run(self, _method, **kwargs):\r\n        if not self.is_running:\r\n            return {'error': 'connection disconnected', 'type': 'connection_error'}\r\n\r\n        timeout = kwargs.pop('_timeout', _S.cdp_timeout)\r\n        if self.session_id:\r\n            result = self._send({'method': _method, 'params': kwargs, 'sessionId': self.session_id}, timeout=timeout)\r\n        else:\r\n            result = self._send({'method': _method, 'params': kwargs}, timeout=timeout)\r\n        if 'result' not in result and 'error' in result:\r\n            kwargs['_timeout'] = timeout\r\n            return {'error': result['error']['message'], 'type': result.get('type', 'call_method_error'),\r\n                    'method': _method, 'args': kwargs, 'data': result['error'].get('data')}\r\n        else:\r\n            return result['result']\r\n\r\n    def start(self):\r\n        self.is_running = True\r\n        try:\r\n            self._ws = create_connection(self.address, enable_multithread=True, suppress_origin=True)\r\n        except WebSocketBadStatusException as e:\r\n            if 'Handshake status 403 Forbidden' in str(e):\r\n                raise EnvironmentError(_S._lang.join(_S._lang.UPGRADE_WS))\r\n            else:\r\n                raise\r\n        except ConnectionRefusedError:\r\n            raise BrowserConnectError(_S._lang.BROWSER_NOT_EXIST)\r\n        self._recv_th.start()\r\n        self._handle_event_th.start()\r\n        return True\r\n\r\n    def stop(self):\r\n        self._stop()\r\n        while self._handle_event_th.is_alive() or self._recv_th.is_alive():\r\n            sleep(.01)\r\n        return True\r\n\r\n    def _stop(self):\r\n        if not self.is_running:\r\n            return False\r\n\r\n        self.is_running = False\r\n        if self._ws:\r\n            self._ws.close()\r\n            self._ws = None\r\n\r\n        self.event_handlers.clear()\r\n        self.method_results.clear()\r\n        self.event_queue.queue.clear()\r\n\r\n        if hasattr(self.owner, '_on_disconnect'):\r\n            self.owner._on_disconnect()\r\n\r\n    def set_callback(self, event, callback, immediate=False):\r\n        handler = self.immediate_event_handlers if immediate else self.event_handlers\r\n        if callback:\r\n            handler[event] = callback\r\n        else:\r\n            handler.pop(event, None)\r\n\r\n\r\nclass BrowserDriver(Driver):\r\n    BROWSERS = {}\r\n\r\n    def __new__(cls, _id, address, owner):\r\n        if address in cls.BROWSERS:\r\n            return cls.BROWSERS[address]\r\n        return object.__new__(cls)\r\n\r\n    def __init__(self, _id, address, owner):\r\n        if hasattr(self, '_created'):\r\n            return\r\n        self._created = True\r\n        BrowserDriver.BROWSERS[address] = self\r\n        super().__init__(_id, address, owner)\r\n\r\n    def __repr__(self):\r\n        return f'<BrowserDriver {self.id}>'\r\n\r\n    @staticmethod\r\n    def get(url):\r\n        s = Session()\r\n        s.trust_env = False\r\n        s.keep_alive = False\r\n        r = s.get(url, headers={'Connection': 'close'})\r\n        r.close()\r\n        s.close()\r\n        return r\r\n"
  },
  {
    "path": "DrissionPage/_base/driver.pyi",
    "content": "# -*- coding: utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom queue import Queue\r\nfrom threading import Thread\r\nfrom typing import Union, Callable, Dict, Optional\r\n\r\nfrom requests import Response\r\nfrom websocket import WebSocket\r\n\r\nfrom .._base.chromium import Chromium\r\n\r\n\r\nclass Driver(object):\r\n    id: str\r\n    address: str\r\n    owner = ...\r\n    alert_flag: bool\r\n    _cur_id: int\r\n    _ws: Optional[WebSocket]\r\n    _recv_th: Thread\r\n    _handle_event_th: Thread\r\n    _handle_immediate_event_th: Optional[Thread]\r\n    session_id: Optional[str] = ...\r\n    is_running: bool\r\n    event_handlers: dict\r\n    immediate_event_handlers: dict\r\n    method_results: dict\r\n    event_queue: Queue\r\n    immediate_event_queue: Queue\r\n\r\n    def __init__(self, _id: str, address: str, owner=None):\r\n        \"\"\"\r\n        :param _id: 标签页id\r\n        :param address: 浏览器连接地址\r\n        :param owner: 创建这个驱动的对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _send(self, message: dict, timeout: float = None) -> dict:\r\n        \"\"\"发送信息到浏览器，并返回浏览器返回的信息\r\n        :param message: 发送给浏览器的数据\r\n        :param timeout: 超时时间，为None表示无限\r\n        :return: 浏览器返回的数据\r\n        \"\"\"\r\n        ...\r\n\r\n    def _recv_loop(self) -> None:\r\n        \"\"\"接收浏览器信息的守护线程方法\"\"\"\r\n        ...\r\n\r\n    def _handle_event_loop(self) -> None:\r\n        \"\"\"当接收到浏览器信息，执行已绑定的方法\"\"\"\r\n        ...\r\n\r\n    def _handle_immediate_event_loop(self): ...\r\n\r\n    def _handle_immediate_event(self, function: Callable, kwargs: dict):\r\n        \"\"\"处理立即执行的动作\r\n        :param function: 要运行下方法\r\n        :param kwargs: 方法参数\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def run(self, _method: str, **kwargs) -> dict:\r\n        \"\"\"执行cdp方法\r\n        :param _method: cdp方法名\r\n        :param kwargs: cdp参数\r\n        :return: 执行结果\r\n        \"\"\"\r\n        ...\r\n\r\n    def start(self) -> bool:\r\n        \"\"\"启动连接\"\"\"\r\n        ...\r\n\r\n    def stop(self) -> bool:\r\n        \"\"\"中断连接\"\"\"\r\n        ...\r\n\r\n    def _stop(self) -> None:\r\n        \"\"\"中断连接\"\"\"\r\n        ...\r\n\r\n    def set_callback(self, event: str, callback: Union[Callable, None], immediate: bool = False) -> None:\r\n        \"\"\"绑定cdp event和回调方法\r\n        :param event: cdp event\r\n        :param callback: 绑定到cdp event的回调方法\r\n        :param immediate: 是否要立即处理的动作\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass BrowserDriver(Driver):\r\n    BROWSERS: Dict[str, Driver] = ...\r\n    owner: Chromium = ...\r\n\r\n    def __new__(cls, _id: str, address: str, owner: Chromium):\r\n        \"\"\"\r\n        :param _id: 浏览器id\r\n        :param address: 浏览器连接地址\r\n        :param owner: 浏览器对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def __init__(self, _id: str, address: str, owner: Optional[Chromium]):\r\n        \"\"\"\r\n        :param _id: 浏览器id\r\n        :param address: 浏览器连接地址\r\n        :param owner: 浏览器对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @staticmethod\r\n    def get(url) -> Response:\r\n        \"\"\"\r\n        :param url: 要访问的链接\r\n        :return: Response对象\r\n        \"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_configs/chromium_options.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom re import search\r\nfrom urllib.parse import urlparse\r\n\r\nfrom .options_manage import OptionsManager\r\nfrom .._functions.settings import Settings as _S\r\n\r\n\r\nclass ChromiumOptions(object):\r\n    def __init__(self, read_file=True, ini_path=None):\r\n        self._user_data_path = None\r\n        self._user = 'Default'\r\n        self._prefs_to_del = []\r\n        self.clear_file_flags = False\r\n        self._is_headless = False\r\n        self._ua_set = False\r\n        self.ws_address = ''\r\n\r\n        if read_file is False:\r\n            ini_path = False\r\n            self.ini_path = None\r\n        elif ini_path:\r\n            ini_path = Path(ini_path).absolute()\r\n            if not ini_path.exists():\r\n                raise FileNotFoundError(_S._lang.join(_S._lang.INI_NOT_FOUND, PATH=ini_path))\r\n            self.ini_path = str(ini_path)\r\n        else:\r\n            self.ini_path = str(Path(__file__).parent / 'configs.ini')\r\n\r\n        om = OptionsManager(ini_path)\r\n        options = om.chromium_options\r\n        self._download_path = om.paths.get('download_path', '.') or '.'\r\n        self._tmp_path = om.paths.get('tmp_path', None) or None\r\n        self._arguments = options.get('arguments', [])\r\n        self._browser_path = options.get('browser_path', '')\r\n        self._extensions = options.get('extensions', [])\r\n        self._prefs = options.get('prefs', {})\r\n        self._flags = options.get('flags', {})\r\n        self._address = options.get('address', '')\r\n        self._load_mode = options.get('load_mode', 'normal')\r\n        self._system_user_path = options.get('system_user_path', False)\r\n        self._existing_only = options.get('existing_only', False)\r\n        self._new_env = options.get('new_env', False)\r\n        for i in self._arguments:\r\n            if i.startswith('--headless'):\r\n                self._is_headless = True\r\n                break\r\n\r\n        self._proxy = om.proxies.get('http', None) or om.proxies.get('https', None)\r\n\r\n        user_path = user = False\r\n        for arg in self._arguments:\r\n            if arg.startswith('--user-data-dir='):\r\n                self.set_user_data_path(arg[16:])\r\n                user_path = True\r\n            if arg.startswith('--profile-directory='):\r\n                self.set_user(arg[20:])\r\n                user = True\r\n            if user and user_path:\r\n                break\r\n\r\n        timeouts = om.timeouts\r\n        self._timeouts = {'base': timeouts['base'],\r\n                          'page_load': timeouts['page_load'],\r\n                          'script': timeouts['script']}\r\n\r\n        self._auto_port = options.get('auto_port', False)\r\n\r\n        others = om.others\r\n        self._retry_times = others.get('retry_times', 3)\r\n        self._retry_interval = others.get('retry_interval', 2)\r\n\r\n        return\r\n\r\n    def __repr__(self):\r\n        return f'<ChromiumOptions at {id(self)}>'\r\n\r\n    @property\r\n    def download_path(self):\r\n        return self._download_path\r\n\r\n    @property\r\n    def browser_path(self):\r\n        return self._browser_path\r\n\r\n    @property\r\n    def user_data_path(self):\r\n        return self._user_data_path\r\n\r\n    @property\r\n    def tmp_path(self):\r\n        return self._tmp_path\r\n\r\n    @property\r\n    def user(self):\r\n        return self._user\r\n\r\n    @property\r\n    def load_mode(self):\r\n        return self._load_mode\r\n\r\n    @property\r\n    def timeouts(self):\r\n        return self._timeouts\r\n\r\n    @property\r\n    def proxy(self):\r\n        return self._proxy\r\n\r\n    @property\r\n    def address(self):\r\n        return self._address\r\n\r\n    @property\r\n    def arguments(self):\r\n        return self._arguments\r\n\r\n    @property\r\n    def extensions(self):\r\n        return self._extensions\r\n\r\n    @property\r\n    def preferences(self):\r\n        return self._prefs\r\n\r\n    @property\r\n    def flags(self):\r\n        return self._flags\r\n\r\n    @property\r\n    def system_user_path(self):\r\n        return self._system_user_path\r\n\r\n    @property\r\n    def is_existing_only(self):\r\n        return self._existing_only\r\n\r\n    @property\r\n    def is_auto_port(self):\r\n        return self._auto_port\r\n\r\n    @property\r\n    def retry_times(self):\r\n        return self._retry_times\r\n\r\n    @property\r\n    def retry_interval(self):\r\n        return self._retry_interval\r\n\r\n    @property\r\n    def is_headless(self):\r\n        return self._is_headless\r\n\r\n    def set_retry(self, times=None, interval=None):\r\n        if times is not None:\r\n            self._retry_times = times\r\n        if interval is not None:\r\n            self._retry_interval = interval\r\n        return self\r\n\r\n    def set_argument(self, arg, value=None):\r\n        self.remove_argument(arg)\r\n        if value is not False:\r\n            if arg == '--headless':\r\n                if value == 'false':\r\n                    self._is_headless = False\r\n                else:\r\n                    if value is None:\r\n                        value = 'new'\r\n                    self._arguments.append(f'--headless={value}')\r\n                    self._is_headless = True\r\n            else:\r\n                arg_str = arg if value is None else f'{arg}={value}'\r\n                self._arguments.append(arg_str)\r\n        elif arg == '--headless':\r\n            self._is_headless = False\r\n        return self\r\n\r\n    def remove_argument(self, value):\r\n        elements_to_delete = [arg for arg in self._arguments if arg == value or arg.startswith(f'{value}=')]\r\n        if not elements_to_delete:\r\n            return self\r\n\r\n        if len(elements_to_delete) == 1:\r\n            self._arguments.remove(elements_to_delete[0])\r\n        else:\r\n            self._arguments = [arg for arg in self._arguments if arg not in elements_to_delete]\r\n\r\n        return self\r\n\r\n    def add_extension(self, path):\r\n        self._extensions.append(path)\r\n        return self\r\n\r\n    def remove_extensions(self):\r\n        self._extensions = []\r\n        return self\r\n\r\n    def set_pref(self, arg, value):\r\n        self._prefs[arg] = value\r\n        return self\r\n\r\n    def remove_pref(self, arg):\r\n        self._prefs.pop(arg, None)\r\n        return self\r\n\r\n    def remove_pref_from_file(self, arg):\r\n        self._prefs_to_del.append(arg)\r\n        return self\r\n\r\n    def set_flag(self, flag, value=None):\r\n        if value is False:\r\n            self._flags.pop(flag, None)\r\n        else:\r\n            self._flags[flag] = value\r\n        return self\r\n\r\n    def clear_flags_in_file(self):\r\n        self.clear_file_flags = True\r\n        return self\r\n\r\n    def clear_flags(self):\r\n        self._flags = {}\r\n        return self\r\n\r\n    def clear_arguments(self):\r\n        self._arguments = []\r\n        return self\r\n\r\n    def clear_prefs(self):\r\n        self._prefs = {}\r\n        return self\r\n\r\n    def set_timeouts(self, base=None, page_load=None, script=None):\r\n        if base is not None:\r\n            self._timeouts['base'] = base\r\n        if page_load is not None:\r\n            self._timeouts['page_load'] = page_load\r\n        if script is not None:\r\n            self._timeouts['script'] = script\r\n\r\n        return self\r\n\r\n    def set_user(self, user='Default'):\r\n        self.set_argument('--profile-directory', user)\r\n        self._user = user\r\n        return self\r\n\r\n    def headless(self, on_off=True):\r\n        on_off = 'new' if on_off else on_off\r\n        return self.set_argument('--headless', on_off)\r\n\r\n    def no_imgs(self, on_off=True):\r\n        on_off = None if on_off else False\r\n        return self.set_argument('--blink-settings=imagesEnabled=false', on_off)\r\n\r\n    def no_js(self, on_off=True):\r\n        on_off = None if on_off else False\r\n        return self.set_argument('--disable-javascript', on_off)\r\n\r\n    def mute(self, on_off=True):\r\n        on_off = None if on_off else False\r\n        return self.set_argument('--mute-audio', on_off)\r\n\r\n    def incognito(self, on_off=True):\r\n        on_off = None if on_off else False\r\n        self.set_argument('--incognito', on_off)\r\n        return self.set_argument('--inprivate', on_off)  # edge\r\n\r\n    def new_env(self, on_off=True):\r\n        self._new_env = on_off\r\n        return self\r\n\r\n    def ignore_certificate_errors(self, on_off=True):\r\n        on_off = None if on_off else False\r\n        return self.set_argument('--ignore-certificate-errors', on_off)\r\n\r\n    def set_user_agent(self, user_agent):\r\n        return self.set_argument('--user-agent', user_agent)\r\n\r\n    def set_proxy(self, proxy):\r\n        if search(r'.*?:.*?@.*?\\..*', proxy):\r\n            print(_S._lang.UNSUPPORTED_USER_PROXY)\r\n        if proxy.lower().startswith('socks'):\r\n            print(_S._lang.UNSUPPORTED_SOCKS_PROXY)\r\n        self._proxy = proxy\r\n        return self.set_argument('--proxy-server', proxy)\r\n\r\n    def set_load_mode(self, value):\r\n        if value not in ('normal', 'eager', 'none'):\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'value',\r\n                                           ALLOW_VAL=\"'normal', 'eager', 'none'\", CURR_VAL=value))\r\n        self._load_mode = value.lower()\r\n        return self\r\n\r\n    def set_paths(self, browser_path=None, local_port=None, address=None, download_path=None,\r\n                  user_data_path=None, cache_path=None):\r\n        \"\"\"快捷的路径设置函数，即将废弃\r\n        :param browser_path: 浏览器可执行文件路径\r\n        :param local_port: 本地端口号\r\n        :param address: 调试浏览器地址，例：127.0.0.1:9222\r\n        :param download_path: 下载文件路径\r\n        :param user_data_path: 用户数据路径\r\n        :param cache_path: 缓存路径\r\n        :return: 当前对象\r\n        \"\"\"\r\n        if browser_path is not None:\r\n            self.set_browser_path(browser_path)\r\n\r\n        if local_port is not None:\r\n            self.set_local_port(local_port)\r\n\r\n        if address is not None:\r\n            self.set_address(address)\r\n\r\n        if download_path is not None:\r\n            self.set_download_path(download_path)\r\n\r\n        if user_data_path is not None:\r\n            self.set_user_data_path(user_data_path)\r\n\r\n        if cache_path is not None:\r\n            self.set_cache_path(cache_path)\r\n\r\n        return self\r\n\r\n    def set_local_port(self, port):\r\n        self._address = f'127.0.0.1:{port}'\r\n        self._auto_port = False\r\n        self.ws_address = ''\r\n        return self\r\n\r\n    def set_address(self, address):\r\n        address = address.replace('localhost', '127.0.0.1')\r\n        self.ws_address = ''\r\n        if address.startswith('http'):\r\n            address = address.lstrip('htps:/')\r\n        elif address.startswith(('ws:', 'wss:')):\r\n            self.ws_address = address\r\n            address = urlparse(address).netloc\r\n        self._address = address\r\n        self._auto_port = False\r\n        return self\r\n\r\n    def set_browser_path(self, path):\r\n        self._browser_path = str(path)\r\n        return self\r\n\r\n    def set_download_path(self, path):\r\n        self._download_path = '.' if path is None else str(path)\r\n        return self\r\n\r\n    def set_tmp_path(self, path):\r\n        self._tmp_path = str(path)\r\n        return self\r\n\r\n    def set_user_data_path(self, path):\r\n        u = str(path)\r\n        self.set_argument('--user-data-dir', u)\r\n        self._user_data_path = u\r\n        self._auto_port = False\r\n        return self\r\n\r\n    def set_cache_path(self, path):\r\n        self.set_argument('--disk-cache-dir', str(path))\r\n        return self\r\n\r\n    def use_system_user_path(self, on_off=True):\r\n        self._system_user_path = on_off\r\n        return self\r\n\r\n    def auto_port(self, on_off=True, scope=None):\r\n        if on_off:\r\n            self._auto_port = scope if scope else (9600, 59600)\r\n        else:\r\n            self._auto_port = False\r\n        self._address = ''\r\n        self.ws_address = ''\r\n        return self\r\n\r\n    def existing_only(self, on_off=True):\r\n        self._existing_only = on_off\r\n        return self\r\n\r\n    def save(self, path=None):\r\n        if path == 'default':\r\n            path = (Path(__file__).parent / 'configs.ini').absolute()\r\n\r\n        elif path is None:\r\n            if self.ini_path:\r\n                path = Path(self.ini_path).absolute()\r\n            else:\r\n                path = (Path(__file__).parent / 'configs.ini').absolute()\r\n\r\n        else:\r\n            path = Path(path).absolute()\r\n\r\n        path = path / 'config.ini' if path.is_dir() else path\r\n\r\n        if path.exists():\r\n            om = OptionsManager(path)\r\n        else:\r\n            om = OptionsManager(self.ini_path or (Path(__file__).parent / 'configs.ini'))\r\n\r\n        # 设置chromium_options\r\n        attrs = ('address', 'browser_path', 'arguments', 'extensions', 'user', 'load_mode',\r\n                 'auto_port', 'system_user_path', 'existing_only', 'flags', 'new_env')\r\n        for i in attrs:\r\n            om.set_item('chromium_options', i, self.__getattribute__(f'_{i}'))\r\n        # 设置代理\r\n        om.set_item('proxies', 'http', self._proxy or '')\r\n        om.set_item('proxies', 'https', self._proxy or '')\r\n        # 设置路径\r\n        om.set_item('paths', 'download_path', self._download_path or '')\r\n        om.set_item('paths', 'tmp_path', self._tmp_path or '')\r\n        # 设置timeout\r\n        om.set_item('timeouts', 'base', self._timeouts['base'])\r\n        om.set_item('timeouts', 'page_load', self._timeouts['page_load'])\r\n        om.set_item('timeouts', 'script', self._timeouts['script'])\r\n        # 设置重试\r\n        om.set_item('others', 'retry_times', self.retry_times)\r\n        om.set_item('others', 'retry_interval', self.retry_interval)\r\n        # 设置prefs\r\n        om.set_item('chromium_options', 'prefs', self._prefs)\r\n\r\n        path = str(path)\r\n        om.save(path)\r\n\r\n        return path\r\n\r\n    def save_to_default(self):\r\n        return self.save('default')\r\n"
  },
  {
    "path": "DrissionPage/_configs/chromium_options.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Union, Any, Literal, Optional, Tuple\r\n\r\n\r\nclass ChromiumOptions(object):\r\n    ini_path: Optional[str] = ...\r\n    _user_data_path: Optional[str] = ...\r\n    _download_path: str = ...\r\n    _tmp_path: str = ...\r\n    _arguments: list = ...\r\n    _browser_path: str = ...\r\n    _user: str = ...\r\n    _load_mode: str = ...\r\n    _timeouts: dict = ...\r\n    _proxy: str = ...\r\n    _address: str = ...\r\n    _extensions: list = ...\r\n    _prefs: dict = ...\r\n    _flags: dict = ...\r\n    _prefs_to_del: list = ...\r\n    _new_env: bool = ...\r\n    clear_file_flags: bool = ...\r\n    _auto_port: Union[Tuple[int, int], False] = ...\r\n    _system_user_path: bool = ...\r\n    _existing_only: bool = ...\r\n    _retry_times: int = ...\r\n    _retry_interval: float = ...\r\n    _is_headless: bool = ...\r\n    _ua_set: bool = ...\r\n    ws_address: Optional[str] = ...\r\n\r\n    def __init__(self,\r\n                 read_file: [bool, None] = True,\r\n                 ini_path: Union[str, Path] = None):\r\n        \"\"\"\r\n        :param read_file: 是否从默认ini文件中读取配置信息\r\n        :param ini_path: ini文件路径，为None则读取默认ini文件\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def download_path(self) -> str:\r\n        \"\"\"默认下载路径文件路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def browser_path(self) -> str:\r\n        \"\"\"浏览器启动文件路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def user_data_path(self) -> str:\r\n        \"\"\"返回用户数据文件夹路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def tmp_path(self) -> Optional[str]:\r\n        \"\"\"返回临时文件夹路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def user(self) -> str:\r\n        \"\"\"返回用户配置文件夹名称\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def load_mode(self) -> str:\r\n        \"\"\"返回页面加载策略，'normal', 'eager', 'none'\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def timeouts(self) -> dict:\r\n        \"\"\"返回timeouts设置\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def proxy(self) -> str:\r\n        \"\"\"返回代理设置\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def address(self) -> str:\r\n        \"\"\"返回浏览器地址，ip:port\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def arguments(self) -> list:\r\n        \"\"\"返回浏览器命令行设置列表\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def extensions(self) -> list:\r\n        \"\"\"以list形式返回要加载的插件路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def preferences(self) -> dict:\r\n        \"\"\"返回用户首选项配置\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def flags(self) -> dict:\r\n        \"\"\"返回实验项配置\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def system_user_path(self) -> bool:\r\n        \"\"\"返回是否使用系统安装的浏览器所使用的用户数据文件夹\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_existing_only(self) -> bool:\r\n        \"\"\"返回是否只接管现有浏览器方式\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_auto_port(self) -> Union[bool, Tuple[int, int]]:\r\n        \"\"\"返回是否使用自动端口和用户文件，如指定范围则返回范围tuple\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def retry_times(self) -> int:\r\n        \"\"\"返回连接失败时的重试次数\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def retry_interval(self) -> float:\r\n        \"\"\"返回连接失败时的重试间隔（秒）\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_headless(self) -> bool:\r\n        \"\"\"返回是否无头模式\"\"\"\r\n        ...\r\n\r\n    def set_retry(self, times: int = None, interval: float = None) -> ChromiumOptions:\r\n        \"\"\"设置连接失败时的重试操作\r\n        :param times: 重试次数\r\n        :param interval: 重试间隔\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_argument(self, arg: str, value: Union[str, None, bool] = None) -> ChromiumOptions:\r\n        \"\"\"设置浏览器配置的argument属性\r\n        :param arg: 属性名\r\n        :param value: 属性值，有值的属性传入值，没有的传入None，如传入False，删除该项\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove_argument(self, value: str) -> ChromiumOptions:\r\n        \"\"\"移除一个argument项\r\n        :param value: 设置项名，有值的设置项传入设置名称即可\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def add_extension(self, path: Union[str, Path]) -> ChromiumOptions:\r\n        \"\"\"添加插件\r\n        :param path: 插件路径，可指向文件夹\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove_extensions(self) -> ChromiumOptions:\r\n        \"\"\"移除所有插件\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_pref(self, arg: str, value: Any) -> ChromiumOptions:\r\n        \"\"\"设置Preferences文件中的用户设置项\r\n        :param arg: 设置项名称\r\n        :param value: 设置项值\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove_pref(self, arg: str) -> ChromiumOptions:\r\n        \"\"\"删除用户首选项设置，不能删除已设置到文件中的项\r\n        :param arg: 设置项名称\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove_pref_from_file(self, arg: str) -> ChromiumOptions:\r\n        \"\"\"删除用户配置文件中已设置的项\r\n        :param arg: 设置项名称\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_flag(self, flag: str, value: Union[int, str, bool] = None) -> ChromiumOptions:\r\n        \"\"\"设置实验项\r\n        :param flag: 设置项名称\r\n        :param value: 设置项的值，为False则删除该项\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def clear_flags_in_file(self) -> ChromiumOptions:\r\n        \"\"\"删除浏览器配置文件中已设置的实验项\"\"\"\r\n        ...\r\n\r\n    def clear_flags(self) -> ChromiumOptions:\r\n        \"\"\"清空本对象已设置的flag参数\"\"\"\r\n        ...\r\n\r\n    def clear_arguments(self) -> ChromiumOptions:\r\n        \"\"\"清空本对象已设置的argument参数\"\"\"\r\n        ...\r\n\r\n    def clear_prefs(self) -> ChromiumOptions:\r\n        \"\"\"清空本对象已设置的pref参数\"\"\"\r\n        ...\r\n\r\n    def set_timeouts(self,\r\n                     base: float = None,\r\n                     page_load: float = None,\r\n                     script: float = None) -> ChromiumOptions:\r\n        \"\"\"设置超时时间，单位为秒\r\n        :param base: 默认超时时间\r\n        :param page_load: 页面加载超时时间\r\n        :param script: 脚本运行超时时间\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_user(self, user: str = 'Default') -> ChromiumOptions:\r\n        \"\"\"设置使用哪个用户配置文件夹\r\n        :param user: 用户文件夹名称\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def headless(self, on_off: bool = True) -> ChromiumOptions:\r\n        \"\"\"设置是否隐藏浏览器界面\r\n        :param on_off: 开或关\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def no_imgs(self, on_off: bool = True) -> ChromiumOptions:\r\n        \"\"\"设置是否加载图片\r\n        :param on_off: 开或关\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def no_js(self, on_off: bool = True) -> ChromiumOptions:\r\n        \"\"\"设置是否禁用js\r\n        :param on_off: 开或关\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def mute(self, on_off: bool = True) -> ChromiumOptions:\r\n        \"\"\"设置是否静音\r\n        :param on_off: 开或关\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def incognito(self, on_off: bool = True) -> ChromiumOptions:\r\n        \"\"\"设置是否使用无痕模式启动\r\n        :param on_off: 开或关\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def new_env(self, on_off: bool = True) -> ChromiumOptions:\r\n        \"\"\"设置是否使用全新浏览器环境\r\n        :param on_off: 开或关\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def ignore_certificate_errors(self, on_off=True) -> ChromiumOptions:\r\n        \"\"\"设置是否忽略证书错误\r\n        :param on_off: 开或关\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_user_agent(self, user_agent: str) -> ChromiumOptions:\r\n        \"\"\"设置user agent\r\n        :param user_agent: user agent文本\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_proxy(self, proxy: str) -> ChromiumOptions:\r\n        \"\"\"设置代理\r\n        :param proxy: 代理url和端口\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_load_mode(self, value: Literal['normal', 'eager', 'none']) -> ChromiumOptions:\r\n        \"\"\"设置load_mode，可接收 'normal', 'eager', 'none'\r\n        normal：默认情况下使用, 等待所有资源下载完成\r\n        eager：DOM访问已准备就绪, 但其他资源 (如图像) 可能仍在加载中\r\n        none：完全不阻塞\r\n        :param value: 可接收 'normal', 'eager', 'none'\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_local_port(self, port: Union[str, int]) -> ChromiumOptions:\r\n        \"\"\"设置本地启动端口，与set_address()和auto_port()互斥\r\n        :param port: 端口号\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_address(self, address: str) -> ChromiumOptions:\r\n        \"\"\"设置浏览器地址，格式'ip:port'，与auto_port()和set_local_port()互斥\r\n        :param address: 浏览器地址\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_browser_path(self, path: Union[str, Path]) -> ChromiumOptions:\r\n        \"\"\"设置浏览器可执行文件路径\r\n        :param path: 浏览器路径\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_download_path(self, path: Union[str, Path]) -> ChromiumOptions:\r\n        \"\"\"设置下载文件保存路径\r\n        :param path: 下载路径\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_tmp_path(self, path: Union[str, Path]) -> ChromiumOptions:\r\n        \"\"\"设置临时文件文件保存路径\r\n        :param path: 下载路径\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_user_data_path(self, path: Union[str, Path]) -> ChromiumOptions:\r\n        \"\"\"设置用户文件夹路径\r\n        :param path: 用户文件夹路径\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_cache_path(self, path: Union[str, Path]) -> ChromiumOptions:\r\n        \"\"\"设置缓存路径\r\n        :param path: 缓存路径\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def use_system_user_path(self, on_off: bool = True) -> ChromiumOptions:\r\n        \"\"\"设置是否使用系统安装的浏览器默认用户文件夹\r\n        :param on_off: 开或关\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def auto_port(self,\r\n                  on_off: bool = True,\r\n                  scope: Tuple[int, int] = None) -> ChromiumOptions:\r\n        \"\"\"自动获取可用端口，与set_address()和set_local_port()互斥\r\n        :param on_off: 是否开启自动获取端口号\r\n        :param scope: 指定端口范围，不含最后的数字，为None则使用[9600-59600)\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def existing_only(self, on_off: bool = True) -> ChromiumOptions:\r\n        \"\"\"设置只接管已有浏览器，不自动启动新的\r\n        :param on_off: 是否开启自动获取端口号\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def save(self, path: Union[str, Path] = None) -> str:\r\n        \"\"\"保存设置到文件\r\n        :param path: ini文件的路径， None 保存到当前读取的配置文件，传入 'default' 保存到默认ini文件\r\n        :return: 保存文件的绝对路径\r\n        \"\"\"\r\n        ...\r\n\r\n    def save_to_default(self) -> str:\r\n        \"\"\"保存当前配置到默认ini文件\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_configs/configs.ini",
    "content": "[paths]\r\ndownload_path =\r\ntmp_path =\r\n\r\n[chromium_options]\r\naddress = 127.0.0.1:9222\r\nbrowser_path = chrome\r\narguments = ['--no-default-browser-check', '--disable-suggestions-ui', '--no-first-run', '--disable-infobars', '--disable-popup-blocking', '--hide-crash-restore-bubble', '--disable-features=PrivacySandboxSettings4']\r\nextensions = []\r\nprefs = {'profile.default_content_settings.popups': 0, 'profile.default_content_setting_values': {'notifications': 2}}\r\nflags = {}\r\nload_mode = normal\r\nuser = Default\r\nauto_port = False\r\nsystem_user_path = False\r\nexisting_only = False\r\nnew_env = False\r\n\r\n[session_options]\r\nheaders = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'connection': 'keep-alive', 'accept-charset': 'GB2312,utf-8;q=0.7,*;q=0.7'}\r\n\r\n[timeouts]\r\nbase = 10\r\npage_load = 30\r\nscript = 30\r\n\r\n[proxies]\r\nhttp =\r\nhttps = \r\n\r\n[others]\r\nretry_times = 3\r\nretry_interval = 2\r\n"
  },
  {
    "path": "DrissionPage/_configs/options_manage.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom configparser import RawConfigParser, NoSectionError, NoOptionError\r\nfrom pathlib import Path\r\nfrom pprint import pprint\r\n\r\nfrom .._functions.settings import Settings as _S\r\n\r\n\r\nclass OptionsManager(object):\r\n    def __init__(self, path=None):\r\n        if path is False:\r\n            self.ini_path = None\r\n        else:\r\n            default_configs = Path(__file__).parent / 'configs.ini'\r\n            if path is None:\r\n                dp_configs = Path('dp_configs.ini')\r\n                if dp_configs.exists():\r\n                    self.ini_path = dp_configs\r\n                else:\r\n                    self.ini_path = default_configs\r\n            elif path == 'default':\r\n                self.ini_path = default_configs\r\n            elif isinstance(path, Path):\r\n                self.ini_path = path\r\n            else:\r\n                self.ini_path = Path(path)\r\n\r\n        self._conf = RawConfigParser()\r\n        if path is not False and self.ini_path.exists():\r\n            self.file_exists = True\r\n            self._conf.read(self.ini_path, encoding='utf-8')\r\n        else:\r\n            self.file_exists = False\r\n            self._conf.add_section('paths')\r\n            self._conf.add_section('chromium_options')\r\n            self._conf.add_section('session_options')\r\n            self._conf.add_section('timeouts')\r\n            self._conf.add_section('proxies')\r\n            self._conf.add_section('others')\r\n            self.set_item('paths', 'download_path', '')\r\n            self.set_item('paths', 'tmp_path', '')\r\n            self.set_item('chromium_options', 'address', '127.0.0.1:9222')\r\n            self.set_item('chromium_options', 'browser_path', 'chrome')\r\n            self.set_item('chromium_options', 'arguments', \"['--no-default-browser-check', '--disable-suggestions-ui', \"\r\n                                                           \"'--no-first-run', '--disable-infobars', \"\r\n                                                           \"'--disable-popup-blocking', '--hide-crash-restore-bubble', \"\r\n                                                           \"'--disable-features=PrivacySandboxSettings4']\")\r\n            self.set_item('chromium_options', 'extensions', '[]')\r\n            self.set_item('chromium_options', 'prefs', \"{'profile.default_content_settings.popups': 0, \"\r\n                                                       \"'profile.default_content_setting_values': \"\r\n                                                       \"{'notifications': 2}}\")\r\n            self.set_item('chromium_options', 'flags', '{}')\r\n            self.set_item('chromium_options', 'load_mode', 'normal')\r\n            self.set_item('chromium_options', 'user', 'Default')\r\n            self.set_item('chromium_options', 'auto_port', 'False')\r\n            self.set_item('chromium_options', 'system_user_path', 'False')\r\n            self.set_item('chromium_options', 'existing_only', 'False')\r\n            self.set_item('chromium_options', 'new_env', 'False')\r\n            self.set_item('session_options', 'headers', \"{'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X \"\r\n                                                        \"10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.\"\r\n                                                        \"1.2 Safari/603.3.8', 'accept': 'text/html,application/xhtml\"\r\n                                                        \"+xml,application/xml;q=0.9,*/*;q=0.8', 'connection': \"\r\n                                                        \"'keep-alive', 'accept-charset': 'GB2312,utf-8;q=0.7,*;q=0.7'}\")\r\n            self.set_item('timeouts', 'base', '10')\r\n            self.set_item('timeouts', 'page_load', '30')\r\n            self.set_item('timeouts', 'script', '30')\r\n            self.set_item('proxies', 'http', '')\r\n            self.set_item('proxies', 'https', '')\r\n            self.set_item('others', 'retry_times', '3')\r\n            self.set_item('others', 'retry_interval', '2')\r\n\r\n    def __getattr__(self, item):\r\n        return self.get_option(item)\r\n\r\n    def get_value(self, section, item):\r\n        try:\r\n            return eval(self._conf.get(section, item))\r\n        except (SyntaxError, NameError):\r\n            return self._conf.get(section, item)\r\n        except NoSectionError and NoOptionError:\r\n            return None\r\n\r\n    def get_option(self, section):\r\n        items = self._conf.items(section)\r\n        option = dict()\r\n\r\n        for j in items:\r\n            try:\r\n                option[j[0]] = eval(self._conf.get(section, j[0]))\r\n            except Exception:\r\n                option[j[0]] = self._conf.get(section, j[0])\r\n\r\n        return option\r\n\r\n    def set_item(self, section, item, value):\r\n        self._conf.set(section, item, str(value))\r\n        self.__setattr__(f'_{section}', None)\r\n        return self\r\n\r\n    def remove_item(self, section, item):\r\n        self._conf.remove_option(section, item)\r\n        return self\r\n\r\n    def save(self, path=None):\r\n        default_path = (Path(__file__).parent / 'configs.ini').absolute()\r\n        if path == 'default':\r\n            path = default_path\r\n        elif path is None:\r\n            if self.ini_path is None:\r\n                raise RuntimeError(_S._lang.join(_S._lang.INI_NOT_SET))\r\n            path = self.ini_path.absolute()\r\n        else:\r\n            path = Path(path).absolute()\r\n\r\n        path = path / 'config.ini' if path.is_dir() else path\r\n        path.parent.mkdir(exist_ok=True, parents=True)\r\n\r\n        path = str(path)\r\n        self._conf.write(open(path, 'w', encoding='utf-8'))\r\n\r\n        print(f'{_S._lang.OPTIONS_HAVE_SAVED}: {path}')\r\n        if path == str(default_path):\r\n            print(_S._lang.AUTO_LOAD_TIP)\r\n\r\n        self.file_exists = True\r\n        return path\r\n\r\n    def save_to_default(self):\r\n        return self.save('default')\r\n\r\n    def show(self):\r\n        for i in self._conf.sections():\r\n            print(f'[{i}]')\r\n            pprint(self.get_option(i))\r\n            print()\r\n"
  },
  {
    "path": "DrissionPage/_configs/options_manage.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom configparser import RawConfigParser\r\nfrom pathlib import Path\r\nfrom typing import Any, Optional, Union\r\n\r\n\r\nclass OptionsManager(object):\r\n    \"\"\"管理配置文件内容的类\"\"\"\r\n    ini_path: Optional[Path] = ...\r\n    file_exists: bool = ...\r\n    _conf: RawConfigParser = ...\r\n\r\n    def __init__(self, path: Union[Path, str] = None):\r\n        \"\"\"初始化，读取配置文件，如没有设置临时文件夹，则设置并新建\r\n        :param path: ini文件的路径，为None则找项目文件夹下的，找不到则读取模块文件夹下的\r\n        \"\"\"\r\n        ...\r\n\r\n    def __getattr__(self, item) -> dict:\r\n        \"\"\"以dict形似返回获取大项信息\r\n        :param item: 项名\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_value(self, section: str, item: str) -> Any:\r\n        \"\"\"获取配置的值\r\n        :param section: 段名\r\n        :param item: 项名\r\n        :return: 项值\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_option(self, section: str) -> dict:\r\n        \"\"\"把section内容以字典方式返回\r\n        :param section: 段名\r\n        :return: 段内容生成的字典\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_item(self, section: str, item: str, value: Any) -> None:\r\n        \"\"\"设置配置值\r\n        :param section: 段名\r\n        :param item: 项名\r\n        :param value: 项值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove_item(self, section: str, item: str) -> None:\r\n        \"\"\"删除配置值\r\n        :param section: 段名\r\n        :param item: 项名\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def save(self, path: str = None) -> str:\r\n        \"\"\"保存配置文件\r\n        :param path: ini文件的路径，传入 'default' 保存到默认ini文件\r\n        :return: 保存路径\r\n        \"\"\"\r\n        ...\r\n\r\n    def save_to_default(self) -> str:\r\n        \"\"\"保存当前配置到默认ini文件\"\"\"\r\n        ...\r\n\r\n    def show(self) -> None:\r\n        \"\"\"打印所有设置信息\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_configs/session_options.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom copy import copy\r\nfrom pathlib import Path\r\n\r\nfrom requests import Session\r\nfrom requests.structures import CaseInsensitiveDict\r\n\r\nfrom .options_manage import OptionsManager\r\nfrom .._functions.cookies import cookies_to_tuple, set_session_cookies\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._functions.web import format_headers\r\n\r\n\r\nclass SessionOptions(object):\r\n\r\n    def __init__(self, read_file=True, ini_path=None):\r\n        self.ini_path = None\r\n        self._download_path = '.'\r\n        self._timeout = 10\r\n        self._del_set = set()  # 记录要从ini文件删除的参数\r\n\r\n        if read_file is False:\r\n            ini_path = False\r\n            self.ini_path = None\r\n        elif ini_path:\r\n            ini_path = Path(ini_path).absolute()\r\n            if not ini_path.exists():\r\n                raise FileNotFoundError(_S._lang.join(_S._lang.INI_NOT_FOUND, PATH=ini_path))\r\n            self.ini_path = str(ini_path)\r\n        else:\r\n            self.ini_path = str(Path(__file__).parent / 'configs.ini')\r\n        om = OptionsManager(ini_path)\r\n\r\n        self._headers = None\r\n        self._cookies = None\r\n        self._auth = None\r\n        self._proxies = None\r\n        self._hooks = None\r\n        self._params = None\r\n        self._verify = None\r\n        self._cert = None\r\n        self._adapters = None\r\n        self._stream = None\r\n        self._trust_env = None\r\n        self._max_redirects = None\r\n\r\n        options = om.session_options\r\n        if options.get('headers', None) is not None:\r\n            self.set_headers(options['headers'])\r\n\r\n        if options.get('cookies', None) is not None:\r\n            self.set_cookies(options['cookies'])\r\n\r\n        if options.get('auth', None) is not None:\r\n            self._auth = options['auth']\r\n\r\n        if options.get('params', None) is not None:\r\n            self._params = options['params']\r\n\r\n        if options.get('verify', None) is not None:\r\n            self._verify = options['verify']\r\n\r\n        if options.get('cert', None) is not None:\r\n            self._cert = options['cert']\r\n\r\n        if options.get('stream', None) is not None:\r\n            self._stream = options['stream']\r\n\r\n        if options.get('trust_env', None) is not None:\r\n            self._trust_env = options['trust_env']\r\n\r\n        if options.get('max_redirects', None) is not None:\r\n            self._max_redirects = options['max_redirects']\r\n\r\n        self.set_proxies(om.proxies.get('http', None), om.proxies.get('https', None))\r\n        self._timeout = om.timeouts.get('base', 10)\r\n        self._download_path = om.paths.get('download_path', '.') or '.'\r\n\r\n        others = om.others\r\n        self._retry_times = others.get('retry_times', 3)\r\n        self._retry_interval = others.get('retry_interval', 2)\r\n\r\n    def __repr__(self):\r\n        return f'<SessionOptions at {id(self)}>'\r\n\r\n    # ===========须独立处理的项开始============\r\n    @property\r\n    def download_path(self):\r\n        return self._download_path\r\n\r\n    def set_download_path(self, path):\r\n        self._download_path = '.' if path is None else str(path)\r\n        return self\r\n\r\n    @property\r\n    def timeout(self):\r\n        return self._timeout\r\n\r\n    def set_timeout(self, second):\r\n        self._timeout = second\r\n        return self\r\n\r\n    @property\r\n    def proxies(self):\r\n        if self._proxies is None:\r\n            self._proxies = {}\r\n        return self._proxies\r\n\r\n    def set_proxies(self, http=None, https=None):\r\n        self._sets('proxies', {'http': http, 'https': https})\r\n        return self\r\n\r\n    @property\r\n    def retry_times(self):\r\n        return self._retry_times\r\n\r\n    @property\r\n    def retry_interval(self):\r\n        return self._retry_interval\r\n\r\n    def set_retry(self, times=None, interval=None):\r\n        if times is not None:\r\n            self._retry_times = times\r\n        if interval is not None:\r\n            self._retry_interval = interval\r\n        return self\r\n\r\n    # ===========须独立处理的项结束============\r\n\r\n    @property\r\n    def headers(self):\r\n        if self._headers is None:\r\n            self._headers = {}\r\n        return self._headers\r\n\r\n    def set_headers(self, headers):\r\n        if headers is None:\r\n            self._headers = None\r\n            self._del_set.add('headers')\r\n        else:\r\n            headers = format_headers(headers)\r\n            self._headers = {key.lower(): headers[key] for key in headers}\r\n        return self\r\n\r\n    def set_a_header(self, name, value):\r\n        if self._headers is None:\r\n            self._headers = {}\r\n\r\n        self._headers[name.lower()] = value\r\n        return self\r\n\r\n    def remove_a_header(self, name):\r\n        if self._headers is None:\r\n            return self\r\n\r\n        self._headers.pop(name.lower(), None)\r\n\r\n        return self\r\n\r\n    def clear_headers(self):\r\n        self._headers = None\r\n        self._del_set.add('headers')\r\n\r\n    @property\r\n    def cookies(self):\r\n        if self._cookies is None:\r\n            self._cookies = []\r\n        return self._cookies\r\n\r\n    def set_cookies(self, cookies):\r\n        cookies = cookies if cookies is None else list(cookies_to_tuple(cookies))\r\n        self._sets('cookies', cookies)\r\n        return self\r\n\r\n    @property\r\n    def auth(self):\r\n        return self._auth\r\n\r\n    def set_auth(self, auth):\r\n        self._sets('auth', auth)\r\n        return self\r\n\r\n    @property\r\n    def hooks(self):\r\n        if self._hooks is None:\r\n            self._hooks = {}\r\n        return self._hooks\r\n\r\n    def set_hooks(self, hooks):\r\n        self._hooks = hooks\r\n        return self\r\n\r\n    @property\r\n    def params(self):\r\n        if self._params is None:\r\n            self._params = {}\r\n        return self._params\r\n\r\n    def set_params(self, params):\r\n        self._sets('params', params)\r\n        return self\r\n\r\n    @property\r\n    def verify(self):\r\n        return self._verify\r\n\r\n    def set_verify(self, on_off):\r\n        self._sets('verify', on_off)\r\n        return self\r\n\r\n    @property\r\n    def cert(self):\r\n        return self._cert\r\n\r\n    def set_cert(self, cert):\r\n        self._sets('cert', cert)\r\n        return self\r\n\r\n    @property\r\n    def adapters(self):\r\n        if self._adapters is None:\r\n            self._adapters = []\r\n        return self._adapters\r\n\r\n    def add_adapter(self, url, adapter):\r\n        self._adapters.append((url, adapter))\r\n        return self\r\n\r\n    @property\r\n    def stream(self):\r\n        return self._stream\r\n\r\n    def set_stream(self, on_off):\r\n        self._sets('stream', on_off)\r\n        return self\r\n\r\n    @property\r\n    def trust_env(self):\r\n        return self._trust_env\r\n\r\n    def set_trust_env(self, on_off):\r\n        self._sets('trust_env', on_off)\r\n        return self\r\n\r\n    @property\r\n    def max_redirects(self):\r\n        return self._max_redirects\r\n\r\n    def set_max_redirects(self, times):\r\n        self._sets('max_redirects', times)\r\n        return self\r\n\r\n    def _sets(self, arg, val):\r\n        if val is None:\r\n            self.__setattr__(f'_{arg}', None)\r\n            self._del_set.add(arg)\r\n        else:\r\n            self.__setattr__(f'_{arg}', val)\r\n            if arg in self._del_set:\r\n                self._del_set.remove(arg)\r\n\r\n    def save(self, path=None):\r\n        if path == 'default':\r\n            path = (Path(__file__).parent / 'configs.ini').absolute()\r\n\r\n        elif path is None:\r\n            if self.ini_path:\r\n                path = Path(self.ini_path).absolute()\r\n            else:\r\n                path = (Path(__file__).parent / 'configs.ini').absolute()\r\n\r\n        else:\r\n            path = Path(path).absolute()\r\n\r\n        path = path / 'config.ini' if path.is_dir() else path\r\n\r\n        if path.exists():\r\n            om = OptionsManager(path)\r\n        else:\r\n            om = OptionsManager(self.ini_path or (Path(__file__).parent / 'configs.ini'))\r\n\r\n        options = session_options_to_dict(self)\r\n\r\n        for i in options:\r\n            if i not in ('download_path', 'timeout', 'proxies'):\r\n                om.set_item('session_options', i, options[i])\r\n\r\n        om.set_item('paths', 'download_path', self.download_path or '')\r\n        om.set_item('timeouts', 'base', self.timeout)\r\n        om.set_item('proxies', 'http', self.proxies.get('http', ''))\r\n        om.set_item('proxies', 'https', self.proxies.get('https', ''))\r\n        om.set_item('others', 'retry_times', self.retry_times)\r\n        om.set_item('others', 'retry_interval', self.retry_interval)\r\n\r\n        for i in self._del_set:\r\n            if i == 'download_path':\r\n                om.set_item('paths', 'download_path', '')\r\n            elif i == 'proxies':\r\n                om.set_item('proxies', 'http', '')\r\n                om.set_item('proxies', 'https', '')\r\n            else:\r\n                om.remove_item('session_options', i)\r\n\r\n        path = str(path)\r\n        om.save(path)\r\n\r\n        return path\r\n\r\n    def save_to_default(self):\r\n        return self.save('default')\r\n\r\n    def as_dict(self):\r\n        return session_options_to_dict(self)\r\n\r\n    def make_session(self):\r\n        s = Session()\r\n        h = CaseInsensitiveDict(self.headers) if self.headers else CaseInsensitiveDict()\r\n\r\n        if self.cookies:\r\n            set_session_cookies(s, self.cookies)\r\n        if self.adapters:\r\n            for url, adapter in self.adapters:\r\n                s.mount(url, adapter)\r\n\r\n        for i in ['auth', 'proxies', 'hooks', 'params', 'verify', 'cert', 'stream', 'trust_env', 'max_redirects']:\r\n            attr = self.__getattribute__(i)\r\n            if attr:\r\n                s.__setattr__(i, attr)\r\n\r\n        return s, h\r\n\r\n    def from_session(self, session, headers=None):\r\n        self._headers = CaseInsensitiveDict(copy(session.headers).update(headers)) if headers else session.headers\r\n        self._cookies = session.cookies\r\n        self._auth = session.auth\r\n        self._proxies = session.proxies\r\n        self._hooks = session.hooks\r\n        self._params = session.params\r\n        self._verify = session.verify\r\n        self._cert = session.cert\r\n        self._stream = session.stream\r\n        self._trust_env = session.trust_env\r\n        self._max_redirects = session.max_redirects\r\n        if session.adapters:\r\n            self._adapters = [(k, i) for k, i in session.adapters.items()]\r\n        return self\r\n\r\n\r\ndef session_options_to_dict(options):\r\n    if options in (False, None):\r\n        return SessionOptions(read_file=False).as_dict()\r\n\r\n    if isinstance(options, dict):\r\n        return options\r\n\r\n    re_dict = dict()\r\n    attrs = ['headers', 'cookies', 'proxies', 'params', 'verify', 'stream', 'trust_env', 'cert',\r\n             'max_redirects', 'timeout', 'download_path']\r\n\r\n    for attr in attrs:\r\n        val = options.__getattribute__(f'_{attr}')\r\n        if val is not None:\r\n            re_dict[attr] = val\r\n\r\n    return re_dict\r\n"
  },
  {
    "path": "DrissionPage/_configs/session_options.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom http.cookiejar import CookieJar, Cookie\r\nfrom pathlib import Path\r\nfrom typing import Any, Union, Tuple, Optional\r\n\r\nfrom requests import Session\r\nfrom requests.adapters import HTTPAdapter\r\nfrom requests.auth import HTTPBasicAuth\r\nfrom requests.cookies import RequestsCookieJar\r\nfrom requests.structures import CaseInsensitiveDict\r\n\r\n\r\nclass SessionOptions(object):\r\n    \"\"\"requests的Session对象配置类\"\"\"\r\n\r\n    ini_path: Optional[str] = ...\r\n    _download_path: str = ...\r\n    _headers: Union[dict, CaseInsensitiveDict, None] = ...\r\n    _cookies: Union[list, RequestsCookieJar, None] = ...\r\n    _auth: Optional[tuple] = ...\r\n    _proxies: Optional[dict] = ...\r\n    _hooks: Optional[dict] = ...\r\n    _params: Union[dict, None] = ...\r\n    _verify: Optional[bool] = ...\r\n    _cert: Union[str, tuple, None] = ...\r\n    _adapters: Optional[list] = ...\r\n    _stream: Optional[bool] = ...\r\n    _trust_env: Optional[bool] = ...\r\n    _max_redirects: Optional[int] = ...\r\n    _timeout: float = ...\r\n    _del_set: set = ...\r\n    _retry_times: int = ...\r\n    _retry_interval: float = ...\r\n\r\n    def __init__(self,\r\n                 read_file: [bool, None] = True,\r\n                 ini_path: Union[str, Path] = None):\r\n        \"\"\"\r\n        :param read_file: 是否从文件读取配置\r\n        :param ini_path: ini文件路径\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def download_path(self) -> str:\r\n        \"\"\"返回默认下载路径属性信息\"\"\"\r\n        ...\r\n\r\n    def set_download_path(self, path: Union[str, Path]) -> SessionOptions:\r\n        \"\"\"设置默认下载路径\r\n        :param path: 下载路径\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def timeout(self) -> float:\r\n        \"\"\"返回timeout属性信息\"\"\"\r\n        ...\r\n\r\n    def set_timeout(self, second: float) -> SessionOptions:\r\n        \"\"\"设置超时信息\r\n        :param second: 秒数\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def proxies(self) -> dict:\r\n        \"\"\"返回proxies设置信息\"\"\"\r\n        ...\r\n\r\n    def set_proxies(self, http: Union[str, None], https: Union[str, None] = None) -> SessionOptions:\r\n        \"\"\"设置proxies参数\r\n        :param http: http代理地址\r\n        :param https: https代理地址\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def retry_times(self) -> int:\r\n        \"\"\"返回连接失败时的重试次数\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def retry_interval(self) -> float:\r\n        \"\"\"返回连接失败时的重试间隔（秒）\"\"\"\r\n        ...\r\n\r\n    def set_retry(self, times: int = None, interval: float = None) -> SessionOptions:\r\n        \"\"\"设置连接失败时的重试操作\r\n        :param times: 重试次数\r\n        :param interval: 重试间隔\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def headers(self) -> dict:\r\n        \"\"\"返回headers设置信息\"\"\"\r\n        ...\r\n\r\n    def set_headers(self, headers: Union[dict, str, None]) -> SessionOptions:\r\n        \"\"\"设置headers参数\r\n        :param headers: 参数值，传入None可在ini文件标记删除\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_a_header(self, name: str, value: str) -> SessionOptions:\r\n        \"\"\"设置headers中一个项\r\n        :param name: 设置名称\r\n        :param value: 设置值\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove_a_header(self, name: str) -> SessionOptions:\r\n        \"\"\"从headers中删除一个设置\r\n        :param name: 要删除的设置\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def clear_headers(self) -> SessionOptions:\r\n        \"\"\"清空已设置的header参数\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def cookies(self) -> list:\r\n        \"\"\"以list形式返回cookies\"\"\"\r\n        ...\r\n\r\n    def set_cookies(self, cookies: Union[Cookie, CookieJar, list, tuple, str, dict, None]) -> SessionOptions:\r\n        \"\"\"设置一个或多个cookies信息\r\n        :param cookies: cookies，可为Cookie, CookieJar, list, tuple, str, dict，传入None可在ini文件标记删除\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def auth(self) -> Union[Tuple[str, str], HTTPBasicAuth]:\r\n        \"\"\"返回认证设置信息\"\"\"\r\n        ...\r\n\r\n    def set_auth(self, auth: Union[Tuple[str, str], HTTPBasicAuth, None]) -> SessionOptions:\r\n        \"\"\"设置认证元组或对象\r\n        :param auth: 认证元组或对象\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def hooks(self) -> dict:\r\n        \"\"\"返回回调方法\"\"\"\r\n        ...\r\n\r\n    def set_hooks(self, hooks: Union[dict, None]) -> SessionOptions:\r\n        \"\"\"设置回调方法\r\n        :param hooks: 回调方法\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def params(self) -> dict:\r\n        \"\"\"返回连接参数设置信息\"\"\"\r\n        ...\r\n\r\n    def set_params(self, params: Union[dict, None]) -> SessionOptions:\r\n        \"\"\"设置查询参数字典\r\n        :param params: 查询参数字典\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def verify(self) -> bool:\r\n        \"\"\"返回是否验证SSL证书设置\"\"\"\r\n        ...\r\n\r\n    def set_verify(self, on_off: Union[bool, None]) -> SessionOptions:\r\n        \"\"\"设置是否验证SSL证书\r\n        :param on_off: 是否验证 SSL 证书\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def cert(self) -> Union[str, tuple]:\r\n        \"\"\"返回SSL证书设置信息\"\"\"\r\n        ...\r\n\r\n    def set_cert(self, cert: Union[str, Tuple[str, str], None]) -> SessionOptions:\r\n        \"\"\"SSL客户端证书文件的路径(.pem格式)，或('cert', 'key')元组\r\n        :param cert: 证书路径或元组\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def adapters(self) -> list:\r\n        \"\"\"返回适配器设置信息\"\"\"\r\n        ...\r\n\r\n    def add_adapter(self, url: str, adapter: HTTPAdapter) -> SessionOptions:\r\n        \"\"\"添加适配器\r\n        :param url: 适配器对应url\r\n        :param adapter: 适配器对象\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def stream(self) -> bool:\r\n        \"\"\"返回是否使用流式响应内容设置信息\"\"\"\r\n        ...\r\n\r\n    def set_stream(self, on_off: Union[bool, None]) -> SessionOptions:\r\n        \"\"\"设置是否使用流式响应内容\r\n        :param on_off: 是否使用流式响应内容\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def trust_env(self) -> bool:\r\n        \"\"\"返回是否信任环境设置信息\"\"\"\r\n        ...\r\n\r\n    def set_trust_env(self, on_off: Union[bool, None]) -> SessionOptions:\r\n        \"\"\"设置是否信任环境\r\n        :param on_off: 是否信任环境\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def max_redirects(self) -> int:\r\n        \"\"\"返回最大重定向次数\"\"\"\r\n        ...\r\n\r\n    def set_max_redirects(self, times: Union[int, None]) -> SessionOptions:\r\n        \"\"\"设置最大重定向次数\r\n        :param times: 最大重定向次数\r\n        :return: 返回当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _sets(self, arg: str, val: Any) -> None:\r\n        \"\"\"给属性赋值或标记删除\r\n        :param arg: 属性名称\r\n        :param val: 参数值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def save(self, path: str = None) -> str:\r\n        \"\"\"保存设置到文件\r\n        :param path: ini文件的路径，传入 'default' 保存到默认ini文件\r\n        :return: 保存文件的绝对路径\r\n        \"\"\"\r\n        ...\r\n\r\n    def save_to_default(self) -> str:\r\n        \"\"\"保存当前配置到默认ini文件\"\"\"\r\n        ...\r\n\r\n    def as_dict(self) -> dict:\r\n        \"\"\"以字典形式返回本对象\"\"\"\r\n        ...\r\n\r\n    def make_session(self) -> Tuple[Session, Optional[CaseInsensitiveDict]]:\r\n        \"\"\"根据内在的配置生成Session对象，headers从对象中分离\"\"\"\r\n        ...\r\n\r\n    def from_session(self, session: Session, headers: CaseInsensitiveDict = None) -> SessionOptions:\r\n        \"\"\"从Session对象中读取配置\r\n        :param session: Session对象\r\n        :param headers: headers\r\n        :return: 当前对象\r\n        \"\"\"\r\n        ...\r\n\r\n\r\ndef session_options_to_dict(options: Union[dict, SessionOptions, None]) -> Union[dict, None]:\r\n    \"\"\"把session配置对象转换为字典\r\n    :param options: session配置对象或字典\r\n    :return: 配置字典\r\n    \"\"\"\r\n    ...\r\n"
  },
  {
    "path": "DrissionPage/_elements/chromium_element.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom json import loads\r\nfrom os.path import basename\r\nfrom pathlib import Path\r\nfrom platform import system\r\nfrom re import search\r\nfrom time import perf_counter, sleep\r\n\r\nfrom DataRecorder.tools import get_usable_path, make_valid_name\r\n\r\nfrom .none_element import NoneElement\r\nfrom .session_element import make_session_ele\r\nfrom .._base.base import DrissionElement, BaseElement\r\nfrom .._functions.elements import ChromiumElementsList, SessionElementsList\r\nfrom .._functions.keys import input_text_or_keys, Keys\r\nfrom .._functions.locator import get_loc, locator_to_tuple\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._functions.web import make_absolute_link, get_ele_txt, format_html, is_js_func, get_blob\r\nfrom .._units.clicker import Clicker\r\nfrom .._units.rect import ElementRect\r\nfrom .._units.scroller import ElementScroller\r\nfrom .._units.selector import SelectElement\r\nfrom .._units.setter import ChromiumElementSetter\r\nfrom .._units.states import ElementStates, ShadowRootStates\r\nfrom .._units.waiter import ElementWaiter\r\nfrom ..errors import (ContextLostError, ElementLostError, JavaScriptError, CDPError, NoResourceError,\r\n                      AlertExistsError, NoRectError, LocatorError)\r\n\r\n__FRAME_ELEMENT__ = ('iframe', 'frame')\r\n\r\n\r\nclass ChromiumElement(DrissionElement):\r\n\r\n    def __init__(self, owner, node_id=None, obj_id=None, backend_id=None):\r\n        super().__init__(owner)\r\n        self.tab = self.owner._tab\r\n        self._select = None\r\n        self._scroll = None\r\n        self._rect = None\r\n        self._set = None\r\n        self._states = None\r\n        self._pseudo = None\r\n        self._clicker = None\r\n        self._tag = None\r\n        self._wait = None\r\n        self._type = 'ChromiumElement'\r\n        self._doc_id = None\r\n\r\n        if node_id and obj_id and backend_id:\r\n            self._node_id = node_id\r\n            self._obj_id = obj_id\r\n            self._backend_id = backend_id\r\n        elif node_id:\r\n            self._node_id = node_id\r\n            self._obj_id = self._get_obj_id(node_id)\r\n            self._backend_id = self._get_backend_id(self._node_id)\r\n        elif obj_id:\r\n            self._node_id = self._get_node_id(obj_id)\r\n            self._obj_id = obj_id\r\n            self._backend_id = self._get_backend_id(self._node_id)\r\n        elif backend_id:\r\n            self._obj_id = self._get_obj_id(backend_id=backend_id)\r\n            self._node_id = self._get_node_id(obj_id=self._obj_id)\r\n            self._backend_id = backend_id\r\n        else:\r\n            raise ElementLostError\r\n\r\n    def __call__(self, locator, index=1, timeout=None):\r\n        return self.ele(locator, index=index, timeout=timeout)\r\n\r\n    def __repr__(self):\r\n        attrs = [f\"{k}='{v}'\" for k, v in self.attrs.items()]\r\n        return f'<ChromiumElement {self.tag} {\" \".join(attrs)}>'\r\n\r\n    def __eq__(self, other):\r\n        return self._backend_id == getattr(other, '_backend_id', None)\r\n\r\n    @property\r\n    def tag(self):\r\n        if self._tag is None:\r\n            self._tag = self.owner._run_cdp('DOM.describeNode',\r\n                                            backendNodeId=self._backend_id)['node']['localName'].lower()\r\n        return self._tag\r\n\r\n    @property\r\n    def html(self):\r\n        return self.owner._run_cdp('DOM.getOuterHTML', backendNodeId=self._backend_id)['outerHTML']\r\n\r\n    @property\r\n    def inner_html(self):\r\n        return self._run_js('return this.innerHTML;')\r\n\r\n    @property\r\n    def attrs(self):\r\n        try:\r\n            attrs = self.owner._run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']\r\n            return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)}\r\n        except ElementLostError:\r\n            self._refresh_id()\r\n            attrs = self.owner._run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']\r\n            return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)}\r\n        except CDPError:  # 文档根元素不能调用此方法\r\n            return {}\r\n\r\n    @property\r\n    def text(self):\r\n        return get_ele_txt(make_session_ele(self.html))\r\n\r\n    @property\r\n    def raw_text(self):\r\n        return self.property('innerText')\r\n\r\n    # -----------------d模式独有属性-------------------\r\n    @property\r\n    def set(self):\r\n        if self._set is None:\r\n            self._set = ChromiumElementSetter(self)\r\n        return self._set\r\n\r\n    @property\r\n    def states(self):\r\n        if self._states is None:\r\n            self._states = ElementStates(self)\r\n        return self._states\r\n\r\n    @property\r\n    def pseudo(self):\r\n        if self._pseudo is None:\r\n            self._pseudo = Pseudo(self)\r\n        return self._pseudo\r\n\r\n    @property\r\n    def rect(self):\r\n        if self._rect is None:\r\n            self._rect = ElementRect(self)\r\n        return self._rect\r\n\r\n    @property\r\n    def sr(self):\r\n        end_time = perf_counter() + self.timeout\r\n        while perf_counter() < end_time:\r\n            info = self.owner._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']\r\n            if info.get('shadowRoots', None):\r\n                return ShadowRoot(self, backend_id=info['shadowRoots'][0]['backendNodeId'])\r\n        return None\r\n\r\n    @property\r\n    def shadow_root(self):\r\n        return self.sr\r\n\r\n    @property\r\n    def scroll(self):\r\n        if self._scroll is None:\r\n            self._scroll = ElementScroller(self)\r\n        return self._scroll\r\n\r\n    @property\r\n    def click(self):\r\n        if self._clicker is None:\r\n            self._clicker = Clicker(self)\r\n        return self._clicker\r\n\r\n    @property\r\n    def wait(self):\r\n        if self._wait is None:\r\n            self._wait = ElementWaiter(self)\r\n        return self._wait\r\n\r\n    @property\r\n    def select(self):\r\n        if self._select is None:\r\n            if self.tag != 'select':\r\n                self._select = False\r\n            else:\r\n                self._select = SelectElement(self)\r\n        return self._select\r\n\r\n    @property\r\n    def value(self):\r\n        return self.property('value')\r\n\r\n    def check(self, uncheck=False, by_js=False):\r\n        is_checked = self.states.is_checked\r\n        if by_js:\r\n            js = None\r\n            if is_checked and uncheck:\r\n                js = 'this.checked=false'\r\n            elif not is_checked and not uncheck:\r\n                js = 'this.checked=true'\r\n            if js:\r\n                self._run_js(js)\r\n                self._run_js('this.dispatchEvent(new Event(\"change\", {bubbles: true}));')\r\n\r\n        else:\r\n            if (is_checked and uncheck) or (not is_checked and not uncheck):\r\n                self.click()\r\n\r\n        return self\r\n\r\n    def parent(self, level_or_loc=1, index=1, timeout=0):\r\n        return super().parent(level_or_loc, index, timeout=timeout)\r\n\r\n    def child(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().child(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def prev(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().prev(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def next(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().next(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def before(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().before(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def after(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().after(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def children(self, locator='', timeout=None, ele_only=True):\r\n        return ChromiumElementsList(self.owner, super().children(locator, timeout, ele_only=ele_only))\r\n\r\n    def prevs(self, locator='', timeout=None, ele_only=True):\r\n        return ChromiumElementsList(self.owner, super().prevs(locator, timeout, ele_only=ele_only))\r\n\r\n    def nexts(self, locator='', timeout=None, ele_only=True):\r\n        return ChromiumElementsList(self.owner, super().nexts(locator, timeout, ele_only=ele_only))\r\n\r\n    def befores(self, locator='', timeout=None, ele_only=True):\r\n        return ChromiumElementsList(self.owner, super().befores(locator, timeout, ele_only=ele_only))\r\n\r\n    def afters(self, locator='', timeout=None, ele_only=True):\r\n        return ChromiumElementsList(self.owner, super().afters(locator, timeout, ele_only=ele_only))\r\n\r\n    def over(self, timeout=None):\r\n        if timeout is None:\r\n            timeout = self.timeout\r\n        bid = self.states.is_covered\r\n        end_time = perf_counter() + timeout\r\n        while not bid and perf_counter() < end_time:\r\n            bid = self.states.is_covered\r\n        return (ChromiumElement(owner=self.owner, backend_id=bid)\r\n                if bid else NoneElement(page=self.owner, method='over()', args={'timeout': timeout}))\r\n\r\n    def offset(self, locator=None, x=None, y=None, timeout=None):\r\n        if locator and not (isinstance(locator, str) and not locator.startswith(\r\n                ('x:', 'xpath:', 'x=', 'xpath=', 'c:', 'css:', 'c=', 'css='))):\r\n            raise LocatorError(ALLOW_TYPE=_S._lang.STR_ONLY, CURR_VAL=locator)\r\n\r\n        if x == y is None:\r\n            x, y = self.rect.midpoint\r\n            x = int(x)\r\n            y = int(y)\r\n        else:\r\n            nx, ny = self.rect.location\r\n            nx += x if x else 0\r\n            ny += y if y else 0\r\n            x = int(nx)\r\n            y = int(ny)\r\n        loc_data = locator_to_tuple(locator) if locator else None\r\n        if timeout is None:\r\n            timeout = self.timeout\r\n        end_time = perf_counter() + timeout\r\n        try:\r\n            ele = ChromiumElement(owner=self.owner,\r\n                                  backend_id=self.owner._run_cdp('DOM.getNodeForLocation', x=x, y=y,\r\n                                                                 includeUserAgentShadowDOM=True,\r\n                                                                 ignorePointerEventsNone=False)['backendNodeId'])\r\n        except CDPError:\r\n            ele = False\r\n        if ele and (loc_data is None or _check_ele(ele, loc_data)):\r\n            return ele\r\n\r\n        while perf_counter() < end_time:\r\n            try:\r\n                ele = ChromiumElement(owner=self.owner,\r\n                                      backend_id=self.owner._run_cdp('DOM.getNodeForLocation', x=x, y=y,\r\n                                                                     includeUserAgentShadowDOM=True,\r\n                                                                     ignorePointerEventsNone=False)['backendNodeId'])\r\n            except CDPError:\r\n                ele = False\r\n\r\n            if ele and (loc_data is None or _check_ele(ele, loc_data)):\r\n                return ele\r\n            sleep(.01)\r\n\r\n        return NoneElement(page=self.owner, method='offset()',\r\n                           args={'locator': locator, 'offset_x': x, 'offset_y': y, 'timeout': timeout})\r\n\r\n    def east(self, loc_or_pixel=None, index=1):\r\n        return self._get_relative_eles(mode='east', locator=loc_or_pixel, index=index)\r\n\r\n    def south(self, loc_or_pixel=None, index=1):\r\n        return self._get_relative_eles(mode='south', locator=loc_or_pixel, index=index)\r\n\r\n    def west(self, loc_or_pixel=None, index=1):\r\n        return self._get_relative_eles(mode='west', locator=loc_or_pixel, index=index)\r\n\r\n    def north(self, loc_or_pixel=None, index=1):\r\n        return self._get_relative_eles(mode='north', locator=loc_or_pixel, index=index)\r\n\r\n    def _get_relative_eles(self, mode='north', locator=None, index=1):\r\n        if locator and not (isinstance(locator, str) and not locator.startswith(\r\n                ('x:', 'xpath:', 'x=', 'xpath=', 'c:', 'css:', 'c=', 'css=')) or isinstance(locator, int)):\r\n            raise LocatorError(ALLOW_TYPE=_S._lang.STR_ONLY, CURR_VAL=locator)\r\n        rect = self.states.has_rect\r\n        if not rect:\r\n            raise NoRectError\r\n\r\n        if mode == 'east':\r\n            cdp_data = {'x': int(rect[1][0]), 'y': int(self.rect.midpoint[1]),\r\n                        'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False}\r\n            variable = 'x'\r\n            minus = False\r\n        elif mode == 'south':\r\n            cdp_data = {'x': int(self.rect.midpoint[0]), 'y': int(rect[2][1]),\r\n                        'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False}\r\n            variable = 'y'\r\n            minus = False\r\n        elif mode == 'west':\r\n            cdp_data = {'x': int(rect[0][0]), 'y': int(self.rect.midpoint[1]),\r\n                        'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False}\r\n            variable = 'x'\r\n            minus = True\r\n        else:  # north\r\n            cdp_data = {'x': int(self.rect.midpoint[0]), 'y': int(rect[0][1]),\r\n                        'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False}\r\n            variable = 'y'\r\n            minus = True\r\n\r\n        if isinstance(locator, int):\r\n            if minus:\r\n                cdp_data[variable] -= locator\r\n            else:\r\n                cdp_data[variable] += locator\r\n            try:\r\n                return ChromiumElement(owner=self.owner,\r\n                                       backend_id=self.owner._run_cdp('DOM.getNodeForLocation',\r\n                                                                      **cdp_data)['backendNodeId'])\r\n            except CDPError:\r\n                return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator})\r\n\r\n        num = 0\r\n        value = -8 if minus else 8\r\n        size = self.owner.rect.size\r\n        max_len = size[0] if mode == 'east' else size[1]\r\n        loc_data = locator_to_tuple(locator) if locator else None\r\n        curr_ele = None\r\n        while 0 < cdp_data[variable] < max_len:\r\n            cdp_data[variable] += value\r\n            try:\r\n                bid = self.owner._run_cdp('DOM.getNodeForLocation', **cdp_data)['backendNodeId']\r\n                if bid == curr_ele:\r\n                    continue\r\n                else:\r\n                    curr_ele = bid\r\n                ele = ChromiumElement(self.owner, backend_id=bid)\r\n\r\n                if loc_data is None or _check_ele(ele, loc_data):\r\n                    num += 1\r\n                    if num == index:\r\n                        return ele\r\n            except:\r\n                pass\r\n\r\n        return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator})\r\n\r\n    def attr(self, name):\r\n        attrs = self.attrs\r\n        if name == 'href':\r\n            link = attrs.get('href')\r\n            if not link or link.lower().startswith(('javascript:', 'mailto:')):\r\n                return link\r\n            else:\r\n                return make_absolute_link(link, self.property('baseURI'))\r\n\r\n        elif name == 'src':\r\n            return make_absolute_link(attrs.get('src'), self.property('baseURI'))\r\n\r\n        elif name == 'text':\r\n            return self.text\r\n\r\n        elif name == 'innerText':\r\n            return self.raw_text\r\n\r\n        elif name in ('html', 'outerHTML'):\r\n            return self.html\r\n\r\n        elif name == 'innerHTML':\r\n            return self.inner_html\r\n\r\n        else:\r\n            return attrs.get(name, None)\r\n\r\n    def remove_attr(self, name):\r\n        self._run_js(f'this.removeAttribute(\"{name}\");')\r\n        return self\r\n\r\n    def property(self, name):\r\n        try:\r\n            value = self._run_js(f'return this.{name};')\r\n            return format_html(value) if isinstance(value, str) else value\r\n        except:\r\n            return None\r\n\r\n    def run_js(self, script, *args, as_expr=False, timeout=None):\r\n        return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)\r\n\r\n    def _run_js(self, script, *args, as_expr=False, timeout=None):\r\n        return run_js(self, script, as_expr, self.owner.timeouts.script if timeout is None else timeout, args)\r\n\r\n    def run_async_js(self, script, *args, as_expr=False):\r\n        run_js(self, script, as_expr, 0, args)\r\n\r\n    def ele(self, locator, index=1, timeout=None):\r\n        return self._ele(locator, timeout, index=index, method='ele()')\r\n\r\n    def eles(self, locator, timeout=None):\r\n        return self._ele(locator, timeout=timeout, index=None)\r\n\r\n    def s_ele(self, locator=None, index=1, timeout=None):\r\n        return (make_session_ele(self, locator, index=index, method='s_ele()')\r\n                if locator is None or self.ele(locator, index=index, timeout=timeout)\r\n                else NoneElement(self.owner, method='s_ele()', args={'locator': locator, 'index': index}))\r\n\r\n    def s_eles(self, locator=None, timeout=None):\r\n        return (make_session_ele(self, locator, index=None)\r\n                if self.ele(locator, timeout=timeout) else SessionElementsList())\r\n\r\n    def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):\r\n        return find_in_chromium_ele(self, locator, index, timeout, relative=relative)\r\n\r\n    def style(self, style, pseudo_ele=''):\r\n        if pseudo_ele:\r\n            pseudo_ele = f', \"{pseudo_ele}\"'\r\n        return self._run_js(f'return window.getComputedStyle(this{pseudo_ele}).getPropertyValue(\"{style}\");')\r\n\r\n    def src(self, timeout=None, base64_to_bytes=True):\r\n        if timeout is None:\r\n            timeout = self.timeout\r\n        if self.tag == 'img':  # 等待图片加载完成\r\n            js = ('return this.complete && typeof this.naturalWidth != \"undefined\" '\r\n                  '&& this.naturalWidth > 0 && typeof this.naturalHeight != \"undefined\" '\r\n                  '&& this.naturalHeight > 0')\r\n            end_time = perf_counter() + timeout\r\n            while not self._run_js(js) and perf_counter() < end_time:\r\n                sleep(.05)\r\n\r\n        src = self.attr('href') if self.tag == 'link' else self.attr('src')\r\n        if not src:\r\n            raise RuntimeError(_S._lang.join(_S._lang.NO_SRC_ATTR))\r\n        if src.lower().startswith('data:image'):\r\n            if base64_to_bytes:\r\n                from base64 import b64decode\r\n                return b64decode(src.split(',', 1)[-1])\r\n            else:\r\n                return src.split(',', 1)[-1]\r\n\r\n        is_blob = src.startswith('blob')\r\n        result = None\r\n        end_time = perf_counter() + timeout\r\n        if is_blob:\r\n            while perf_counter() < end_time:\r\n                result = get_blob(self.owner, src, base64_to_bytes)\r\n                if result:\r\n                    break\r\n                sleep(.05)\r\n\r\n        else:\r\n            while perf_counter() < end_time:\r\n                src = self.attr('href') if self.tag == 'link' else self.property('currentSrc') or self.property('src')\r\n                if not src:\r\n                    sleep(.01)\r\n                    continue\r\n\r\n                node = self.owner._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']\r\n                frame = node.get('frameId', None) or self.owner._frame_id\r\n\r\n                try:\r\n                    result = self.owner._run_cdp('Page.getResourceContent', frameId=frame, url=src)\r\n                    break\r\n                except CDPError:\r\n                    pass\r\n                sleep(.05)\r\n\r\n        if not result:\r\n            return None\r\n\r\n        elif is_blob:\r\n            return result\r\n\r\n        elif result['base64Encoded'] and base64_to_bytes:\r\n            from base64 import b64decode\r\n            return b64decode(result['content'])\r\n        else:\r\n            return result['content']\r\n\r\n    def save(self, path=None, name=None, timeout=None, rename=True):\r\n        data = self.src(timeout=timeout)\r\n        if not data:\r\n            raise NoResourceError\r\n\r\n        path = path or '.'\r\n        if not name and self.tag == 'img':\r\n            src = self.attr('src')\r\n            if src.lower().startswith('data:image'):\r\n                r = search(r'data:image/(.*?);base64,', src)\r\n                name = f'img.{r.group(1)}' if r else None\r\n        path = Path(path) / make_valid_name(name or basename(self.property('currentSrc')))\r\n        if not path.suffix:\r\n            path = path.with_suffix('.jpg')\r\n        if rename:\r\n            path = get_usable_path(path)\r\n        path.parent.mkdir(parents=True, exist_ok=True)\r\n        path = path.absolute()\r\n        write_type = 'wb' if isinstance(data, bytes) else 'w'\r\n\r\n        with open(path, write_type) as f:\r\n            f.write(data)\r\n\r\n        return str(path)\r\n\r\n    def get_screenshot(self, path=None, name=None, as_bytes=None, as_base64=None, scroll_to_center=True):\r\n        if self.tag == 'img':  # 等待图片加载完成\r\n            js = ('return this.complete && typeof this.naturalWidth != \"undefined\" && this.naturalWidth > 0 '\r\n                  '&& typeof this.naturalHeight != \"undefined\" && this.naturalHeight > 0')\r\n            end_time = perf_counter() + self.timeout\r\n            while not self._run_js(js) and perf_counter() < end_time:\r\n                sleep(.05)\r\n        if scroll_to_center:\r\n            self.scroll.to_see(center=True)\r\n\r\n        left, top = self.rect.location\r\n        width, height = self.rect.size\r\n        left_top = (left, top)\r\n        right_bottom = (left + width, top + height)\r\n        if not name:\r\n            name = f'{self.tag}.jpg'\r\n\r\n        return self.owner._get_screenshot(path, name, as_bytes=as_bytes, as_base64=as_base64, full_page=False,\r\n                                          left_top=left_top, right_bottom=right_bottom, ele=self)\r\n\r\n    def input(self, vals, clear=False, by_js=False):\r\n        if self.tag == 'input' and self.attr('type') == 'file':\r\n            return self._set_file_input(vals)\r\n\r\n        if by_js:\r\n            if clear:\r\n                self.clear(True)\r\n            if isinstance(vals, (list, tuple)):\r\n                vals = ''.join([str(i) for i in vals])\r\n            self.set.property('value', str(vals))\r\n            self._run_js('this.dispatchEvent(new Event(\"change\", {bubbles: true}));')\r\n            return self\r\n\r\n        self.wait.clickable(wait_moved=False, timeout=.5)\r\n        if clear and vals not in ('\\n', '\\ue007', '\\ue006'):\r\n            self.clear(by_js=False)\r\n        else:\r\n            self._input_focus()\r\n\r\n        if isinstance(vals, str) and vals not in ('\\ue003', '\\ue017', '\\ue010', '\\ue011',\r\n                                                  '\\ue012', '\\ue013', '\\ue014', '\\ue015',):\r\n            input_text_or_keys(self.owner, vals)\r\n        else:\r\n            self.owner.actions.type(vals)\r\n\r\n        return self\r\n\r\n    def clear(self, by_js=False):\r\n        if by_js or system().lower() in ('macos', 'darwin'):\r\n            self._run_js(\"this.value='';\")\r\n            self._run_js('this.dispatchEvent(new Event(\"change\", {bubbles: true}));')\r\n            return self\r\n\r\n        self._input_focus()\r\n        self.input((Keys.CTRL_A, Keys.DEL), clear=False)\r\n        return self\r\n\r\n    def _input_focus(self):\r\n        try:\r\n            self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id)\r\n        except Exception:\r\n            self.click(by_js=None)\r\n\r\n    def focus(self):\r\n        try:\r\n            self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id)\r\n        except Exception:\r\n            self._run_js('this.focus();')\r\n        return self\r\n\r\n    def hover(self, offset_x=None, offset_y=None):\r\n        self.owner.actions.move_to(self, offset_x=offset_x, offset_y=offset_y, duration=.1)\r\n        return self\r\n\r\n    def drag(self, offset_x=0, offset_y=0, duration=.5):\r\n        curr_x, curr_y = self.rect.midpoint\r\n        offset_x += curr_x\r\n        offset_y += curr_y\r\n        self.drag_to((offset_x, offset_y), duration)\r\n        return self\r\n\r\n    def drag_to(self, ele_or_loc, duration=.5):\r\n        if isinstance(ele_or_loc, ChromiumElement):\r\n            ele_or_loc = ele_or_loc.rect.midpoint\r\n        elif not isinstance(ele_or_loc, (list, tuple)):\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'ele_or_loc',\r\n                                           ALLOW_TYPE=_S._lang.ELE_LOC_FORMAT, CURR_VAL=ele_or_loc))\r\n        self.owner.actions.hold(self).move_to(ele_or_loc, duration=duration).release()\r\n        return self\r\n\r\n    def _get_obj_id(self, node_id=None, backend_id=None):\r\n        if node_id:\r\n            return self.owner._run_cdp('DOM.resolveNode', nodeId=node_id)['object']['objectId']\r\n        else:\r\n            return self.owner._run_cdp('DOM.resolveNode', backendNodeId=backend_id)['object']['objectId']\r\n\r\n    def _get_node_id(self, obj_id=None, backend_id=None):\r\n        if obj_id:\r\n            return self.owner._run_cdp('DOM.requestNode', objectId=obj_id)['nodeId']\r\n        else:\r\n            n = self.owner._run_cdp('DOM.describeNode', backendNodeId=backend_id)['node']\r\n            self._tag = n['localName']\r\n            return n['nodeId']\r\n\r\n    def _get_backend_id(self, node_id):\r\n        n = self.owner._run_cdp('DOM.describeNode', nodeId=node_id)['node']\r\n        self._tag = n['localName']\r\n        return n['backendNodeId']\r\n\r\n    def _refresh_id(self):\r\n        self._obj_id = self._get_obj_id(backend_id=self._backend_id)\r\n        self._node_id = self._get_node_id(obj_id=self._obj_id)\r\n\r\n    def _get_ele_path(self, xpath=True):\r\n        if xpath:\r\n            txt1 = 'let tag = el.nodeName.toLowerCase();'\r\n            txt3 = ''' && sib.nodeName.toLowerCase()===tag'''\r\n            txt4 = '''path = '/' + tag + '[' + nth + ']' + path;'''\r\n            txt5 = '''return path;'''\r\n\r\n        else:\r\n            txt1 = '''\r\n            let i = el.getAttribute(\"id\");\r\n            if (i){path = '>' + el.tagName.toLowerCase() + \"#\" + i + path;\r\n            el = el.parentNode;\r\n            continue;}\r\n            '''\r\n            txt3 = ''\r\n            txt4 = '''path = '>' + el.tagName.toLowerCase() + \":nth-child(\" + nth + \")\" + path;'''\r\n            txt5 = '''return path.substr(1);'''\r\n\r\n        js = '''function(){\r\n        function e(el) {\r\n            if (!(el instanceof Element)) return;\r\n            let path = '';\r\n            while (el.nodeType === Node.ELEMENT_NODE) {\r\n                ''' + txt1 + '''\r\n                    let sib = el, nth = 0;\r\n                    while (sib) {\r\n                        if(sib.nodeType === Node.ELEMENT_NODE''' + txt3 + '''){nth += 1;}\r\n                        sib = sib.previousSibling;\r\n                    }\r\n                    ''' + txt4 + '''\r\n                el = el.parentNode;\r\n            }\r\n            ''' + txt5 + '''\r\n        }\r\n        return e(this);}\r\n        '''\r\n        return self._run_js(js)\r\n\r\n    def _set_file_input(self, files):\r\n        if isinstance(files, str):\r\n            files = files.split('\\n')\r\n        files = [str(Path(i).absolute()) for i in files]\r\n        self.owner._run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=self._backend_id)\r\n        return self\r\n\r\n\r\nclass ShadowRoot(BaseElement):\r\n\r\n    def __init__(self, parent_ele, obj_id=None, backend_id=None):\r\n        super().__init__(parent_ele.owner)\r\n        self.tab = self.owner._tab\r\n        self.parent_ele = parent_ele\r\n        if backend_id:\r\n            self._backend_id = backend_id\r\n            self._obj_id = self._get_obj_id(backend_id)\r\n            self._node_id = self._get_node_id(self._obj_id)\r\n        elif obj_id:\r\n            self._obj_id = obj_id\r\n            self._node_id = self._get_node_id(obj_id)\r\n            self._backend_id = self._get_backend_id(self._node_id)\r\n        self._states = None\r\n        self._type = 'ShadowRoot'\r\n\r\n    def __call__(self, locator, index=1, timeout=None):\r\n        return self.ele(locator, index=index, timeout=timeout)\r\n\r\n    def __repr__(self):\r\n        return f'<ShadowRoot in {self.parent_ele}>'\r\n\r\n    def __eq__(self, other):\r\n        return self._backend_id == getattr(other, '_backend_id', None)\r\n\r\n    @property\r\n    def tag(self):\r\n        return 'shadow-root'\r\n\r\n    @property\r\n    def html(self):\r\n        return f'<shadow_root>{self.inner_html}</shadow_root>'\r\n\r\n    @property\r\n    def inner_html(self):\r\n        return self._run_js('return this.innerHTML;')\r\n\r\n    @property\r\n    def states(self):\r\n        if self._states is None:\r\n            self._states = ShadowRootStates(self)\r\n        return self._states\r\n\r\n    def run_js(self, script, *args, as_expr=False, timeout=None):\r\n        return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)\r\n\r\n    def _run_js(self, script, *args, as_expr=False, timeout=None):\r\n        return run_js(self, script, as_expr, self.owner.timeouts.script if timeout is None else timeout, args)\r\n\r\n    def run_async_js(self, script, *args, as_expr=False, timeout=None):\r\n        from threading import Thread\r\n        Thread(target=run_js, args=(self, script, as_expr,\r\n                                    self.owner.timeouts.script if timeout is None else timeout, args)).start()\r\n\r\n    def parent(self, level_or_loc=1, index=1, timeout=0):\r\n        if isinstance(level_or_loc, int):\r\n            loc = f'xpath:./ancestor-or-self::*[{level_or_loc}]'\r\n\r\n        elif isinstance(level_or_loc, (tuple, str)):\r\n            loc = get_loc(level_or_loc, True)\r\n\r\n            if loc[0] == 'css selector':\r\n                raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n\r\n            loc = f'xpath:./ancestor-or-self::{loc[1].lstrip(\". / \")}[{index}]'\r\n\r\n        else:\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'level_or_loc',\r\n                                           ALLOW_TYPE=_S._lang.LOC_OR_IND, CURR_VAL=level_or_loc))\r\n\r\n        return self.parent_ele._ele(loc, timeout=timeout, relative=True, raise_err=False, method='parent()')\r\n\r\n    def child(self, locator='', index=1, timeout=None):\r\n        if not locator:\r\n            loc = '*'\r\n        else:\r\n            loc = get_loc(locator, True)  # 把定位符转换为xpath\r\n            if loc[0] == 'css selector':\r\n                raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n            loc = loc[1].lstrip('./')\r\n\r\n        loc = f'xpath:./{loc}'\r\n        ele = self._ele(loc, index=index, relative=True, timeout=timeout)\r\n\r\n        return ele if ele else NoneElement(self.owner, 'child()',\r\n                                           {'locator': locator, 'index': index, 'timeout': timeout})\r\n\r\n    def next(self, locator='', index=1, timeout=None):\r\n        loc = get_loc(locator, True)\r\n        if loc[0] == 'css selector':\r\n            raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n\r\n        loc = loc[1].lstrip('./')\r\n        xpath = f'xpath:./{loc}'\r\n        ele = self.parent_ele._ele(xpath, index=index, relative=True, timeout=timeout)\r\n\r\n        return ele if ele else NoneElement(self.owner, 'next()',\r\n                                           {'locator': locator, 'index': index, 'timeout': timeout})\r\n\r\n    def before(self, locator='', index=1, timeout=None):\r\n        loc = get_loc(locator, True)\r\n        if loc[0] == 'css selector':\r\n            raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n\r\n        loc = loc[1].lstrip('./')\r\n        xpath = f'xpath:./preceding::{loc}'\r\n        ele = self.parent_ele._ele(xpath, index=index, relative=True, timeout=timeout)\r\n\r\n        return ele if ele else NoneElement(self.owner, 'before()',\r\n                                           {'locator': locator, 'index': index, 'timeout': timeout})\r\n\r\n    def after(self, locator='', index=1, timeout=None):\r\n        nodes = self.afters(locator=locator, timeout=timeout)\r\n        return nodes[index - 1] if nodes else NoneElement(self.owner, 'after()',\r\n                                                          {'locator': locator, 'index': index, 'timeout': timeout})\r\n\r\n    def children(self, locator='', timeout=None):\r\n        if not locator:\r\n            loc = '*'\r\n        else:\r\n            loc = get_loc(locator, True)  # 把定位符转换为xpath\r\n            if loc[0] == 'css selector':\r\n                raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n            loc = loc[1].lstrip('./')\r\n\r\n        loc = f'xpath:./{loc}'\r\n        return self._ele(loc, index=None, relative=True, timeout=timeout)\r\n\r\n    def nexts(self, locator='', timeout=None):\r\n        loc = get_loc(locator, True)\r\n        if loc[0] == 'css selector':\r\n            raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n\r\n        loc = loc[1].lstrip('./')\r\n        xpath = f'xpath:./{loc}'\r\n        return self.parent_ele._ele(xpath, index=None, relative=True, timeout=timeout)\r\n\r\n    def befores(self, locator='', timeout=None):\r\n        loc = get_loc(locator, True)\r\n        if loc[0] == 'css selector':\r\n            raise LocatorError(_S._lang.UNSUPPORTED_CSS_SYNTAX)\r\n\r\n        loc = loc[1].lstrip('./')\r\n        xpath = f'xpath:./preceding::{loc}'\r\n        return self.parent_ele._ele(xpath, index=None, relative=True, timeout=timeout)\r\n\r\n    def afters(self, locator='', timeout=None):\r\n        eles1 = self.nexts(locator)\r\n        loc = get_loc(locator, True)[1].lstrip('./')\r\n        xpath = f'xpath:./following::{loc}'\r\n        return eles1 + self.parent_ele._ele(xpath, index=None, relative=True, timeout=timeout)\r\n\r\n    def ele(self, locator, index=1, timeout=None):\r\n        return self._ele(locator, timeout, index=index, method='ele()')\r\n\r\n    def eles(self, locator, timeout=None):\r\n        return self._ele(locator, timeout=timeout, index=None)\r\n\r\n    def s_ele(self, locator=None, index=1, timeout=None):\r\n        return (make_session_ele(self, locator, index=index, method='s_ele()')\r\n                if locator is None or self.ele(locator, index=index, timeout=timeout)\r\n                else NoneElement(self.owner, method='s_ele()', args={'locator': locator, 'index': index}))\r\n\r\n    def s_eles(self, locator, timeout=None):\r\n        return (make_session_ele(self, locator, index=None)\r\n                if self.ele(locator, timeout=timeout) else SessionElementsList())\r\n\r\n    def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):\r\n        loc = get_loc(locator, css_mode=False)\r\n        if loc[0] == 'css selector' and str(loc[1]).startswith(':root'):\r\n            loc = loc[0], loc[1][5:]\r\n\r\n        def do_find():\r\n            if loc[0] == 'css selector':\r\n                if index == 1:\r\n                    nod_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, selector=loc[1])['nodeId']\r\n                    if nod_id:\r\n                        r = make_chromium_eles(self.owner, _ids=nod_id, is_obj_id=False)\r\n                        return None if r is False else r\r\n\r\n                else:\r\n                    nod_ids = self.owner._run_cdp('DOM.querySelectorAll',\r\n                                                  nodeId=self._node_id, selector=loc[1])['nodeIds']\r\n                    r = make_chromium_eles(self.owner, _ids=nod_ids, index=index, is_obj_id=False)\r\n                    return None if r is False else r\r\n\r\n            else:\r\n                eles = make_session_ele(self, loc, index=None)\r\n                if isinstance(eles, (float, str, int)):\r\n                    return eles\r\n                elif not eles:\r\n                    return None\r\n\r\n                css = []\r\n                for i in eles:\r\n                    if hasattr(i, 'css_path'):\r\n                        c = i.css_path\r\n                        if c in ('html:nth-child(1)', 'html:nth-child(1)>body:nth-child(1)',\r\n                                 'html:nth-child(1)>body:nth-child(1)>shadow_root:nth-child(1)'):\r\n                            continue\r\n                        elif c.startswith('html:nth-child(1)>body:nth-child(1)>shadow_root:nth-child(1)'):\r\n                            c = c[61:]\r\n                        css.append((True, c))\r\n\r\n                    else:\r\n                        css.append((False, i))\r\n\r\n                if index is not None:\r\n                    try:\r\n                        c = css[index - 1]\r\n                        if c[0] is False:\r\n                            return c[1]\r\n                        node_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id,\r\n                                                      selector=c[1])['nodeId']\r\n                        r = make_chromium_eles(self.owner, _ids=node_id, is_obj_id=False)\r\n                        return None if r is False else r\r\n\r\n                    except IndexError:\r\n                        return None\r\n\r\n                else:\r\n                    r = []\r\n                    for i in css:\r\n                        if i[0] is False:\r\n                            r.append(i[1])\r\n\r\n                        else:\r\n                            node_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id,\r\n                                                          selector=i[1])['nodeId']\r\n                            if node_id:\r\n                                e = make_chromium_eles(self.owner, _ids=node_id, is_obj_id=False)\r\n                                if e is False:\r\n                                    return None\r\n                                r.append(e)\r\n\r\n                    return None if not r else r\r\n\r\n        end_time = perf_counter() + timeout\r\n        result = do_find()\r\n        while result is None and perf_counter() <= end_time:\r\n            sleep(.01)\r\n            result = do_find()\r\n\r\n        if result or isinstance(result, (str, float, int)):\r\n            return result\r\n        return NoneElement(self.owner) if index is not None else ChromiumElementsList(self.owner)\r\n\r\n    def _get_node_id(self, obj_id):\r\n        return self.owner._run_cdp('DOM.requestNode', objectId=obj_id)['nodeId']\r\n\r\n    def _get_obj_id(self, back_id):\r\n        return self.owner._run_cdp('DOM.resolveNode', backendNodeId=back_id)['object']['objectId']\r\n\r\n    def _get_backend_id(self, node_id):\r\n        r = self.owner._run_cdp('DOM.describeNode', nodeId=node_id)['node']\r\n        self._tag = r['localName'].lower()\r\n        return r['backendNodeId']\r\n\r\n\r\ndef find_in_chromium_ele(ele, locator, index=1, timeout=None, relative=True):\r\n    # ---------------处理定位符---------------\r\n    if isinstance(locator, (str, tuple)):\r\n        loc = get_loc(locator)\r\n    else:\r\n        raise LocatorError(ALLOW_TYPE=_S._lang.LOC_FORMAT, CURR_VAL=locator)\r\n\r\n    loc_str = loc[1]\r\n    if loc[0] == 'xpath' and loc[1].lstrip().startswith('/'):\r\n        loc_str = f'.{loc_str}'\r\n    elif loc[0] == 'css selector' and loc[1].lstrip().startswith('>'):\r\n        loc_str = f'{ele.css_path}{loc[1]}'\r\n    loc = loc[0], loc_str\r\n\r\n    if timeout is None:\r\n        timeout = ele.timeout\r\n\r\n    # ---------------执行查找-----------------\r\n    if loc[0] == 'xpath':\r\n        return find_by_xpath(ele, loc[1], index, timeout, relative=relative)\r\n\r\n    else:\r\n        return find_by_css(ele, loc[1], index, timeout)\r\n\r\n\r\ndef find_by_xpath(ele, xpath, index, timeout, relative=True):\r\n    type_txt = '9' if index == 1 else '7'\r\n    node_txt = 'this.contentDocument' if ele.tag in __FRAME_ELEMENT__ and not relative else 'this'\r\n    js = make_js_for_find_ele_by_xpath(xpath, type_txt, node_txt)\r\n    ele.owner.wait.doc_loaded()\r\n\r\n    def do_find():\r\n        res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id,\r\n                                 returnByValue=False, awaitPromise=True, userGesture=True)\r\n        if res['result']['type'] == 'string':\r\n            return res['result']['value']\r\n        if 'exceptionDetails' in res:\r\n            if 'The result is not a node set' in res['result']['description']:\r\n                js1 = make_js_for_find_ele_by_xpath(xpath, '1', node_txt)\r\n                res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js1, objectId=ele._obj_id,\r\n                                         returnByValue=False, awaitPromise=True, userGesture=True)\r\n                return res['result']['value']\r\n            elif 'is not a valid XPath expression' in res['result']['description']:\r\n                raise LocatorError(_S._lang.INVALID_XPATH_, xpath)\r\n            else:\r\n                raise LocatorError(_S._lang.FIND_ELE_ERR, INFO=res)\r\n\r\n        if res['result']['subtype'] == 'null' or res['result']['description'] in ('NodeList(0)', 'Array(0)'):\r\n            return None\r\n\r\n        if index == 1:\r\n            r = make_chromium_eles(ele.owner, _ids=res['result']['objectId'], is_obj_id=True)\r\n            return None if r is False else r\r\n\r\n        else:\r\n            res = ele.owner._run_cdp('Runtime.getProperties', objectId=res['result']['objectId'],\r\n                                     ownProperties=True)['result'][:-1]\r\n            if index is None:\r\n                r = ChromiumElementsList(owner=ele.owner)\r\n                for i in res:\r\n                    if i['value']['type'] == 'object':\r\n                        r.append(make_chromium_eles(ele.owner, _ids=i['value']['objectId'], is_obj_id=True))\r\n                    else:\r\n                        r.append(i['value']['value'])\r\n                return None if False in r else r\r\n\r\n            else:\r\n                eles_count = len(res)\r\n                if eles_count == 0 or abs(index) > eles_count:\r\n                    return None\r\n\r\n                index1 = eles_count + index + 1 if index < 0 else index\r\n                res = res[index1 - 1]\r\n                if res['value']['type'] == 'object':\r\n                    r = make_chromium_eles(ele.owner, _ids=res['value']['objectId'], is_obj_id=True)\r\n                else:\r\n                    r = res['value']['value']\r\n                return None if r is False else r\r\n\r\n    end_time = perf_counter() + timeout\r\n    result = do_find()\r\n    while result is None and perf_counter() < end_time:\r\n        sleep(.01)\r\n        result = do_find()\r\n\r\n    if result:\r\n        return result\r\n    return NoneElement(ele.owner) if index is not None else ChromiumElementsList(owner=ele.owner)\r\n\r\n\r\ndef find_by_css(ele, selector, index, timeout):\r\n    selector = selector.replace('\"', r'\\\"')\r\n    find_all = '' if index == 1 else 'All'\r\n    node_txt = 'this.contentDocument' if ele.tag in ('iframe', 'frame', 'shadow-root') else 'this'\r\n    js = f'function(){{return {node_txt}.querySelector{find_all}(\"{selector}\");}}'\r\n\r\n    ele.owner.wait.doc_loaded()\r\n\r\n    def do_find():\r\n        res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id,\r\n                                 returnByValue=False, awaitPromise=True, userGesture=True)\r\n\r\n        if 'exceptionDetails' in res:\r\n            if 'is not a valid selector' in res['result']['description']:\r\n                raise LocatorError(_S._lang.INVALID_CSS_, selector)\r\n            else:\r\n                raise LocatorError(_S._lang.FIND_ELE_ERR, INFO=res)\r\n        if res['result']['subtype'] == 'null' or res['result']['description'] in ('NodeList(0)', 'Array(0)'):\r\n            return None\r\n\r\n        if index == 1:\r\n            r = make_chromium_eles(ele.owner, _ids=res['result']['objectId'], is_obj_id=True)\r\n            return None if r is False else r\r\n\r\n        else:\r\n            obj_ids = [i['value']['objectId'] for i in ele.owner._run_cdp('Runtime.getProperties',\r\n                                                                          objectId=res['result']['objectId'],\r\n                                                                          ownProperties=True)['result']]\r\n            r = make_chromium_eles(ele.owner, _ids=obj_ids, index=index, is_obj_id=True)\r\n            return None if r is False else r\r\n\r\n    end_time = perf_counter() + timeout\r\n    result = do_find()\r\n    while result is None and perf_counter() < end_time:\r\n        sleep(.01)\r\n        result = do_find()\r\n\r\n    if result:\r\n        return result\r\n    return NoneElement(ele.owner) if index is not None else ChromiumElementsList(owner=ele.owner)\r\n\r\n\r\ndef make_chromium_eles(page, _ids, index=1, is_obj_id=True, ele_only=False):\r\n    if is_obj_id:\r\n        get_node_func = _get_node_by_obj_id\r\n    else:\r\n        get_node_func = _get_node_by_node_id\r\n    if not isinstance(_ids, (list, tuple)):\r\n        _ids = (_ids,)\r\n\r\n    if index is not None:  # 获取一个\r\n        if ele_only:\r\n            for obj_id in _ids:\r\n                tmp = get_node_func(page, obj_id, ele_only)\r\n                if tmp is not None:\r\n                    return tmp\r\n            return False\r\n\r\n        else:\r\n            obj_id = _ids[index - 1]\r\n            return get_node_func(page, obj_id, ele_only)\r\n\r\n    else:  # 获取全部\r\n        nodes = ChromiumElementsList(owner=page)\r\n        for obj_id in _ids:\r\n            # if obj_id == 0:\r\n            #     continue\r\n            tmp = get_node_func(page, obj_id, ele_only)\r\n            if tmp is False:\r\n                return False\r\n            elif tmp is not None:\r\n                nodes.append(tmp)\r\n        return nodes\r\n\r\n\r\ndef _get_node_info(page, id_type, _id):\r\n    if not _id:\r\n        return False\r\n    arg = {id_type: _id}\r\n    node = page.driver.run('DOM.describeNode', **arg)\r\n    if 'error' in node:\r\n        return False\r\n    return node\r\n\r\n\r\ndef _get_node_by_obj_id(page, obj_id, ele_only):\r\n    \"\"\"根据obj id返回元素对象或文本，ele_only时如果是文本返回None，出错返回False\"\"\"\r\n    node = _get_node_info(page, 'objectId', obj_id)\r\n    if node is False:\r\n        return False\r\n    if node['node']['nodeName'] in ('#text', '#comment'):\r\n        return None if ele_only else node['node']['nodeValue']\r\n    else:\r\n        return _make_ele(page, obj_id, node)\r\n\r\n\r\ndef _get_node_by_node_id(page, node_id, ele_only):\r\n    \"\"\"根据node id返回元素对象或文本，ele_only时如果是文本返回None，出错返回False\"\"\"\r\n    node = _get_node_info(page, 'nodeId', node_id)\r\n    if node is False:\r\n        return False\r\n    if node['node']['nodeName'] in ('#text', '#comment'):\r\n        return None if ele_only else node['node']['nodeValue']\r\n    else:\r\n        obj_id = page.driver.run('DOM.resolveNode', nodeId=node_id)\r\n        if 'error' in obj_id:\r\n            return False\r\n        obj_id = obj_id['object']['objectId']\r\n        return _make_ele(page, obj_id, node)\r\n\r\n\r\ndef _make_ele(page, obj_id, node):\r\n    ele = ChromiumElement(page, obj_id=obj_id, node_id=node['node']['nodeId'],\r\n                          backend_id=node['node']['backendNodeId'])\r\n    if ele.tag in __FRAME_ELEMENT__:\r\n        from .._pages.chromium_frame import ChromiumFrame\r\n        ele = ChromiumFrame(page, ele, node)\r\n    return ele\r\n\r\n\r\ndef make_js_for_find_ele_by_xpath(xpath, type_txt, node_txt):\r\n    for_txt = ''\r\n\r\n    # 获取第一个元素、节点或属性\r\n    if type_txt == '9':\r\n        return_txt = '''\r\nif(e.singleNodeValue==null){return null;}\r\nelse if(e.singleNodeValue.constructor.name==\"Text\"){return e.singleNodeValue.data;}\r\nelse if(e.singleNodeValue.constructor.name==\"Attr\"){return e.singleNodeValue.nodeValue;}\r\nelse if(e.singleNodeValue.constructor.name==\"Comment\"){return e.singleNodeValue.nodeValue;}\r\nelse{return e.singleNodeValue;}'''\r\n\r\n    # 按顺序获取所有元素、节点或属性\r\n    elif type_txt == '7':\r\n        for_txt = \"\"\"\r\nlet a=new Array();\r\nfor(let i = 0; i <e.snapshotLength ; i++){\r\nif(e.snapshotItem(i).constructor.name==\"Text\"){a.push(e.snapshotItem(i).data);}\r\nelse if(e.snapshotItem(i).constructor.name==\"Attr\"){a.push(e.snapshotItem(i).nodeValue);}\r\nelse if(e.snapshotItem(i).constructor.name==\"Comment\"){a.push(e.snapshotItem(i).nodeValue);}\r\nelse{a.push(e.snapshotItem(i));}}\"\"\"\r\n        return_txt = 'return a;'\r\n\r\n    elif type_txt == '2':\r\n        return_txt = 'return e.stringValue;'\r\n    elif type_txt == '1':\r\n        return_txt = 'return e.numberValue;'\r\n    else:\r\n        return_txt = 'return e.singleNodeValue;'\r\n\r\n    xpath = xpath.replace(r\"'\", r\"\\'\")\r\n    js = f'function(){{let e=document.evaluate(\\'{xpath}\\',{node_txt},null,{type_txt},null);\\n{for_txt}\\n{return_txt}}}'\r\n\r\n    return js\r\n\r\n\r\ndef run_js(page_or_ele, script, as_expr, timeout, args=None):\r\n    if isinstance(page_or_ele, (ChromiumElement, ShadowRoot)):\r\n        is_page = False\r\n        page = page_or_ele.owner\r\n        obj_id = page_or_ele._obj_id\r\n    else:\r\n        is_page = True\r\n        page = page_or_ele\r\n        end_time = perf_counter() + 5\r\n        while perf_counter() < end_time:\r\n            obj_id = page_or_ele._root_id\r\n            if obj_id is not None:\r\n                break\r\n            sleep(.01)\r\n        else:\r\n            raise RuntimeError(_S._lang.join(_S._lang.JS_RUNTIME_ERR))\r\n\r\n    if page.states.has_alert:\r\n        raise AlertExistsError\r\n\r\n    try:\r\n        if Path(script).exists():\r\n            with open(script, 'r', encoding='utf-8') as f:\r\n                script = f.read()\r\n    except (OSError, ValueError):\r\n        pass\r\n\r\n    end_time = perf_counter() + timeout\r\n    try:\r\n        if as_expr:\r\n            res = page._run_cdp('Runtime.evaluate', expression=script, returnByValue=False,\r\n                                awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError)\r\n\r\n        else:\r\n            args = args or ()\r\n            if not is_js_func(script):\r\n                script = f'function(){{{script}}}'\r\n            res = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=script, objectId=obj_id,\r\n                                arguments=[convert_argument(arg) for arg in args], returnByValue=False,\r\n                                awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError)\r\n    except TimeoutError:\r\n        raise TimeoutError(_S._lang.join(_S._lang.TIMEOUT_, _S._lang.RUN_JS, timeout))\r\n    except ContextLostError:\r\n        raise ContextLostError() if is_page else ElementLostError()\r\n\r\n    if not res:  # _timeout=0或js激活alert时\r\n        return None\r\n\r\n    exceptionDetails = res.get('exceptionDetails')\r\n    if exceptionDetails:\r\n        raise JavaScriptError(JS=script, INFO=exceptionDetails)\r\n\r\n    try:\r\n        return parse_js_result(page, page_or_ele, res.get('result'), end_time)\r\n    except Exception:\r\n        from DrissionPage import __version__\r\n        raise RuntimeError(_S._lang.join(_S._lang.JS_RESULT_ERR, INFO=res, JS=script, TIP=_S._lang.FEEDBACK))\r\n\r\n\r\ndef parse_js_result(page, ele, result, end_time):\r\n    if 'unserializableValue' in result:\r\n        return result['unserializableValue']\r\n\r\n    the_type = result['type']\r\n    if the_type == 'object':\r\n        sub_type = result.get('subtype', None)\r\n        if sub_type == 'null':\r\n            return None\r\n\r\n        elif sub_type == 'node':\r\n            class_name = result['className']\r\n            if class_name == 'ShadowRoot':\r\n                return ShadowRoot(ele, obj_id=result['objectId'])\r\n            elif class_name == 'HTMLDocument':\r\n                return result\r\n            else:\r\n                r = make_chromium_eles(page, _ids=result['objectId'])\r\n                if r is False:\r\n                    raise ElementLostError\r\n                return r\r\n\r\n        elif sub_type == 'array':\r\n            r = page._run_cdp('Runtime.getProperties', objectId=result['objectId'], ownProperties=True)['result']\r\n            return [parse_js_result(page, ele, result=i['value'], end_time=end_time) for i in r if i['name'].isdigit()]\r\n\r\n        elif result.get('className') == 'Blob':\r\n            data = page._run_cdp('IO.read',\r\n                                 handle=f\"blob:{page._run_cdp('IO.resolveBlob', objectId=result['objectId'])['uuid']}\")[\r\n                'data']\r\n            return data\r\n\r\n        elif 'objectId' in result:\r\n            timeout = end_time - perf_counter()\r\n            if timeout < 0:\r\n                return\r\n            js = 'function(){return JSON.stringify(this);}'\r\n            r = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=result['objectId'],\r\n                              returnByValue=False, awaitPromise=True, userGesture=True, _ignore=AlertExistsError,\r\n                              _timeout=timeout)\r\n            return loads(parse_js_result(page, ele, r['result'], end_time))\r\n\r\n        else:\r\n            return result.get('value', result)\r\n\r\n    elif the_type == 'undefined':\r\n        return None\r\n\r\n    elif the_type == 'function':\r\n        return result['description']\r\n\r\n    else:\r\n        return result['value']\r\n\r\n\r\ndef convert_argument(arg):\r\n    if isinstance(arg, ChromiumElement):\r\n        return {'objectId': arg._obj_id}\r\n\r\n    elif isinstance(arg, (int, float, str, bool, dict)):\r\n        return {'value': arg}\r\n\r\n    from math import inf\r\n    if arg == inf:\r\n        return {'unserializableValue': 'Infinity'}\r\n    elif arg == -inf:\r\n        return {'unserializableValue': '-Infinity'}\r\n\r\n    raise TypeError(_S._lang.join(_S._lang.UNSUPPORTED_ARG_TYPE_, arg, type(arg)))\r\n\r\n\r\nclass Pseudo(object):\r\n    def __init__(self, ele):\r\n        self._ele = ele\r\n\r\n    @property\r\n    def before(self):\r\n        for i in ('::before', ':before', 'before'):\r\n            r = self._ele.style('content', i)\r\n            if r != 'content':\r\n                return r\r\n        return r\r\n\r\n    @property\r\n    def after(self):\r\n        for i in ('::after', ':after', 'after'):\r\n            r = self._ele.style('content', i)\r\n            if r != 'content':\r\n                return r\r\n        return r\r\n\r\n\r\ndef _check_ele(ele, loc_data):\r\n    \"\"\"检查元素是否符合loc_data指定的要求\r\n    :param ele: 元素对象\r\n    :param loc_data: 格式： {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]}\r\n    :return: bool\r\n    \"\"\"\r\n    attrs = ele.attrs\r\n    if loc_data['and']:\r\n        ok = True\r\n        for i in loc_data['args']:\r\n            name, symbol, value, deny = i\r\n            if name == 'tag()':\r\n                arg = ele.tag\r\n                symbol = '='\r\n            elif name == 'text()':\r\n                arg = ele.raw_text\r\n            elif name is None:\r\n                arg = None\r\n            else:\r\n                arg = attrs.get(name, '')\r\n\r\n            if ((symbol == '=' and ((deny and arg == value) or (not deny and arg != value)))\r\n                    or (symbol == ':' and ((deny and value in arg) or (not deny and value not in arg)))\r\n                    or (symbol == '^' and ((deny and arg.startswith(value))\r\n                                           or (not deny and not arg.startswith(value))))\r\n                    or (symbol == '$' and ((deny and arg.endswith(value)) or (not deny and not arg.endswith(value))))\r\n                    or (arg is None and attrs)):\r\n                ok = False\r\n                break\r\n\r\n    else:\r\n        ok = False\r\n        for i in loc_data['args']:\r\n            name, value, symbol, deny = i\r\n            if name == 'tag()':\r\n                arg = ele.tag\r\n                symbol = '='\r\n            elif name == 'text()':\r\n                arg = ele.text\r\n            elif name is None:\r\n                arg = None\r\n            else:\r\n                arg = attrs.get(name, '')\r\n\r\n            if ((symbol == '=' and ((not deny and arg == value) or (deny and arg != value)))\r\n                    or (symbol == ':' and ((not deny and value in arg) or (deny and value not in arg)))\r\n                    or (symbol == '^' and ((not deny and arg.startswith(value))\r\n                                           or (deny and not arg.startswith(value))))\r\n                    or (symbol == '$' and ((not deny and arg.endswith(value)) or (deny and not arg.endswith(value))))\r\n                    or (arg is None and not attrs)):\r\n                ok = True\r\n                break\r\n\r\n    return ok\r\n"
  },
  {
    "path": "DrissionPage/_elements/chromium_element.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Union, Tuple, List, Any, Literal, Optional\r\n\r\nfrom .._base.base import DrissionElement, BaseElement\r\nfrom .._elements.session_element import SessionElement\r\nfrom .._functions.elements import SessionElementsList, ChromiumElementsList\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.chromium_frame import ChromiumFrame\r\nfrom .._pages.chromium_page import ChromiumPage\r\nfrom .._pages.chromium_tab import ChromiumTab\r\nfrom .._pages.web_page import WebPage\r\nfrom .._units.clicker import Clicker\r\nfrom .._units.rect import ElementRect\r\nfrom .._units.scroller import ElementScroller\r\nfrom .._units.selector import SelectElement\r\nfrom .._units.setter import ChromiumElementSetter\r\nfrom .._units.states import ShadowRootStates, ElementStates\r\nfrom .._units.waiter import ElementWaiter\r\n\r\nPIC_TYPE = Literal['jpg', 'jpeg', 'png', 'webp', True]\r\n\r\n\r\nclass ChromiumElement(DrissionElement):\r\n    _tag: Optional[str] = ...\r\n    owner: ChromiumBase = ...\r\n    page: Union[ChromiumPage, WebPage] = ...\r\n    tab: Union[ChromiumPage, ChromiumTab] = ...\r\n    _node_id: int = ...\r\n    _obj_id: str = ...\r\n    _backend_id: int = ...\r\n    _doc_id: Optional[str] = ...\r\n    _scroll: Optional[ElementScroller] = ...\r\n    _clicker: Optional[Clicker] = ...\r\n    _select: Union[SelectElement, None, False] = ...\r\n    _wait: Optional[ElementWaiter] = ...\r\n    _rect: Optional[ElementRect] = ...\r\n    _set: Optional[ChromiumElementSetter] = ...\r\n    _states: Optional[ElementStates] = ...\r\n    _pseudo: Optional[Pseudo] = ...\r\n\r\n    def __init__(self,\r\n                 owner: ChromiumBase,\r\n                 node_id: int = None,\r\n                 obj_id: str = None,\r\n                 backend_id: int = None):\r\n        \"\"\"node_id、obj_id和backend_id必须至少传入一个\r\n        :param owner: 元素所在页面对象\r\n        :param node_id: cdp中的node id\r\n        :param obj_id: js中的object id\r\n        :param backend_id: backend id\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self,\r\n                 locator: Union[Tuple[str, str], str],\r\n                 index: int = 1,\r\n                 timeout: float = None) -> ChromiumElement:\r\n        \"\"\"在内部查找元素\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 超时时间（秒）\r\n        :return: ChromiumElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def __repr__(self) -> str: ...\r\n\r\n    def __eq__(self, other: ChromiumElement) -> bool: ...\r\n\r\n    @property\r\n    def tag(self) -> str:\r\n        \"\"\"返回元素tag\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def html(self) -> str:\r\n        \"\"\"返回元素outerHTML文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def inner_html(self) -> str:\r\n        \"\"\"返回元素innerHTML文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def attrs(self) -> dict:\r\n        \"\"\"返回元素所有attribute属性\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def text(self) -> str:\r\n        \"\"\"返回元素内所有文本，文本已格式化\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def raw_text(self) -> str:\r\n        \"\"\"返回未格式化处理的元素内文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def set(self) -> ChromiumElementSetter:\r\n        \"\"\"返回用于设置元素属性的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def states(self) -> ElementStates:\r\n        \"\"\"返回用于获取元素状态的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def pseudo(self) -> Pseudo:\r\n        \"\"\"返回用于获取伪元素内容的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def rect(self) -> ElementRect:\r\n        \"\"\"返回用于获取元素位置的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def shadow_root(self) -> Union[None, ShadowRoot]:\r\n        \"\"\"返回当前元素的shadow_root元素对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def sr(self) -> Union[None, ShadowRoot]:\r\n        \"\"\"返回当前元素的shadow_root元素对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def scroll(self) -> ElementScroller:\r\n        \"\"\"用于滚动滚动条的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def click(self) -> Clicker:\r\n        \"\"\"返回用于点击的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def wait(self) -> ElementWaiter:\r\n        \"\"\"返回用于等待的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def select(self) -> Union[SelectElement, False]:\r\n        \"\"\"返回专门处理下拉列表的Select类，非<select>元素返回False\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def value(self) -> str:\r\n        \"\"\"返回元素property属性的value值\"\"\"\r\n        ...\r\n\r\n    def parent(self,\r\n               level_or_loc: Union[tuple, str, int] = 1,\r\n               index: int = 1,\r\n               timeout: float = 0) -> ChromiumElement:\r\n        \"\"\"返回上面某一级父元素，可指定层数或用查询语法定位\r\n        :param level_or_loc: 第几级父元素，1开始，或定位符\r\n        :param index: 当level_or_loc传入定位符，使用此参数选择第几个结果，1开始\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: 上级元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def child(self,\r\n              locator: Union[Tuple[str, str], str, int] = '',\r\n              index: int = 1,\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[ChromiumElement, str]:\r\n        \"\"\"返回当前元素的一个符合条件的直接子元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 直接子元素或节点文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def prev(self,\r\n             locator: Union[Tuple[str, str], str, int] = '',\r\n             index: int = 1,\r\n             timeout: float = None,\r\n             ele_only: bool = True) -> Union[ChromiumElement, str]:\r\n        \"\"\"返回当前元素前面一个符合条件的同级元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 兄弟元素或节点文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def next(self,\r\n             locator: Union[Tuple[str, str], str, int] = '',\r\n             index: int = 1,\r\n             timeout: float = None,\r\n             ele_only: bool = True) -> Union[ChromiumElement, str]:\r\n        \"\"\"返回当前元素后面一个符合条件的同级元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 兄弟元素或节点文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def before(self,\r\n               locator: Union[Tuple[str, str], str, int] = '',\r\n               index: int = 1,\r\n               timeout: float = None,\r\n               ele_only: bool = True) -> Union[ChromiumElement, str]:\r\n        \"\"\"返回文档中当前元素前面符合条件的一个元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def after(self,\r\n              locator: Union[Tuple[str, str], str, int] = '',\r\n              index: int = 1,\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[ChromiumElement, str]:\r\n        \"\"\"返回文档中此当前元素后面符合条件的一个元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素后面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def children(self,\r\n                 locator: Union[Tuple[str, str], str] = '',\r\n                 timeout: float = None,\r\n                 ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]:\r\n        \"\"\"返回当前元素符合条件的直接子元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 直接子元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def prevs(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]:\r\n        \"\"\"返回当前元素前面符合条件的同级元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 兄弟元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def nexts(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]:\r\n        \"\"\"返回当前元素后面符合条件的同级元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 兄弟元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def befores(self,\r\n                locator: Union[Tuple[str, str], str] = '',\r\n                timeout: float = None,\r\n                ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]:\r\n        \"\"\"返回文档中当前元素前面符合条件的元素或节点组成的列表，可用查询语法筛选\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def afters(self,\r\n               locator: Union[Tuple[str, str], str] = '',\r\n               timeout: float = None,\r\n               ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]:\r\n        \"\"\"返回文档中当前元素后面符合条件的元素或节点组成的列表，可用查询语法筛选\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素后面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def over(self, timeout: float = None) -> ChromiumElement:\r\n        \"\"\"获取覆盖在本元素上最上层的元素\r\n        :param timeout: 等待元素出现的超时时间（秒）\r\n        :return: 元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def offset(self,\r\n               locator: Optional[str] = None,\r\n               x: int = None,\r\n               y: int = None,\r\n               timeout: float = None) -> ChromiumElement:\r\n        \"\"\"获取相对本元素左上角左边指定偏移量位置的元素，如果offset_x和offset_y都是None，定位到元素中间点\r\n        :param locator: 定位符，只支持str，且不支持xpath和css方式\r\n        :param x: 横坐标偏移量，向右为正\r\n        :param y: 纵坐标偏移量，向下为正\r\n        :param timeout: 超时时间（秒），为None使用所在页面设置\r\n        :return: 元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def east(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement:\r\n        \"\"\"获取元素右边某个指定元素\r\n        :param loc_or_pixel: 定位符，只支持str或int，且不支持xpath和css方式，传入int按像素距离获取\r\n        :param index: 第几个，从1开始\r\n        :return: 获取到的元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def south(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement:\r\n        \"\"\"获取元素下方某个指定元素\r\n        :param loc_or_pixel: 定位符，只支持str或int，且不支持xpath和css方式，传入int按像素距离获取\r\n        :param index: 第几个，从1开始\r\n        :return: 获取到的元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def west(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement:\r\n        \"\"\"获取元素左边某个指定元素\r\n        :param loc_or_pixel: 定位符，只支持str或int，且不支持xpath和css方式，传入int按像素距离获取\r\n        :param index: 第几个，从1开始\r\n        :return: 获取到的元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def north(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement:\r\n        \"\"\"获取元素上方某个指定元素\r\n        :param loc_or_pixel: 定位符，只支持str或int，且不支持xpath和css方式，传入int按像素距离获取\r\n        :param index: 第几个，从1开始\r\n        :return: 获取到的元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _get_relative_eles(self,\r\n                           mode: str = 'north',\r\n                           locator: Union[int, str] = None,\r\n                           index: int = 1) -> ChromiumElement:\r\n        \"\"\"获取元素下方某个指定元素\r\n        :param locator: 定位符，只支持str或int，且不支持xpath和css方式\r\n        :param index: 第几个，从1开始\r\n        :return: 获取到的元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def check(self, uncheck: bool = False, by_js: bool = False) -> None:\r\n        \"\"\"选中或取消选中当前元素\r\n        :param uncheck: 是否取消选中\r\n        :param by_js: 是否用js执行\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def attr(self, name: str) -> Union[str, None]:\r\n        \"\"\"返回一个attribute属性值\r\n        :param name: 属性名\r\n        :return: 属性值文本，没有该属性返回None\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove_attr(self, name: str) -> ChromiumElement:\r\n        \"\"\"删除元素一个attribute属性\r\n        :param name: 属性名\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def property(self, name: str) -> Union[str, int, None]:\r\n        \"\"\"获取一个property属性值\r\n        :param name: 属性名\r\n        :return: 属性值文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any:\r\n        \"\"\"对本元素执行javascript代码\r\n        :param script: js文本，文本中用this表示本元素\r\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\r\n        :param as_expr: 是否作为表达式运行，为True时args无效\r\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script设置\r\n        :return: 运行的结果\r\n        \"\"\"\r\n        ...\r\n\r\n    def _run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any:\r\n        \"\"\"对本元素执行javascript代码\r\n        :param script: js文本，文本中用this表示本元素\r\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\r\n        :param as_expr: 是否作为表达式运行，为True时args无效\r\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script设置\r\n        :return: 运行的结果\r\n        \"\"\"\r\n        ...\r\n\r\n    def run_async_js(self, script: str, *args, as_expr: bool = False) -> None:\r\n        \"\"\"以异步方式对本元素执行javascript代码\r\n        :param script: js文本，文本中用this表示本元素\r\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\r\n        :param as_expr: 是否作为表达式运行，为True时args无效\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def ele(self,\r\n            locator: Union[Tuple[str, str], str],\r\n            index: int = 1,\r\n            timeout: float = None) -> ChromiumElement:\r\n        \"\"\"返回当前元素下级符合条件的一个元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个元素，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 查找元素超时时间（秒），默认与元素所在页面等待时间一致\r\n        :return: ChromiumElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def eles(self,\r\n             locator: Union[Tuple[str, str], str],\r\n             timeout: float = None) -> ChromiumElementsList:\r\n        \"\"\"返回当前元素下级所有符合条件的子元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 查找元素超时时间（秒），默认与元素所在页面等待时间一致\r\n        :return: ChromiumElement对象或属性、文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_ele(self,\r\n              locator: Union[Tuple[str, str], str] = None,\r\n              index: int = 1,\r\n              timeout: float = None) -> SessionElement:\r\n        \"\"\"查找一个符合条件的元素，以SessionElement形式返回\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 查找元素超时时间（秒），默认与元素所在页面等待时间一致\r\n        :return: SessionElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_eles(self,\r\n               locator: Union[Tuple[str, str], str] = None,\r\n               timeout: float = None) -> SessionElementsList:\r\n        \"\"\"查找所有符合条件的元素，以SessionElement列表形式返回\r\n        :param locator: 定位符\r\n        :param timeout: 查找元素超时时间（秒），默认与元素所在页面等待时间一致\r\n        :return: SessionElement或属性、文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def _find_elements(self,\r\n                       locator: Union[Tuple[str, str], str],\r\n                       timeout: float,\r\n                       index: Optional[int] = 1,\r\n                       relative: bool = False,\r\n                       raise_err: bool = False) -> Union[ChromiumElement, ChromiumFrame, ChromiumElementsList]:\r\n        \"\"\"返回当前元素下级符合条件的子元素、属性或节点文本，默认返回第一个\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 查找元素超时时间（秒）\r\n        :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\r\n        :param relative: MixTab用的表示是否相对定位的参数\r\n        :param raise_err: 找不到元素是是否抛出异常，为None时根据全局设置\r\n        :return: ChromiumElement对象或文本、属性或其组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def style(self, style: str, pseudo_ele: str = '') -> str:\r\n        \"\"\"返回元素样式属性值，可获取伪元素属性值\r\n        :param style: 样式属性名称\r\n        :param pseudo_ele: 伪元素名称（如有）\r\n        :return: 样式属性的值\r\n        \"\"\"\r\n        ...\r\n\r\n    def src(self, timeout: float = None, base64_to_bytes: bool = True) -> Union[bytes, str, None]:\r\n        \"\"\"返回元素src资源，base64的可转为bytes返回，其它返回str\r\n        :param timeout: 等待资源加载的超时时间（秒）\r\n        :param base64_to_bytes: 为True时，如果是base64数据，转换为bytes格式\r\n        :return: 资源内容\r\n        \"\"\"\r\n        ...\r\n\r\n    def save(self,\r\n             path: [str, bool] = None,\r\n             name: str = None,\r\n             timeout: float = None,\r\n             rename: bool = True) -> str:\r\n        \"\"\"保存图片或其它有src属性的元素的资源\r\n        :param path: 文件保存路径，为None时保存到当前文件夹\r\n        :param name: 文件名称，为None时从资源url获取\r\n        :param timeout: 等待资源加载的超时时间（秒）\r\n        :param rename: 遇到重名文件时是否自动重命名\r\n        :return: 返回保存路径\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_screenshot(self,\r\n                       path: [str, Path] = None,\r\n                       name: str = None,\r\n                       as_bytes: PIC_TYPE = None,\r\n                       as_base64: PIC_TYPE = None,\r\n                       scroll_to_center: bool = True) -> Union[str, bytes]:\r\n        \"\"\"对当前元素截图，可保存到文件，或以字节方式返回\r\n        :param path: 文件保存路径\r\n        :param name: 完整文件名，后缀可选 'jpg','jpeg','png','webp'\r\n        :param as_bytes: 是否以字节形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数和as_base64参数无效\r\n        :param as_base64: 是否以base64字符串形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数无效\r\n        :param scroll_to_center: 截图前是否滚动到视口中央\r\n        :return: 图片完整路径或字节文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def input(self, vals: Any, clear: bool = False, by_js: bool = False) -> ChromiumElement:\r\n        \"\"\"输入文本或组合键，也可用于输入文件路径到input元素（路径间用\\n间隔）\r\n        :param vals: 文本值或按键组合\r\n        :param clear: 输入前是否清空文本框\r\n        :param by_js: 是否用js方式输入，不能输入组合键\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def clear(self, by_js: bool = False) -> ChromiumElement:\r\n        \"\"\"清空元素文本\r\n        :param by_js: 是否用js方式清空，为False则用全选+del模拟输入删除\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _input_focus(self) -> None:\r\n        \"\"\"输入前使元素获取焦点\"\"\"\r\n        ...\r\n\r\n    def focus(self) -> ChromiumElement:\r\n        \"\"\"使元素获取焦点\"\"\"\r\n        ...\r\n\r\n    def hover(self, offset_x: int = None, offset_y: int = None) -> ChromiumElement:\r\n        \"\"\"鼠标悬停，可接受偏移量，偏移量相对于元素左上角坐标。不传入offset_x和offset_y值时悬停在元素中点\r\n        :param offset_x: 相对元素左上角坐标的x轴偏移量\r\n        :param offset_y: 相对元素左上角坐标的y轴偏移量\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def drag(self, offset_x: int = 0, offset_y: int = 0, duration: float = 0.5) -> ChromiumElement:\r\n        \"\"\"拖拽当前元素到相对位置\r\n        :param offset_x: x变化值\r\n        :param offset_y: y变化值\r\n        :param duration: 拖动用时，传入0即瞬间到达\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def drag_to(self,\r\n                ele_or_loc: Union[Tuple[int, int], str, ChromiumElement],\r\n                duration: float = 0.5) -> ChromiumElement:\r\n        \"\"\"拖拽当前元素，目标为另一个元素或坐标元组(x, y)\r\n        :param ele_or_loc: 另一个元素或坐标元组，坐标为元素中点的坐标\r\n        :param duration: 拖动用时，传入0即瞬间到达\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _get_obj_id(self, node_id: int = None, backend_id: int = None) -> str: ...\r\n\r\n    def _get_node_id(self, obj_id: str = None, backend_id: int = None) -> int: ...\r\n\r\n    def _get_backend_id(self, node_id: int) -> int: ...\r\n\r\n    def _refresh_id(self) -> None:\r\n        \"\"\"根据backend id刷新其它id\"\"\"\r\n        ...\r\n\r\n    def _get_ele_path(self, xpath: bool = True) -> str:\r\n        \"\"\"返获取绝对的css路径或xpath路径\"\"\"\r\n        ...\r\n\r\n    def _set_file_input(self, files: Union[str, list, tuple]) -> None:\r\n        \"\"\"对上传控件写入路径\r\n        :param files: 文件路径列表或字符串，字符串时多个文件用回车分隔\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass ShadowRoot(BaseElement):\r\n    owner: ChromiumBase = ...\r\n    tab: Union[ChromiumPage, ChromiumTab] = ...\r\n    _obj_id: str = ...\r\n    _node_id: int = ...\r\n    _backend_id: int = ...\r\n    parent_ele: ChromiumElement = ...\r\n    _states: Optional[ShadowRootStates] = ...\r\n\r\n    def __init__(self, parent_ele: ChromiumElement, obj_id: str = None, backend_id: int = None):\r\n        \"\"\"\r\n        :param parent_ele: shadow root 所在父元素\r\n        :param obj_id: js中的object id\r\n        :param backend_id: cdp中的backend id\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self,\r\n                 locator: Union[Tuple[str, str], str],\r\n                 index: int = 1,\r\n                 timeout: float = None) -> ChromiumElement:\r\n        \"\"\"在内部查找元素\r\n        例：ele2 = ele1('@id=ele_id')\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 超时时间（秒）\r\n        :return: 元素对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def __repr__(self) -> str: ...\r\n\r\n    def __eq__(self, other: ShadowRoot) -> bool: ...\r\n\r\n    @property\r\n    def tag(self) -> str:\r\n        \"\"\"返回元素标签名\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def html(self) -> str:\r\n        \"\"\"返回outerHTML文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def inner_html(self) -> str:\r\n        \"\"\"返回内部的html文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def states(self) -> ShadowRootStates:\r\n        \"\"\"返回用于获取元素状态的对象\"\"\"\r\n        ...\r\n\r\n    def run_js(self,\r\n               script: str,\r\n               *args,\r\n               as_expr: bool = False,\r\n               timeout: float = None) -> Any:\r\n        \"\"\"运行javascript代码\r\n        :param script: js文本\r\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\r\n        :param as_expr: 是否作为表达式运行，为True时args无效\r\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script设置\r\n        :return: 运行的结果\r\n        \"\"\"\r\n        ...\r\n\r\n    def _run_js(self,\r\n                script: str,\r\n                *args,\r\n                as_expr: bool = False,\r\n                timeout: float = None) -> Any:\r\n        \"\"\"运行javascript代码\r\n        :param script: js文本\r\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\r\n        :param as_expr: 是否作为表达式运行，为True时args无效\r\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script设置\r\n        :return: 运行的结果\r\n        \"\"\"\r\n        ...\r\n\r\n    def run_async_js(self,\r\n                     script: str,\r\n                     *args,\r\n                     as_expr: bool = False,\r\n                     timeout: float = None) -> None:\r\n        \"\"\"以异步方式执行js代码\r\n        :param script: js文本\r\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\r\n        :param as_expr: 是否作为表达式运行，为True时args无效\r\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script设置\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def parent(self,\r\n               level_or_loc: Union[str, int] = 1,\r\n               index: int = 1,\r\n               timeout: float = 0) -> ChromiumElement:\r\n        \"\"\"返回上面某一级父元素，可指定层数或用查询语法定位\r\n        :param level_or_loc: 第几级父元素，或定位符\r\n        :param index: 当level_or_loc传入定位符，使用此参数选择第几个结果\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: ChromiumElement对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def child(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              index: int = 1, timeout: float = None) -> ChromiumElement:\r\n        \"\"\"返回直接子元素元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 第几个查询结果，1开始\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: 直接子元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def next(self,\r\n             locator: Union[Tuple[str, str], str] = '',\r\n             index: int = 1, timeout: float = None) -> ChromiumElement:\r\n        \"\"\"返回当前元素后面一个符合条件的同级元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 第几个查询结果，1开始\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: ChromiumElement对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def before(self,\r\n               locator: Union[Tuple[str, str], str] = '',\r\n               index: int = 1, timeout: float = None) -> ChromiumElement:\r\n        \"\"\"返回文档中当前元素前面符合条件的一个元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: 本元素前面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def after(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              index: int = 1, timeout: float = None) -> ChromiumElement:\r\n        \"\"\"返回文档中此当前元素后面符合条件的一个元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 后面第几个查询结果，1开始\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: 本元素后面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def children(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None) -> List[ChromiumElement]:\r\n        \"\"\"返回当前元素符合条件的直接子元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: 直接子元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def nexts(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None) -> List[ChromiumElement]:\r\n        \"\"\"返回当前元素后面符合条件的同级元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: ChromiumElement对象组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def befores(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None) -> List[ChromiumElement]:\r\n        \"\"\"返回文档中当前元素前面符合条件的元素或节点组成的列表，可用查询语法筛选\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: 本元素前面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def afters(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None) -> List[ChromiumElement]:\r\n        \"\"\"返回文档中当前元素后面符合条件的元素或节点组成的列表，可用查询语法筛选\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: 本元素后面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def ele(self,\r\n            locator: Union[Tuple[str, str], str],\r\n            index: int = 1,\r\n            timeout: float = None) -> ChromiumElement:\r\n        \"\"\"返回当前元素下级符合条件的一个元素\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个元素，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 查找元素超时时间（秒），默认与元素所在页面等待时间一致\r\n        :return: ChromiumElement对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def eles(self,\r\n             locator: Union[Tuple[str, str], str],\r\n             timeout: float = None) -> ChromiumElementsList:\r\n        \"\"\"返回当前元素下级所有符合条件的子元素\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 查找元素超时时间（秒），默认与元素所在页面等待时间一致\r\n        :return: ChromiumElement对象组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_ele(self,\r\n              locator: Union[Tuple[str, str], str] = None,\r\n              index: int = 1,\r\n              timeout: float = None) -> SessionElement:\r\n        \"\"\"查找一个符合条件的元素以SessionElement形式返回，处理复杂页面时效率很高\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 查找元素超时时间（秒），默认与元素所在页面等待时间一致\r\n        :return: SessionElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_eles(self, locator: Union[Tuple[str, str], str], timeout: float = None) -> SessionElementsList:\r\n        \"\"\"查找所有符合条件的元素以SessionElement列表形式返回，处理复杂页面时效率很高\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 查找元素超时时间（秒），默认与元素所在页面等待时间一致\r\n        :return: SessionElement对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _find_elements(self,\r\n                       locator: Union[Tuple[str, str], str],\r\n                       timeout: float,\r\n                       index: Optional[int] = 1,\r\n                       relative: bool = False,\r\n                       raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, str, ChromiumElementsList]:\r\n        \"\"\"返回当前元素下级符合条件的子元素、属性或节点文本，默认返回第一个\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 查找元素超时时间（秒）\r\n        :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\r\n        :param relative: MixTab用的表示是否相对定位的参数\r\n        :param raise_err: 找不到元素是是否抛出异常，为None时根据全局设置\r\n        :return: ChromiumElement对象或其组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def _get_node_id(self, obj_id: str) -> int: ...\r\n\r\n    def _get_obj_id(self, back_id: int) -> str: ...\r\n\r\n    def _get_backend_id(self, node_id: int) -> int: ...\r\n\r\n\r\ndef find_in_chromium_ele(ele: ChromiumElement,\r\n                         locator: Union[str, Tuple[str, str]],\r\n                         index: Optional[int] = 1,\r\n                         timeout: float = None,\r\n                         relative: bool = True) -> Union[ChromiumElement, List[ChromiumElement]]:\r\n    \"\"\"在chromium元素中查找\r\n    :param ele: ChromiumElement对象\r\n    :param locator: 元素定位元组\r\n    :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\r\n    :param timeout: 查找元素超时时间（秒）\r\n    :param relative: MixTab用于标记是否相对定位使用\r\n    :return: 返回ChromiumElement元素或它们组成的列表\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef find_by_xpath(ele: ChromiumElement,\r\n                  xpath: str,\r\n                  index: Optional[int],\r\n                  timeout: float,\r\n                  relative: bool = True) -> Union[ChromiumElement, List[ChromiumElement]]:\r\n    \"\"\"执行用xpath在元素中查找元素\r\n    :param ele: 在此元素中查找\r\n    :param xpath: 查找语句\r\n    :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\r\n    :param timeout: 超时时间（秒）\r\n    :param relative: 是否相对定位\r\n    :return: ChromiumElement或其组成的列表\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef find_by_css(ele: ChromiumElement,\r\n                selector: str,\r\n                index: Optional[int],\r\n                timeout: float) -> Union[ChromiumElement, List[ChromiumElement],]:\r\n    \"\"\"执行用css selector在元素中查找元素\r\n    :param ele: 在此元素中查找\r\n    :param selector: 查找语句\r\n    :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\r\n    :param timeout: 超时时间（秒）\r\n    :return: ChromiumElement或其组成的列表\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef make_chromium_eles(page: Union[ChromiumBase, ChromiumPage, WebPage, ChromiumTab, ChromiumFrame],\r\n                       _ids: Union[tuple, list, str, int],\r\n                       index: Optional[int] = 1,\r\n                       is_obj_id: bool = True,\r\n                       ele_only: bool = False\r\n                       ) -> Union[ChromiumElement, ChromiumFrame, ChromiumElementsList]:\r\n    \"\"\"根据node id或object id生成相应元素对象\r\n    :param page: ChromiumPage对象\r\n    :param _ids: 元素的id列表\r\n    :param index: 获取第几个，为None返回全部\r\n    :param is_obj_id: 传入的id是obj id还是node id\r\n    :param ele_only: 是否只返回ele，在页面查找元素时生效\r\n    :return: 浏览器元素对象或它们组成的列表，生成失败返回False\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef make_js_for_find_ele_by_xpath(xpath: str, type_txt: str, node_txt: str) -> str:\r\n    \"\"\"生成用xpath在元素中查找元素的js文本\r\n    :param xpath: xpath文本\r\n    :param type_txt: 查找类型\r\n    :param node_txt: 节点类型\r\n    :return: js文本\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef run_js(page_or_ele: Union[ChromiumBase, ChromiumElement, ShadowRoot],\r\n           script: str,\r\n           as_expr: bool,\r\n           timeout: float,\r\n           args: tuple = ...) -> Any:\r\n    \"\"\"运行javascript代码\r\n    :param page_or_ele: 页面对象或元素对象\r\n    :param script: js文本\r\n    :param as_expr: 是否作为表达式运行，为True时args无效\r\n    :param timeout: 超时时间（秒）\r\n    :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\r\n    :return: js执行结果\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef parse_js_result(page: ChromiumBase,\r\n                    ele: ChromiumElement,\r\n                    result: dict,\r\n                    end_time: float):\r\n    \"\"\"解析js返回的结果\"\"\"\r\n    ...\r\n\r\n\r\ndef convert_argument(arg: Any) -> dict:\r\n    \"\"\"把参数转换成js能够接收的形式\"\"\"\r\n    ...\r\n\r\n\r\nclass Pseudo(object):\r\n    _ele: ChromiumElement = ...\r\n\r\n    def __init__(self, ele: ChromiumElement):\r\n        \"\"\"\r\n        :param ele: ChromiumElement\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def before(self) -> str:\r\n        \"\"\"返回当前元素的::before伪元素文本内容\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def after(self) -> str:\r\n        \"\"\"返回当前元素的::after伪元素文本内容\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_elements/none_element.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom .._functions.settings import Settings\r\nfrom ..errors import ElementNotFoundError\r\n\r\n\r\nclass NoneElement(object):\r\n    def __init__(self, page=None, method=None, args=None):\r\n        if method and Settings.raise_when_ele_not_found:  # 无传入method时不自动抛出，由调用者处理\r\n            raise ElementNotFoundError(METHOD=method, ARGS=args)\r\n\r\n        if page:\r\n            self._none_ele_value = page._none_ele_value\r\n            self._none_ele_return_value = page._none_ele_return_value\r\n        else:\r\n            self._none_ele_value = None\r\n            self._none_ele_return_value = False\r\n        self.method = method\r\n        self.args = {} if args is None else args\r\n\r\n    def __call__(self, *args, **kwargs):\r\n        if not self._none_ele_return_value:\r\n            raise ElementNotFoundError(METHOD=self.method, ARGS=self.args)\r\n        else:\r\n            return self\r\n\r\n    def __repr__(self):\r\n        return f'<NoneElement method={self.method}, {\", \".join([f\"{k}={v}\" for k, v in self.args.items()])}>'\r\n\r\n    def __getattr__(self, item):\r\n        if not self._none_ele_return_value:\r\n            raise ElementNotFoundError(METHOD=self.method, ARGS=self.args)\r\n        elif item in ('ele', 's_ele', 'parent', 'child', 'next', 'prev', 'before', 'east', 'north', 'south', 'west',\r\n                      'offset', 'over', 'after', 'get_frame', 'shadow_root', 'sr'):\r\n            return self\r\n        else:\r\n            if item in ('size', 'link', 'css_path', 'xpath', 'comments', 'texts', 'tag', 'html', 'inner_html',\r\n                        'attrs', 'text', 'raw_text', 'value', 'attr', 'style', 'src', 'property'):\r\n                return self._none_ele_value\r\n            else:\r\n                raise ElementNotFoundError(METHOD=self.method, ARGS=self.args)\r\n\r\n    def __eq__(self, other):\r\n        return other is None\r\n\r\n    def __bool__(self):\r\n        return False\r\n"
  },
  {
    "path": "DrissionPage/_elements/none_element.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom typing import Any, Optional\n\nfrom .._base.base import BasePage\n\n\nclass NoneElement(object):\n    _none_ele_value: Any = ...\n    _none_ele_return_value: Any = ...\n    method: Optional[str] = ...\n    args: Optional[dict] = ...\n\n    def __init__(self,\n                 page: BasePage = None,\n                 method: str = None,\n                 args: dict = None):\n        \"\"\"\n        :param page: 元素所在页面\n        :param method: 查找元素的方法\n        :param args: 查找元素的参数\n        \"\"\"\n        ...\n\n    def __call__(self, *args, **kwargs) -> NoneElement: ...\n\n    def __repr__(self) -> str: ...\n\n    def __getattr__(self, item: str) -> str: ...\n\n    def __eq__(self, other: Any) -> bool: ...\n\n    def __bool__(self) -> bool: ...\n"
  },
  {
    "path": "DrissionPage/_elements/session_element.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom html import unescape\r\nfrom re import match, sub, DOTALL, search\r\n\r\nfrom lxml.etree import tostring\r\nfrom lxml.html import HtmlElement, fromstring\r\n\r\nfrom .none_element import NoneElement\r\nfrom .._base.base import DrissionElement, BasePage, BaseElement\r\nfrom .._functions.elements import SessionElementsList\r\nfrom .._functions.locator import get_loc\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._functions.web import get_ele_txt, make_absolute_link\r\nfrom ..errors import LocatorError\r\n\r\n\r\nclass SessionElement(DrissionElement):\r\n\r\n    def __init__(self, ele, owner=None):\r\n        \"\"\"初始化对象\r\n        :param ele: 被包装的HtmlElement元素\r\n        :param owner: 元素所在页面对象，如果是从 html 文本生成的元素，则为 None\r\n        \"\"\"\r\n        super().__init__(owner)\r\n        self._inner_ele = ele\r\n        self._type = 'SessionElement'\r\n\r\n    def __repr__(self):\r\n        attrs = [f\"{k}='{v}'\" for k, v in self.attrs.items()]\r\n        return f'<SessionElement {self.tag} {\" \".join(attrs)}>'\r\n\r\n    def __call__(self, locator, index=1, timeout=None):\r\n        return self.ele(locator, index=index)\r\n\r\n    def __eq__(self, other):\r\n        return self.xpath == getattr(other, 'xpath', None)\r\n\r\n    @property\r\n    def inner_ele(self):\r\n        return self._inner_ele\r\n\r\n    @property\r\n    def tag(self):\r\n        return self._inner_ele.tag\r\n\r\n    @property\r\n    def html(self):\r\n        html = tostring(self._inner_ele, method=\"html\").decode()\r\n        return unescape(html[:html.rfind('>') + 1])  # tostring()会把跟紧元素的文本节点也带上，因此要去掉\r\n\r\n    @property\r\n    def inner_html(self):\r\n        r = match(r'<.*?>(.*)</.*?>', self.html, flags=DOTALL)\r\n        return '' if not r else r.group(1)\r\n\r\n    @property\r\n    def attrs(self):\r\n        r = {}\r\n        for attr, val in self.inner_ele.items():\r\n            r[attr] = val if attr.lower in ('href', 'src') else self.attr(attr)\r\n        return r\r\n\r\n    @property\r\n    def text(self):\r\n        return get_ele_txt(self)\r\n\r\n    @property\r\n    def raw_text(self):\r\n        return str(self._inner_ele.text_content())\r\n\r\n    def parent(self, level_or_loc=1, index=1, timeout: float = None):\r\n        return super().parent(level_or_loc, index)\r\n\r\n    def child(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().child(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def prev(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().prev(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def next(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().next(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def before(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().before(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def after(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return super().after(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def children(self, locator='', timeout=0, ele_only=True):\r\n        return SessionElementsList(self.owner, super().children(locator, timeout, ele_only=ele_only))\r\n\r\n    def prevs(self, locator='', timeout=None, ele_only=True):\r\n        return SessionElementsList(self.owner, super().prevs(locator, timeout, ele_only=ele_only))\r\n\r\n    def nexts(self, locator='', timeout=None, ele_only=True):\r\n        return SessionElementsList(self.owner, super().nexts(locator, timeout, ele_only=ele_only))\r\n\r\n    def befores(self, locator='', timeout=None, ele_only=True):\r\n        return SessionElementsList(self.owner, super().befores(locator, timeout, ele_only=ele_only))\r\n\r\n    def afters(self, locator='', timeout=None, ele_only=True):\r\n        return SessionElementsList(self.owner, super().afters(locator, timeout, ele_only=ele_only))\r\n\r\n    def attr(self, name):\r\n        if name == 'href':\r\n            link = self.inner_ele.get('href')\r\n            if not link or link.lower().startswith(('javascript:', 'mailto:')):\r\n                return link\r\n            else:\r\n                return make_absolute_link(link, self.owner.url) if self.owner else link\r\n\r\n        elif name == 'src':\r\n            return make_absolute_link(self.inner_ele.get('src'),\r\n                                      self.owner.url) if self.owner else self.inner_ele.get('src')\r\n\r\n        elif name == 'text':\r\n            return self.text\r\n\r\n        elif name == 'innerText':\r\n            return self.raw_text\r\n\r\n        elif name in ('html', 'outerHTML'):\r\n            return self.html\r\n\r\n        elif name == 'innerHTML':\r\n            return self.inner_html\r\n\r\n        else:\r\n            return self.inner_ele.get(name.lower())\r\n\r\n    def ele(self, locator, index=1, timeout=None):\r\n        return self._ele(locator, index=index, method='ele()')\r\n\r\n    def eles(self, locator, timeout=None):\r\n        return self._ele(locator, index=None)\r\n\r\n    def s_ele(self, locator=None, index=1):\r\n        return self._ele(locator, index=index, method='s_ele()')\r\n\r\n    def s_eles(self, locator):\r\n        return self._ele(locator, index=None)\r\n\r\n    def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):\r\n        return make_session_ele(self, locator, index=index)\r\n\r\n    def _get_ele_path(self, xpath=True):\r\n        if xpath:\r\n            return self._inner_ele.getroottree().getpath(self._inner_ele)\r\n\r\n        path_str = ''\r\n        ele = self\r\n        while ele:\r\n            id_ = ele.attr('id')\r\n            if id_:\r\n                path_str = f'>{ele.tag}#{id_}{path_str}'\r\n            else:\r\n                path_str = f'>{ele.tag}:nth-child({len(ele.eles(\"xpath:./preceding-sibling::*\")) + 1}){path_str}'\r\n            ele = ele.parent()\r\n\r\n        return path_str[1:]\r\n\r\n\r\ndef make_session_ele(html_or_ele, loc=None, index=1, method=None):\r\n    # ---------------处理定位符---------------\r\n    if not loc:\r\n        if isinstance(html_or_ele, SessionElement):\r\n            return html_or_ele\r\n        loc = ('xpath', '.')\r\n\r\n    elif isinstance(loc, (str, tuple)):\r\n        loc = get_loc(loc)\r\n\r\n    else:\r\n        raise LocatorError(ALLOW_VAL=_S._lang.LOC_FORMAT, CURR_VAL=loc)\r\n\r\n    # ---------------根据传入对象类型获取页面对象和lxml元素对象---------------\r\n    the_type = getattr(html_or_ele, '_type', None)\r\n    # 直接传入html文本\r\n    if isinstance(html_or_ele, str):\r\n        page = None\r\n        html_or_ele = fromstring(html_or_ele)\r\n\r\n    # SessionElement\r\n    elif the_type == 'SessionElement':\r\n        page = html_or_ele.owner\r\n\r\n        loc_str = loc[1]\r\n        if loc[0] == 'xpath' and loc[1].lstrip().startswith('/'):\r\n            loc_str = f'.{loc[1]}'\r\n            html_or_ele = html_or_ele.inner_ele\r\n\r\n        # 若css以>开头，表示找元素的直接子元素，要用page以绝对路径才能找到\r\n        elif loc[0] == 'css selector' and loc[1].lstrip().startswith('>'):\r\n            loc_str = f'{html_or_ele.css_path}{loc[1]}'\r\n            if html_or_ele.owner:\r\n                html_or_ele = fromstring(html_or_ele.owner.html)\r\n            else:  # 接收html文本，无page的情况\r\n                html_or_ele = fromstring(html_or_ele('xpath:/ancestor::*').html)\r\n\r\n        else:\r\n            html_or_ele = html_or_ele.inner_ele\r\n\r\n        loc = loc[0], loc_str\r\n\r\n    elif the_type == 'ChromiumElement':\r\n        loc_str = loc[1]\r\n        if loc[0] == 'xpath' and loc[1].lstrip().startswith('/'):\r\n            loc_str = f'.{loc[1]}'\r\n        elif loc[0] == 'css selector' and loc[1].lstrip().startswith('>'):\r\n            loc_str = f'{html_or_ele.css_path}{loc[1]}'\r\n        loc = loc[0], loc_str\r\n\r\n        # 获取整个页面html再定位到当前元素，以实现查找上级元素\r\n        page = html_or_ele.owner\r\n        xpath = html_or_ele.xpath\r\n        # ChromiumElement，兼容传入的元素在iframe内的情况\r\n        if html_or_ele._doc_id is None:\r\n            doc = html_or_ele._run_js('return this.ownerDocument;')\r\n            html_or_ele._doc_id = doc['objectId'] if doc else False\r\n\r\n        if html_or_ele._doc_id:\r\n            html = html_or_ele.owner._run_cdp('DOM.getOuterHTML', objectId=html_or_ele._doc_id)['outerHTML']\r\n        else:\r\n            html = html_or_ele.owner.html\r\n        html_or_ele = fromstring(html)\r\n        html_or_ele = html_or_ele.xpath(xpath)[0]\r\n\r\n    elif the_type == 'ChromiumFrame':\r\n        page = html_or_ele\r\n        html_or_ele = fromstring(html_or_ele.inner_html)\r\n\r\n    # 各种页面对象\r\n    elif isinstance(html_or_ele, BasePage):\r\n        page = html_or_ele\r\n        html = html_or_ele.html\r\n        if html.startswith('<?xml '):\r\n            html = sub(r'^<\\?xml.*?>', '', html)\r\n        html_or_ele = fromstring(html)\r\n\r\n    # ShadowRoot\r\n    elif isinstance(html_or_ele, BaseElement):\r\n        page = html_or_ele.owner\r\n        html = html_or_ele.html\r\n        r = search(r'^<shadow_root>[ \\n]*?<html>[ \\n]*?(.*?)[ \\n]*?</html>[ \\n]*?</shadow_root>$', html)\r\n        if r:\r\n            html = r.group(1)\r\n        html_or_ele = fromstring(html)\r\n\r\n    else:\r\n        raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'html_or_ele',\r\n                                       ALLOW_TYPE=_S._lang.HTML_ELE_TYPE, CURR_VAL=html_or_ele))\r\n\r\n    # ---------------执行查找-----------------\r\n    try:\r\n        if loc[0] == 'xpath':  # 用lxml内置方法获取lxml的元素对象列表\r\n            eles = html_or_ele.xpath(loc[1])\r\n        else:  # 用css selector获取元素对象列表\r\n            eles = html_or_ele.cssselect(loc[1])\r\n\r\n        if not isinstance(eles, list):  # 结果不是列表，如数字\r\n            return eles\r\n\r\n        # 把lxml元素对象包装成SessionElement对象并按需要返回一个或全部\r\n        if index is None:\r\n            r = SessionElementsList(owner=page)\r\n            for e in eles:\r\n                if e != '\\n':\r\n                    r.append(SessionElement(e, page) if isinstance(e, HtmlElement) else e)\r\n            return r\r\n\r\n        else:\r\n            eles_count = len(eles)\r\n            if eles_count == 0 or abs(index) > eles_count:\r\n                return NoneElement(page, method=method, args={'locator': loc, 'index': index})\r\n            if index < 0:\r\n                index = eles_count + index + 1\r\n\r\n            ele = eles[index - 1]\r\n            if isinstance(ele, HtmlElement):\r\n                return SessionElement(ele, page)\r\n            elif isinstance(ele, str):\r\n                return ele\r\n            else:\r\n                return NoneElement(page, method=method, args={'locator': loc, 'index': index})\r\n\r\n    except Exception as e:\r\n        if 'Invalid expression' in str(e):\r\n            raise LocatorError(_S._lang.INVALID_XPATH_, loc)\r\n        elif 'Expected selector' in str(e):\r\n            raise LocatorError(_S._lang.INVALID_CSS_, loc)\r\n\r\n        raise e\r\n"
  },
  {
    "path": "DrissionPage/_elements/session_element.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Union, List, Tuple, Optional\r\n\r\nfrom lxml.html import HtmlElement\r\n\r\nfrom .._base.base import DrissionElement, BaseElement\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._functions.elements import SessionElementsList\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.chromium_frame import ChromiumFrame\r\nfrom .._pages.session_page import SessionPage\r\n\r\n\r\nclass SessionElement(DrissionElement):\r\n    \"\"\"静态元素对象\"\"\"\r\n\r\n    def __init__(self, ele: HtmlElement, owner: Union[SessionPage, None] = None):\r\n        self._inner_ele: HtmlElement = ...\r\n        self.owner: SessionPage = ...\r\n        self.page: SessionPage = ...\r\n\r\n    def __call__(self,\r\n                 locator: Union[Tuple[str, str], str],\r\n                 index: int = 1,\r\n                 timeout: float = None) -> SessionElement:\r\n        \"\"\"在内部查找元素\r\n        例：ele2 = ele1('@id=ele_id')\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 第几个元素，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 不起实际作用\r\n        :return: SessionElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def __repr__(self) -> str: ...\r\n\r\n    def __eq__(self, other: SessionElement) -> bool: ...\r\n\r\n    @property\r\n    def inner_ele(self) -> HtmlElement: ...\r\n\r\n    @property\r\n    def tag(self) -> str:\r\n        \"\"\"返回元素类型\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def html(self) -> str:\r\n        \"\"\"返回outerHTML文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def inner_html(self) -> str:\r\n        \"\"\"返回元素innerHTML文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def attrs(self) -> dict:\r\n        \"\"\"返回元素所有属性及值\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def text(self) -> str:\r\n        \"\"\"返回元素内文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def raw_text(self) -> str:\r\n        \"\"\"返回未格式化处理的元素内文本\"\"\"\r\n        ...\r\n\r\n    def parent(self,\r\n               level_or_loc: Union[tuple, str, int] = 1,\r\n               index: int = 1,\r\n               timeout: float = None) -> SessionElement:\r\n        \"\"\"返回上面某一级父元素，可指定层数或用查询语法定位\r\n        :param level_or_loc: 第几级父元素，或定位符\r\n        :param index: 当level_or_loc传入定位符，使用此参数选择第几个结果\r\n        :param timeout: 此参数不起实际作用\r\n        :return: 上级元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def child(self,\r\n              locator: Union[Tuple[str, str], str, int] = '',\r\n              index: int = 1,\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[SessionElement, str]:\r\n        \"\"\"返回当前元素的一个符合条件的直接子元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 第几个查询结果，1开始\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 直接子元素或节点文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def prev(self,\r\n             locator: Union[Tuple[str, str], str, int] = '',\r\n             index: int = 1,\r\n             timeout: float = None,\r\n             ele_only: bool = True) -> Union[SessionElement, str]:\r\n        \"\"\"返回当前元素前面一个符合条件的同级元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 同级元素\r\n        \"\"\"\r\n        ...\r\n\r\n    def next(self,\r\n             locator: Union[Tuple[str, str], str, int] = '',\r\n             index: int = 1,\r\n             timeout: float = None,\r\n             ele_only: bool = True) -> Union[SessionElement, str]:\r\n        \"\"\"返回当前元素后面一个符合条件的同级元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 第几个查询结果，1开始\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 同级元素\r\n        \"\"\"\r\n        ...\r\n\r\n    def before(self,\r\n               locator: Union[Tuple[str, str], str, int] = '',\r\n               index: int = 1,\r\n               timeout: float = None,\r\n               ele_only: bool = True) -> Union[SessionElement, str]:\r\n        \"\"\"返回文档中当前元素前面符合条件的一个元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def after(self,\r\n              locator: Union[Tuple[str, str], str, int] = '',\r\n              index: int = 1,\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[SessionElement, str]:\r\n        \"\"\"返回文档中此当前元素后面符合条件的一个元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 第几个查询结果，1开始\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素后面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def children(self,\r\n                 locator: Union[Tuple[str, str], str] = '',\r\n                 timeout: float = None,\r\n                 ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]:\r\n        \"\"\"返回当前元素符合条件的直接子元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 直接子元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def prevs(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]:\r\n        \"\"\"返回当前元素前面符合条件的同级元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 同级元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def nexts(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]:\r\n        \"\"\"返回当前元素后面符合条件的同级元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 同级元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def befores(self,\r\n                locator: Union[Tuple[str, str], str] = '',\r\n                timeout: float = None,\r\n                ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]:\r\n        \"\"\"返回文档中当前元素前面符合条件的元素或节点组成的列表，可用查询语法筛选\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def afters(self,\r\n               locator: Union[Tuple[str, str], str] = '',\r\n               timeout: float = None,\r\n               ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]:\r\n        \"\"\"返回文档中当前元素后面符合条件的元素或节点组成的列表，可用查询语法筛选\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 此参数不起实际作用\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素后面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def attr(self, name: str) -> Optional[str]:\r\n        \"\"\"返回attribute属性值\r\n        :param name: 属性名\r\n        :return: 属性值文本，没有该属性返回None\r\n        \"\"\"\r\n        ...\r\n\r\n    def ele(self,\r\n            locator: Union[Tuple[str, str], str],\r\n            index: int = 1,\r\n            timeout: float = None) -> SessionElement:\r\n        \"\"\"返回当前元素下级符合条件的一个元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 第几个元素，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 不起实际作用\r\n        :return: SessionElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def eles(self,\r\n             locator: Union[Tuple[str, str], str],\r\n             timeout: float = None) -> SessionElementsList:\r\n        \"\"\"返回当前元素下级所有符合条件的子元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 不起实际作用\r\n        :return: SessionElement对象或属性、文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_ele(self,\r\n              locator: Union[Tuple[str, str], str] = None,\r\n              index: int = 1) -> SessionElement:\r\n        \"\"\"返回当前元素下级符合条件的一个元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :return: SessionElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList:\r\n        \"\"\"返回当前元素下级所有符合条件的子元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :return: SessionElement对象或属性、文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def _find_elements(self,\r\n                       locator: Union[Tuple[str, str], str],\r\n                       timeout: float,\r\n                       index: Optional[int] = 1,\r\n                       relative: bool = False,\r\n                       raise_err: bool = None) -> Union[SessionElement, SessionElementsList]:\r\n        \"\"\"返回当前元素下级符合条件的子元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 不起实际作用，用于和父类对应\r\n        :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\r\n        :param relative: MixTab用的表示是否相对定位的参数\r\n        :param raise_err: 找不到元素是是否抛出异常，为None时根据全局设置\r\n        :return: SessionElement对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _get_ele_path(self, xpath: bool = True) -> str:\r\n        \"\"\"获取css路径或xpath路径\r\n        :param xpath: 用xpath还是css\r\n        :return: css路径或xpath路径\r\n        \"\"\"\r\n        ...\r\n\r\n\r\ndef make_session_ele(html_or_ele: Union[str, SessionElement, SessionPage, ChromiumElement, BaseElement, ChromiumFrame,\r\nChromiumBase],\r\n                     loc: Union[str, Tuple[str, str]] = None,\r\n                     index: Optional[int] = 1,\r\n                     method: Optional[str] = None) -> Union[SessionElement, SessionElementsList, str, float]:\r\n    \"\"\"从接收到的对象或html文本中查找元素，返回SessionElement对象\r\n    如要直接从html生成SessionElement而不在下级查找，loc输入None即可\r\n    :param html_or_ele: html文本、BaseParser对象\r\n    :param loc: 定位元组或字符串，为None时不在下级查找，返回根元素\r\n    :param index: 获取第几个元素，从1开始，可传入负数获取倒数第几个，None获取所有\r\n    :param method: 调用此方法的方法\r\n    :return: 返回SessionElement元素或列表，或属性文本\r\n    \"\"\"\r\n    ...\r\n"
  },
  {
    "path": "DrissionPage/_functions/browser.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom json import load, dump, JSONDecodeError\r\nfrom os import environ\r\nfrom pathlib import Path\r\nfrom shutil import rmtree\r\nfrom subprocess import Popen, DEVNULL\r\nfrom tempfile import gettempdir\r\nfrom time import perf_counter, sleep\r\n\r\nfrom requests import Session\r\n\r\nfrom .settings import Settings as _S\r\nfrom .tools import port_is_using\r\nfrom .._configs.options_manage import OptionsManager\r\nfrom ..errors import BrowserConnectError\r\n\r\n\r\ndef connect_browser(option):\r\n    address = option.address.replace('localhost', '127.0.0.1')\r\n    if address.startswith('http'):\r\n        address = address.lstrip('htps:/')\r\n    browser_path = option.browser_path\r\n\r\n    ip, port = address.split(':')\r\n    using = port_is_using(ip, port)\r\n    if ip != '127.0.0.1' or using or option.is_existing_only:\r\n        if test_connect(ip, port):\r\n            return True\r\n        elif ip != '127.0.0.1':\r\n            raise BrowserConnectError(ADDRESS=address)\r\n        elif using:\r\n            raise BrowserConnectError(_S._lang.BROWSER_CONNECT_ERR1_, port, port, ADDRESS=address)\r\n        else:  # option.is_existing_only\r\n            raise BrowserConnectError(_S._lang.BROWSER_CONNECT_ERR2, ADDRESS=address)\r\n\r\n    # ----------创建浏览器进程----------\r\n    args, user_path = get_launch_args(option)\r\n    if option._new_env:\r\n        rmtree(user_path, ignore_errors=True)\r\n    set_prefs(option)\r\n    set_flags(option)\r\n    try:\r\n        _run_browser(port, browser_path, args)\r\n\r\n    except FileNotFoundError:  # 传入的路径找不到，主动在ini文件、注册表、系统变量中找\r\n        browser_path = get_chrome_path(option.ini_path)\r\n        if not browser_path:\r\n            raise FileNotFoundError(_S._lang.join(_S._lang.BROWSER_EXE_NOT_FOUND))\r\n        _run_browser(port, browser_path, args)\r\n\r\n    if not test_connect(ip, port):\r\n        raise BrowserConnectError(ADDRESS=address, TIP=_S._lang.BROWSER_CONNECT_ERR_INFO)\r\n    return False\r\n\r\n\r\ndef get_launch_args(opt):\r\n    # ----------处理arguments-----------\r\n    result = set()\r\n    user_path = False\r\n    for i in opt.arguments:\r\n        if i.startswith(('--disable-extensions-except=', '--load-extension=', '--remote-debugging-port=')):\r\n            continue\r\n        elif i.startswith('--user-data-dir') and not opt.system_user_path:\r\n            user_path = f'--user-data-dir={Path(i[16:]).absolute()}'\r\n            result.add(user_path)\r\n            continue\r\n        elif i.startswith('--user-agent='):\r\n            opt._ua_set = True\r\n        result.add(i)\r\n\r\n    if not user_path and not opt.system_user_path:\r\n        port = opt.address.split(':')[-1] if opt.address else '0'\r\n        p = Path(opt.tmp_path) if opt.tmp_path else Path(gettempdir()) / 'DrissionPage'\r\n        path = p / 'userData' / port\r\n        path.mkdir(parents=True, exist_ok=True)\r\n        user_path = path.absolute()\r\n        opt.set_user_data_path(user_path)\r\n        result.add(f'--user-data-dir={user_path}')\r\n\r\n    result = list(result)\r\n\r\n    # ----------处理插件extensions-------------\r\n    ext = [Path(e) for e in opt.extensions]\r\n    exts = []\r\n    if ext:\r\n        for e in ext:\r\n            if e.is_file():\r\n                raise ValueError(_S._lang.join(_S._lang.PLUGIN_NEED_FOLDER, str(e.absolute())))\r\n            elif not e.exists():\r\n                raise FileNotFoundError(_S._lang.join(_S._lang.EXT_NOT_FOUND, PATH=e))\r\n            else:\r\n                exts.append(str(e.absolute()))\r\n        ext = ','.join(set(exts))\r\n        result.append(f'--disable-extensions-except={ext}')\r\n        result.append(f'--load-extension={ext}')\r\n\r\n    return result, user_path\r\n\r\n\r\ndef set_prefs(opt):\r\n    if not opt.user_data_path or (not opt.preferences and not opt._prefs_to_del):\r\n        return\r\n    prefs = opt.preferences\r\n    del_list = opt._prefs_to_del\r\n\r\n    user = 'Default'\r\n    for arg in opt.arguments:\r\n        if arg.startswith('--profile-directory'):\r\n            user = arg.split('=')[-1].strip()\r\n            break\r\n\r\n    prefs_file = Path(opt.user_data_path) / user / 'Preferences'\r\n\r\n    if not prefs_file.exists():\r\n        prefs_file.parent.mkdir(parents=True, exist_ok=True)\r\n        with open(prefs_file, 'w') as f:\r\n            f.write('{}')\r\n\r\n    with open(prefs_file, \"r\", encoding='utf-8') as f:\r\n        try:\r\n            prefs_dict = load(f)\r\n        except JSONDecodeError:\r\n            prefs_dict = {}\r\n\r\n        for pref in prefs:\r\n            value = prefs[pref]\r\n            pref = pref.split('.')\r\n            _make_leave_in_dict(prefs_dict, pref, 0, len(pref))\r\n            _set_value_to_dict(prefs_dict, pref, value)\r\n\r\n        for pref in del_list:\r\n            _remove_arg_from_dict(prefs_dict, pref)\r\n\r\n    with open(prefs_file, 'w', encoding='utf-8') as f:\r\n        dump(prefs_dict, f)\r\n\r\n\r\ndef set_flags(opt):\r\n    if not opt.user_data_path or (not opt.clear_file_flags and not opt.flags):\r\n        return\r\n\r\n    state_file = Path(opt.user_data_path) / 'Local State'\r\n\r\n    if not state_file.exists():\r\n        state_file.parent.mkdir(parents=True, exist_ok=True)\r\n        with open(state_file, 'w') as f:\r\n            f.write('{}')\r\n\r\n    with open(state_file, \"r\", encoding='utf-8') as f:\r\n        try:\r\n            states_dict = load(f)\r\n        except JSONDecodeError:\r\n            states_dict = {}\r\n        states_dict.setdefault('browser', {}).setdefault('enabled_labs_experiments', [])\r\n        flags_list = [] if opt.clear_file_flags else states_dict['browser']['enabled_labs_experiments']\r\n        flags_dict = {}\r\n        for i in flags_list:\r\n            f = str(i).split('@', 1)\r\n            flags_dict[f[0]] = None if len(f) == 1 else f[1]\r\n\r\n        for k, i in opt.flags.items():\r\n            flags_dict[k] = i\r\n\r\n        states_dict['browser']['enabled_labs_experiments'] = [f'{k}@{i}' if i else k for k, i in flags_dict.items()]\r\n\r\n    with open(state_file, 'w', encoding='utf-8') as f:\r\n        dump(states_dict, f)\r\n\r\n\r\ndef test_connect(ip, port):\r\n    end_time = perf_counter() + _S.browser_connect_timeout\r\n    s = Session()\r\n    s.trust_env = False\r\n    s.keep_alive = False\r\n    while perf_counter() < end_time:\r\n        try:\r\n            r = s.get(f'http://{ip}:{port}/json', timeout=10, headers={'Connection': 'close'})\r\n            for tab in r.json():\r\n                if tab['type'] in ('page', 'webview'):\r\n                    r.close()\r\n                    s.close()\r\n                    return True\r\n            r.close()\r\n        except Exception:\r\n            sleep(.2)\r\n\r\n    s.close()\r\n    return False\r\n\r\n\r\ndef _run_browser(port, path: str, args) -> Popen:\r\n    \"\"\"创建浏览器进程\r\n    :param port: 端口号\r\n    :param path: 浏览器路径\r\n    :param args: 启动参数\r\n    :return: 进程对象\r\n    \"\"\"\r\n    p = Path(path)\r\n    p = str(p / 'chrome') if p.is_dir() else str(path)\r\n    arguments = [p, f'--remote-debugging-port={port}']\r\n    arguments.extend(args)\r\n    try:\r\n        return Popen(arguments, shell=False, stdout=DEVNULL, stderr=DEVNULL)\r\n    except FileNotFoundError:\r\n        raise FileNotFoundError(_S._lang.join(_S._lang.BROWSER_NOT_FOUND))\r\n\r\n\r\ndef _make_leave_in_dict(target_dict: dict, src: list, num: int, end: int) -> None:\r\n    \"\"\"把prefs中a.b.c形式的属性转为a['b']['c']形式\r\n    :param target_dict: 要处理的字典\r\n    :param src: 属性层级列表[a, b, c]\r\n    :param num: 当前处理第几个\r\n    :param end: src长度\r\n    :return: None\r\n    \"\"\"\r\n    if num == end:\r\n        return\r\n    if src[num] not in target_dict:\r\n        target_dict[src[num]] = {}\r\n    num += 1\r\n    _make_leave_in_dict(target_dict[src[num - 1]], src, num, end)\r\n\r\n\r\ndef _set_value_to_dict(target_dict: dict, src: list, value) -> None:\r\n    \"\"\"把a.b.c形式的属性的值赋值到a['b']['c']形式的字典中\r\n    :param target_dict: 要处理的字典\r\n    :param src: 属性层级列表[a, b, c]\r\n    :param value: 属性值\r\n    :return: None\r\n    \"\"\"\r\n    src = \"']['\".join(src)\r\n    src = f\"target_dict['{src}']=value\"\r\n    exec(src)\r\n\r\n\r\ndef _remove_arg_from_dict(target_dict: dict, arg: str) -> None:\r\n    \"\"\"把a.b.c形式的属性从字典中删除\r\n    :param target_dict: 要处理的字典\r\n    :param arg: 层级属性，形式'a.b.c'\r\n    :return: None\r\n    \"\"\"\r\n    args = arg.split('.')\r\n    args = [f\"['{i}']\" for i in args]\r\n    src = ''.join(args)\r\n    src = f\"target_dict{src}\"\r\n    try:\r\n        exec(src)\r\n        src = ''.join(args[:-1])\r\n        src = f\"target_dict{src}.pop({args[-1][1:-1]})\"\r\n        exec(src)\r\n    except:\r\n        pass\r\n\r\n\r\ndef get_chrome_path(ini_path):\r\n    # -----------从ini文件中获取--------------\r\n    if ini_path and Path(ini_path).exists():\r\n        path = OptionsManager(ini_path).chromium_options.get('browser_path', None)\r\n        if path and Path(path).is_file():\r\n            return str(path)\r\n\r\n    # -----------使用which获取-----------\r\n    from shutil import which\r\n    path = (which('chrome') or which('chromium') or which('google-chrome') or which('google-chrome-stable')\r\n            or which('google-chrome-unstable') or which('google-chrome-beta'))\r\n    if path:\r\n        return path\r\n\r\n    # -----------从MAC和Linux默认路径获取-----------\r\n    from platform import system\r\n    sys = system().lower()\r\n    if sys in ('macos', 'darwin'):\r\n        p = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'\r\n        return p if Path(p).exists() else None\r\n\r\n    elif sys == 'linux':\r\n        paths = ('/usr/bin/google-chrome', '/opt/google/chrome/google-chrome',\r\n                 '/user/lib/chromium-browser/chromium-browser')\r\n        for p in paths:\r\n            if Path(p).exists():\r\n                return p\r\n        return None\r\n\r\n    elif sys != 'windows':\r\n        return None\r\n\r\n    # -----------从注册表中获取--------------\r\n    from winreg import OpenKey, EnumValue, CloseKey, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ\r\n    txt = r'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe'\r\n    try:\r\n        key = OpenKey(HKEY_CURRENT_USER, txt, reserved=0, access=KEY_READ)\r\n        k = EnumValue(key, 0)\r\n        CloseKey(key)\r\n        if k[1]:\r\n            return k[1]\r\n\r\n    except (FileNotFoundError, OSError):\r\n        try:\r\n            key = OpenKey(HKEY_LOCAL_MACHINE, txt, reserved=0, access=KEY_READ)\r\n            k = EnumValue(key, 0)\r\n            CloseKey(key)\r\n            if k[1]:\r\n                return k[1]\r\n\r\n        except (FileNotFoundError, OSError):\r\n            pass\r\n\r\n    # -----------从系统变量中获取--------------\r\n    for path in environ.get('PATH', '').split(';'):\r\n        path = Path(path) / 'chrome.exe'\r\n        try:\r\n            if path.exists():\r\n                return str(path)\r\n        except OSError:\r\n            pass\r\n"
  },
  {
    "path": "DrissionPage/_functions/browser.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Union\r\n\r\nfrom .._configs.chromium_options import ChromiumOptions\r\n\r\n\r\ndef connect_browser(option: ChromiumOptions) -> bool:\r\n    \"\"\"连接或启动浏览器\r\n    :param option: ChromiumOptions对象\r\n    :return: 返回是否接管的浏览器\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef get_launch_args(opt: ChromiumOptions) -> list:\r\n    \"\"\"从ChromiumOptions获取命令行启动参数\r\n    :param opt: ChromiumOptions\r\n    :return: 启动参数列表\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef set_prefs(opt: ChromiumOptions) -> None:\r\n    \"\"\"处理启动配置中的prefs项，目前只能对已存在文件夹配置\r\n    :param opt: ChromiumOptions\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef set_flags(opt: ChromiumOptions) -> None:\r\n    \"\"\"处理启动配置中的flags项\r\n    :param opt: ChromiumOptions\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef test_connect(ip: str, port: Union[int, str], timeout: float = 30) -> bool:\r\n    \"\"\"测试浏览器是否可用\r\n    :param ip: 浏览器ip\r\n    :param port: 浏览器端口\r\n    :param timeout: 超时时间（秒）\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef get_chrome_path(ini_path: str) -> Union[str, None]:\r\n    \"\"\"从ini文件或系统变量中获取chrome可执行文件的路径\r\n    :param ini_path: ini文件路径\r\n    :return: 文件路径\r\n    \"\"\"\r\n    ...\r\n"
  },
  {
    "path": "DrissionPage/_functions/by.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\n\r\n\r\nclass By:\r\n    ID = 'id'\r\n    XPATH = 'xpath'\r\n    LINK_TEXT = 'link text'\r\n    PARTIAL_LINK_TEXT = 'partial link text'\r\n    NAME = 'name'\r\n    TAG_NAME = 'tag name'\r\n    CLASS_NAME = 'class name'\r\n    CSS_SELECTOR = 'css selector'\r\n"
  },
  {
    "path": "DrissionPage/_functions/cli.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom click import command, option\r\n\r\nfrom .._functions.tools import configs_to_here as ch\r\nfrom .._configs.chromium_options import ChromiumOptions\r\nfrom .._pages.chromium_page import ChromiumPage\r\n\r\n\r\n@command()\r\n@option(\"-p\", \"--set-browser-path\", help=\"设置浏览器路径\")\r\n@option(\"-u\", \"--set-user-path\", help=\"设置用户数据路径\")\r\n@option(\"-c\", \"--configs-to-here\", is_flag=True, help=\"复制默认配置文件到当前路径\")\r\n@option(\"-l\", \"--launch-browser\", default=-1, help=\"启动浏览器，传入端口号，0表示用配置文件中的值\")\r\ndef main(set_browser_path, set_user_path, configs_to_here, launch_browser):\r\n    if set_browser_path:\r\n        set_paths(browser_path=set_browser_path)\r\n\r\n    if set_user_path:\r\n        set_paths(user_data_path=set_user_path)\r\n\r\n    if configs_to_here:\r\n        ch()\r\n\r\n    if launch_browser >= 0:\r\n        port = f'127.0.0.1:{launch_browser}' if launch_browser else None\r\n        ChromiumPage(port)\r\n\r\n\r\ndef set_paths(browser_path=None, user_data_path=None):\r\n    \"\"\"快捷的路径设置函数\r\n    :param browser_path: 浏览器可执行文件路径\r\n    :param user_data_path: 用户数据路径\r\n    :return: None\r\n    \"\"\"\r\n    co = ChromiumOptions()\r\n\r\n    if browser_path is not None:\r\n        co.set_browser_path(browser_path)\r\n\r\n    if user_data_path is not None:\r\n        co.set_user_data_path(user_data_path)\r\n\r\n    co.save()\r\n\r\n\r\nif __name__ == '__main__':\r\n    main()\r\n"
  },
  {
    "path": "DrissionPage/_functions/cookies.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom datetime import datetime\nfrom http.cookiejar import Cookie, CookieJar\n\nfrom tldextract import TLDExtract\n\nfrom .settings import Settings as _S\n\n\ndef cookie_to_dict(cookie):\n    if isinstance(cookie, Cookie):\n        cookie_dict = cookie.__dict__.copy()\n        cookie_dict.pop('rfc2109', None)\n        cookie_dict.pop('_rest', None)\n        return cookie_dict\n\n    elif isinstance(cookie, dict):\n        cookie_dict = cookie\n\n    elif isinstance(cookie, str):\n        cookie_dict = {}\n        for attr in cookie.strip().rstrip(';,').split(',' if ',' in cookie else ';'):\n            attr_val = attr.strip().split('=', 1)\n            if attr_val[0] in ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry', 'name', 'value'):\n                cookie_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else ''\n            else:\n                cookie_dict['name'] = attr_val[0]\n                cookie_dict['value'] = attr_val[1] if len(attr_val) == 2 else ''\n\n        return cookie_dict\n\n    else:\n        raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'cookie',\n                                       ALLOW_TYPE='Cookie, str, dict', CURR_TYPE=type(cookie)))\n\n    return cookie_dict\n\n\ndef cookies_to_tuple(cookies):\n    if isinstance(cookies, (list, tuple, CookieJar)):\n        cookies = tuple(cookie_to_dict(cookie) for cookie in cookies)\n\n    elif isinstance(cookies, str):\n        c_dict = {}\n        cookies = cookies.rstrip('; ')\n        cookies = cookies.split(';')\n\n        for attr in cookies:\n            attr_val = attr.strip().split('=', 1)\n            c_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else True\n        cookies = _dict_cookies_to_tuple(c_dict)\n\n    elif isinstance(cookies, dict):\n        cookies = _dict_cookies_to_tuple(cookies)\n\n    elif isinstance(cookies, Cookie):\n        cookies = (cookie_to_dict(cookies),)\n\n    else:\n        raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'cookies',\n                                       ALLOW_TYPE='Cookie, CookieJar, list, tuple, str, dict', CURR_TYPE=type(cookies)))\n\n    return cookies\n\n\ndef set_session_cookies(session, cookies):\n    for cookie in cookies_to_tuple(cookies):\n        if cookie['value'] is None:\n            cookie['value'] = ''\n\n        kwargs = {x: cookie[x] for x in cookie\n                  if x.lower() in ('version', 'port', 'domain', 'path', 'secure',\n                                   'expires', 'discard', 'comment', 'comment_url', 'rest')}\n\n        if 'expiry' in cookie:\n            kwargs['expires'] = cookie['expiry']\n\n        session.cookies.set(cookie['name'], cookie['value'], **kwargs)\n\n\ndef set_browser_cookies(browser, cookies):\n    c = []\n    for cookie in cookies_to_tuple(cookies):\n        if 'domain' not in cookie and 'url' not in cookie:\n            raise ValueError(_S._lang.join(_S._lang.NEED_DOMAIN2, cookie=cookie))\n        c.append(format_cookie(cookie))\n    browser._run_cdp('Storage.setCookies', cookies=c)\n\n\ndef set_tab_cookies(page, cookies):\n    for cookie in cookies_to_tuple(cookies):\n        cookie = format_cookie(cookie)\n\n        if cookie['name'].startswith('__Host-'):\n            if not page.url.startswith('http'):\n                cookie['name'] = cookie['name'].replace('__Host-', '__Secure-', 1)\n            else:\n                cookie['url'] = page.url\n            page._run_cdp_loaded('Network.setCookie', **cookie)\n            continue  # 不用设置域名，可退出\n\n        if cookie.get('domain', None):\n            try:\n                page._run_cdp_loaded('Network.setCookie', **cookie)\n                if not is_cookie_in_driver(page, cookie):\n                    page.browser.set.cookies(cookie)\n                continue\n            except Exception:\n                pass\n\n        url = page._browser_url\n        if not url.startswith('http'):\n            raise ValueError(_S._lang.join(_S._lang.DOMAIN_NOT_SET, cookie=cookie))\n        ex_url = TLDExtract(suffix_list_urls=[\"https://publicsuffix.org/list/public_suffix_list.dat\",\n                                              f\"file:///{_S.suffixes_list}\"]).extract_str(url)\n        d_list = ex_url.subdomain.split('.')\n        d_list.append(f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain)\n\n        tmp = [d_list[0]]\n        if len(d_list) > 1:\n            for i in d_list[1:]:\n                tmp.append('.')\n                tmp.append(i)\n\n        for i in range(len(tmp)):\n            cookie['domain'] = ''.join(tmp[i:])\n            page._run_cdp_loaded('Network.setCookie', **cookie)\n            if is_cookie_in_driver(page, cookie):\n                break\n\n\ndef is_cookie_in_driver(page, cookie):\n    if 'domain' in cookie:\n        for c in page.cookies(all_domains=True):\n            if cookie['name'] == c['name'] and cookie['value'] == c['value'] and cookie['domain'] == c.get('domain',\n                                                                                                           None):\n                return True\n    else:\n        for c in page.cookies(all_domains=True):\n            if cookie['name'] == c['name'] and cookie['value'] == c['value']:\n                return True\n    return False\n\n\ndef format_cookie(cookie):\n    if 'expiry' in cookie:\n        cookie['expires'] = int(cookie['expiry'])\n        cookie.pop('expiry')\n\n    if 'expires' in cookie:\n        if not cookie['expires']:\n            cookie.pop('expires')\n\n        elif isinstance(cookie['expires'], str):\n            if cookie['expires'].isdigit():\n                cookie['expires'] = int(cookie['expires'])\n\n            elif cookie['expires'].replace('.', '').isdigit():\n                cookie['expires'] = float(cookie['expires'])\n\n            else:\n                try:\n                    cookie['expires'] = datetime.strptime(cookie['expires'], '%a, %d %b %Y %H:%M:%S GMT').timestamp()\n                except ValueError:\n                    cookie.pop('expires')\n\n    if cookie['value'] is None:\n        cookie['value'] = ''\n    elif not isinstance(cookie['value'], str):\n        cookie['value'] = str(cookie['value'])\n\n    if cookie['name'].startswith('__Host-'):\n        cookie['path'] = '/'\n        cookie['secure'] = True\n\n    elif cookie['name'].startswith('__Secure-'):\n        cookie['secure'] = True\n\n    if 'sameSite' in cookie:\n        sameSite = cookie['sameSite']\n        if sameSite in (None, False) or sameSite not in ('None', 'Lax', 'Strict', 'no_restriction'):\n            cookie.pop('sameSite')\n\n    if 'priority' in cookie:\n        priority = cookie['priority']\n        if priority in (None, False):\n            cookie.pop('priority')\n        elif priority not in ('Low', 'Medium', 'High'):\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'priority', ALLOW_VAL='\"Low\", \"Medium\", \"High\"',\n                                           CURR_VAL=priority, cookie=cookie))\n\n    if 'sourceScheme' in cookie:\n        sourceScheme = cookie['sourceScheme']\n        if sourceScheme in (None, False):\n            cookie.pop('sourceScheme')\n        elif sourceScheme not in ('Unset', 'NonSecure', 'Secure'):\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'sourceScheme',\n                                           ALLOW_VAL='\"Unset\", \"NonSecure\", \"Secure\"', CURR_VAL=sourceScheme,\n                                           cookie=cookie))\n\n    return cookie\n\n\nclass CookiesList(list):\n    def as_dict(self):\n        return {c['name']: c['value'] for c in self}\n\n    def as_str(self):\n        return '; '.join([f'{c[\"name\"]}={c[\"value\"]}' for c in self])\n\n    def as_json(self):\n        from json import dumps\n        return dumps(self)\n\n\ndef _dict_cookies_to_tuple(cookies: dict):\n    \"\"\"把dict形式的cookies转换为tuple形式\n    :param cookies: 单个或多个cookies，单个时包含 'name' 和 'value'\n    :return: 多个dict格式cookies组成的列表\n    \"\"\"\n    if 'name' in cookies and 'value' in cookies:  # 单个cookie\n        return (cookies,)\n    keys = ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry')\n    template = {k: v for k, v in cookies.items() if k in keys}\n    return tuple(dict(**{'name': k, 'value': v}, **template) for k, v in cookies.items() if k not in keys)\n"
  },
  {
    "path": "DrissionPage/_functions/cookies.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom http.cookiejar import Cookie\nfrom typing import Union\n\nfrom requests import Session\nfrom requests.cookies import RequestsCookieJar\n\nfrom .._base.chromium import Chromium\nfrom .._pages.chromium_base import ChromiumBase\n\n\ndef cookie_to_dict(cookie: Union[Cookie, str, dict]) -> dict:\n    \"\"\"把Cookie对象转为dict格式\n    :param cookie: Cookie对象、字符串或字典\n    :return: cookie字典\n    \"\"\"\n    ...\n\n\ndef cookies_to_tuple(cookies: Union[RequestsCookieJar, list, tuple, str, dict, Cookie]) -> tuple:\n    \"\"\"把cookies转为tuple格式\n    :param cookies: cookies信息，可为CookieJar, list, tuple, str, dict\n    :return: 返回tuple形式的cookies\n    \"\"\"\n    ...\n\n\ndef set_session_cookies(session: Session,\n                        cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None:\n    \"\"\"设置Session对象的cookies\n    :param session: Session对象\n    :param cookies: cookies信息\n    :return: None\n    \"\"\"\n    ...\n\n\ndef set_browser_cookies(browser: Chromium,\n                        cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None:\n    \"\"\"设置cookies值\n    :param browser: 页面对象\n    :param cookies: cookies信息\n    :return: None\n    \"\"\"\n    ...\n\n\ndef set_tab_cookies(page: ChromiumBase,\n                    cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None:\n    \"\"\"设置cookies值\n    :param page: 页面对象\n    :param cookies: cookies信息\n    :return: None\n    \"\"\"\n    ...\n\n\ndef is_cookie_in_driver(page: ChromiumBase, cookie: dict) -> bool:\n    \"\"\"查询cookie是否在浏览器内\n    :param page: BasePage对象\n    :param cookie: dict格式cookie\n    :return: bool\n    \"\"\"\n    ...\n\n\ndef format_cookie(cookie: dict) -> dict:\n    \"\"\"设置cookie为可用格式\n    :param cookie: dict格式cookie\n    :return: 格式化后的cookie字典\n    \"\"\"\n    ...\n\n\nclass CookiesList(list):\n    def as_dict(self) -> dict:\n        \"\"\"以dict格式返回，只包含name和value字段\"\"\"\n        ...\n\n    def as_str(self) -> str:\n        \"\"\"以str格式返回，只包含name和value字段\"\"\"\n        ...\n\n    def as_json(self) -> str:\n        \"\"\"以json格式返回\"\"\"\n        ...\n\n    def __next__(self) -> dict: ...\n"
  },
  {
    "path": "DrissionPage/_functions/elements.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom time import perf_counter, sleep\n\nfrom .locator import is_str_loc, is_selenium_loc\nfrom .._functions.settings import Settings as _S\nfrom .._elements.none_element import NoneElement\nfrom ..errors import LocatorError\n\n\nclass SessionElementsList(list):\n    def __init__(self, owner=None, *args):\n        super().__init__(*args)\n        self._owner = owner\n\n    def __getitem__(self, item):\n        cls = type(self)\n        if isinstance(item, slice):\n            return cls(self._owner, super().__getitem__(item))\n        elif isinstance(item, int):\n            return super().__getitem__(item)\n        else:\n            raise ValueError(_S._lang.join(_S._lang.INDEX_FORMAT, CURR_VAL=item))\n\n    @property\n    def get(self):\n        return Getter(self)\n\n    @property\n    def filter(self):\n        return SessionFilter(self)\n\n    @property\n    def filter_one(self):\n        return SessionFilterOne(self)\n\n\nclass ChromiumElementsList(SessionElementsList):\n\n    @property\n    def filter(self):\n        return ChromiumFilter(self)\n\n    @property\n    def filter_one(self):\n        return ChromiumFilterOne(self)\n\n    def search(self, displayed=None, checked=None, selected=None, enabled=None, clickable=None,\n               have_rect=None, have_text=None, tag=None):\n        return _search(self, displayed=displayed, checked=checked, selected=selected, enabled=enabled,\n                       clickable=clickable, have_rect=have_rect, have_text=have_text, tag=tag)\n\n    def search_one(self, index=1, displayed=None, checked=None, selected=None, enabled=None, clickable=None,\n                   have_rect=None, have_text=None, tag=None):\n        return _search_one(self, index=index, displayed=displayed, checked=checked, selected=selected,\n                           enabled=enabled, clickable=clickable, have_rect=have_rect, have_text=have_text, tag=tag)\n\n\nclass SessionFilterOne(object):\n    def __init__(self, _list):\n        self._list = _list\n        self._index = 1\n\n    def __call__(self, index=1):\n        self._index = index\n        return self\n\n    def tag(self, name, equal=True):\n        num = 0\n        name = name.lower()\n        if equal:\n            for i in self._list:\n                if not isinstance(i, str) and i.tag == name:\n                    num += 1\n                    if self._index == num:\n                        return i\n        else:\n            for i in self._list:\n                if not isinstance(i, str) and i.tag != name:\n                    num += 1\n                    if self._index == num:\n                        return i\n        return NoneElement(self._list._owner, 'tag()', args={'name': name, 'equal': equal, 'index': self._index})\n\n    def attr(self, name, value, equal=True):\n        return self._get_attr(name, value, 'attr', equal=equal)\n\n    def text(self, text, fuzzy=True, contain=True):\n        num = 0\n        if contain:\n            for i in self._list:\n                t = i if isinstance(i, str) else i.raw_text\n                if (fuzzy and text in t) or (not fuzzy and text == t):\n                    num += 1\n                    if self._index == num:\n                        return i\n        else:\n            for i in self._list:\n                t = i if isinstance(i, str) else i.raw_text\n                if (fuzzy and text not in t) or (not fuzzy and text != t):\n                    num += 1\n                    if self._index == num:\n                        return i\n        return NoneElement(self._list._owner, 'text()',\n                           args={'text': text, 'fuzzy': fuzzy, 'contain': contain, 'index': self._index})\n\n    def _get_attr(self, name, value, method, equal=True):\n        num = 0\n        if equal:\n            for i in self._list:\n                if not isinstance(i, str) and getattr(i, method)(name) == value:\n                    num += 1\n                    if self._index == num:\n                        return i\n        else:\n            for i in self._list:\n                if not isinstance(i, str) and getattr(i, method)(name) != value:\n                    num += 1\n                    if self._index == num:\n                        return i\n        return NoneElement(self._list._owner, f'{method}()',\n                           args={'name': name, 'value': value, 'equal': equal, 'index': self._index})\n\n\nclass SessionFilter(SessionFilterOne):\n\n    def __iter__(self):\n        return iter(self._list)\n\n    def __next__(self):\n        return next(self._list)\n\n    def __len__(self):\n        return len(self._list)\n\n    def __getitem__(self, item):\n        return self._list[item]\n\n    @property\n    def get(self):\n        return self._list.get\n\n    def tag(self, name, equal=True):\n        self._list = _tag_all(self._list, SessionElementsList(owner=self._list._owner), name=name, equal=equal)\n        return self\n\n    def text(self, text, fuzzy=True, contain=True):\n        self._list = _text_all(self._list, SessionElementsList(owner=self._list._owner),\n                               text=text, fuzzy=fuzzy, contain=contain)\n        return self\n\n    def _get_attr(self, name, value, method, equal=True):\n        self._list = _attr_all(self._list, SessionElementsList(owner=self._list._owner),\n                               name=name, value=value, method=method, equal=equal)\n        return self\n\n\nclass ChromiumFilterOne(SessionFilterOne):\n\n    def displayed(self, equal=True):\n        return self._any_state('is_displayed', equal=equal)\n\n    def checked(self, equal=True):\n        return self._any_state('is_checked', equal=equal)\n\n    def selected(self, equal=True):\n        return self._any_state('is_selected', equal=equal)\n\n    def enabled(self, equal=True):\n        return self._any_state('is_enabled', equal=equal)\n\n    def clickable(self, equal=True):\n        return self._any_state('is_clickable', equal=equal)\n\n    def have_rect(self, equal=True):\n        return self._any_state('has_rect', equal=equal)\n\n    def style(self, name, value, equal=True):\n        return self._get_attr(name, value, 'style', equal=equal)\n\n    def property(self, name, value, equal=True):\n        return self._get_attr(name, value, 'property', equal=equal)\n\n    def _any_state(self, name, equal=True):\n        num = 0\n        if equal:\n            for i in self._list:\n                if not isinstance(i, str) and getattr(i.states, name):\n                    num += 1\n                    if self._index == num:\n                        return i\n        else:\n            for i in self._list:\n                if not isinstance(i, str) and not getattr(i.states, name):\n                    num += 1\n                    if self._index == num:\n                        return i\n        return NoneElement(self._list._owner, f'{name}()', args={'equal': equal, 'index': self._index})\n\n\nclass ChromiumFilter(ChromiumFilterOne):\n\n    def __iter__(self):\n        return iter(self._list)\n\n    def __next__(self):\n        return next(self._list)\n\n    def __len__(self):\n        return len(self._list)\n\n    def __getitem__(self, item):\n        return self._list[item]\n\n    @property\n    def get(self):\n        return self._list.get\n\n    def search_one(self, index=1, displayed=None, checked=None, selected=None, enabled=None, clickable=None,\n                   have_rect=None, have_text=None, tag=None):\n        return _search_one(self._list, index=index, displayed=displayed, checked=checked, selected=selected,\n                           enabled=enabled, clickable=clickable, have_rect=have_rect, have_text=have_text, tag=tag)\n\n    def search(self, displayed=None, checked=None, selected=None, enabled=None, clickable=None,\n               have_rect=None, have_text=None, tag=None):\n        return _search(self._list, displayed=displayed, checked=checked, selected=selected, enabled=enabled,\n                       clickable=clickable, have_rect=have_rect, have_text=have_text, tag=tag)\n\n    def tag(self, name, equal=True):\n        self._list = _tag_all(self._list, ChromiumElementsList(owner=self._list._owner), name=name, equal=equal)\n        return self\n\n    def text(self, text, fuzzy=True, contain=True):\n        self._list = _text_all(self._list, ChromiumElementsList(owner=self._list._owner),\n                               text=text, fuzzy=fuzzy, contain=contain)\n        return self\n\n    def _get_attr(self, name, value, method, equal=True):\n        self._list = _attr_all(self._list, ChromiumElementsList(owner=self._list._owner),\n                               name=name, value=value, method=method, equal=equal)\n        return self\n\n    def _any_state(self, name, equal=True):\n        r = ChromiumElementsList(owner=self._list._owner)\n        if equal:\n            for i in self._list:\n                if not isinstance(i, str) and getattr(i.states, name):\n                    r.append(i)\n        else:\n            for i in self._list:\n                if not isinstance(i, str) and not getattr(i.states, name):\n                    r.append(i)\n        self._list = r\n        return self\n\n\nclass Getter(object):\n    def __init__(self, _list):\n        self._list = _list\n\n    def links(self):\n        return [e.link for e in self._list if not isinstance(e, str)]\n\n    def texts(self):\n        return [e if isinstance(e, str) else e.text for e in self._list]\n\n    def attrs(self, name):\n        return [e.attr(name) for e in self._list if not isinstance(e, str)]\n\n\ndef get_eles(locators, owner, any_one=False, first_ele=True, timeout=10):\n    if is_selenium_loc(locators):\n        locators = (locators,)\n    res = {loc: None for loc in locators}\n\n    if timeout == 0:\n        for loc in locators:\n            ele = owner._ele(loc, timeout=0, raise_err=False, index=1 if first_ele else None, method='find()')\n            res[loc] = ele\n            if ele and any_one:\n                return res\n        return res\n\n    end_time = perf_counter() + timeout\n    while perf_counter() <= end_time:\n        for loc in locators:\n            if res[loc]:\n                continue\n            ele = owner._ele(loc, timeout=0, raise_err=False, index=1 if first_ele else None, method='find()')\n            res[loc] = ele\n            if ele and any_one:\n                return res\n        if all(res.values()):\n            return res\n        sleep(.05)\n\n    return res\n\n\ndef get_frame(owner, loc_ind_ele, timeout=None):\n    if isinstance(loc_ind_ele, str):\n        if is_str_loc(loc_ind_ele):\n            xpath = loc_ind_ele\n        else:\n            xpath = f'xpath://*[(name()=\"iframe\" or name()=\"frame\") and (@name=\"{loc_ind_ele}\" or @id=\"{loc_ind_ele}\")]'\n        ele = owner._ele(xpath, timeout=timeout)\n        if ele and ele._type != 'ChromiumFrame':\n            raise LocatorError(_S._lang.LOC_NOT_FOR_FRAME, LOCATOR=loc_ind_ele)\n        r = ele\n\n    elif isinstance(loc_ind_ele, tuple):\n        ele = owner._ele(loc_ind_ele, timeout=timeout)\n        if ele and ele._type != 'ChromiumFrame':\n            raise LocatorError(_S._lang.LOC_NOT_FOR_FRAME, LOCATOR=loc_ind_ele)\n        r = ele\n\n    elif isinstance(loc_ind_ele, int):\n        ele = owner._ele('@|tag():iframe@|tag():frame', timeout=timeout, index=loc_ind_ele)\n        if ele and ele._type != 'ChromiumFrame':\n            raise LocatorError(_S._lang.LOC_NOT_FOR_FRAME, LOCATOR=loc_ind_ele)\n        r = ele\n\n    elif getattr(loc_ind_ele, '_type', None) == 'ChromiumFrame':\n        r = loc_ind_ele\n\n    else:\n        raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'loc_ind_ele',\n                                       ALLOW_VAL=_S._lang.FRAME_LOC_FORMAT, CURR_VAL=loc_ind_ele))\n\n    if isinstance(r, NoneElement):\n        r.method = 'get_frame()'\n        r.args = {'loc_ind_ele': loc_ind_ele}\n    return r\n\n\ndef _attr_all(src_list, aim_list, name, value, method, equal=True):\n    if equal:\n        for i in src_list:\n            if not isinstance(i, str) and getattr(i, method)(name) == value:\n                aim_list.append(i)\n    else:\n        for i in src_list:\n            if not isinstance(i, str) and getattr(i, method)(name) != value:\n                aim_list.append(i)\n    return aim_list\n\n\ndef _tag_all(src_list, aim_list, name, equal=True):\n    name = name.lower()\n    if equal:\n        for i in src_list:\n            if not isinstance(i, str) and i.tag == name:\n                aim_list.append(i)\n    else:\n        for i in src_list:\n            if not isinstance(i, str) and i.tag != name:\n                aim_list.append(i)\n    return aim_list\n\n\ndef _text_all(src_list, aim_list, text, fuzzy=True, contain=True):\n    \"\"\"以是否含有指定文本为条件筛选元素\n    :param text: 用于匹配的文本\n    :param fuzzy: 是否模糊匹配\n    :param contain: 是否包含该字符串，False表示不包含\n    :return: 筛选结果\n    \"\"\"\n    if contain:\n        for i in src_list:\n            t = i if isinstance(i, str) else i.raw_text\n            if (fuzzy and text in t) or (not fuzzy and text == t):\n                aim_list.append(i)\n    else:\n        for i in src_list:\n            t = i if isinstance(i, str) else i.raw_text\n            if (fuzzy and text not in t) or (not fuzzy and text != t):\n                aim_list.append(i)\n    return aim_list\n\n\ndef _search(_list, displayed=None, checked=None, selected=None, enabled=None, clickable=None,\n            have_rect=None, have_text=None, tag=None):\n    \"\"\"或关系筛选元素\n    :param displayed: 是否显示，bool，None为忽略该项\n    :param checked: 是否被选中，bool，None为忽略该项\n    :param selected: 是否被选择，bool，None为忽略该项\n    :param enabled: 是否可用，bool，None为忽略该项\n    :param clickable: 是否可点击，bool，None为忽略该项\n    :param have_rect: 是否拥有大小和位置，bool，None为忽略该项\n    :param have_text: 是否含有文本，bool，None为忽略该项\n    :param tag: 元素类型\n    :return: 筛选结果\n    \"\"\"\n    r = ChromiumElementsList(owner=_list._owner)\n    for i in _list:\n        if not isinstance(i, str) and (\n                (displayed is not None and (displayed is True and i.states.is_displayed)\n                 or (displayed is False and not i.states.is_displayed))\n                or (checked is not None and (checked is True and i.states.is_checked)\n                    or (checked is False and not i.states.is_checked))\n                or (selected is not None and (selected is True and i.states.is_selected)\n                    or (selected is False and not i.states.is_selected))\n                or (enabled is not None and (enabled is True and i.states.is_enabled)\n                    or (enabled is False and not i.states.is_enabled))\n                or (clickable is not None and (clickable is True and i.states.is_clickable)\n                    or (clickable is False and not i.states.is_clickable))\n                or (have_rect is not None and (have_rect is True and i.states.has_rect)\n                    or (have_rect is False and not i.states.has_rect))\n                or (have_text is not None and (have_text is True and i.raw_text)\n                    or (have_text is False and not i.raw_text))\n                or (tag is not None and i.tag == tag.lower())):\n            r.append(i)\n    return ChromiumFilter(r)\n\n\ndef _search_one(_list, index=1, displayed=None, checked=None, selected=None, enabled=None, clickable=None,\n                have_rect=None, have_text=None, tag=None):\n    \"\"\"或关系筛选元素，获取一个结果\n    :param index: 元素序号，从1开始\n    :param displayed: 是否显示，bool，None为忽略该项\n    :param checked: 是否被选中，bool，None为忽略该项\n    :param selected: 是否被选择，bool，None为忽略该项\n    :param enabled: 是否可用，bool，None为忽略该项\n    :param clickable: 是否可点击，bool，None为忽略该项\n    :param have_rect: 是否拥有大小和位置，bool，None为忽略该项\n    :param have_text: 是否含有文本，bool，None为忽略该项\n    :param tag: 元素类型\n    :return: 筛选结果\n    \"\"\"\n    num = 0\n    for i in _list:\n        if not isinstance(i, str) and (\n                (displayed is not None and (displayed is True and i.states.is_displayed)\n                 or (displayed is False and not i.states.is_displayed))\n                or (checked is not None and (checked is True and i.states.is_checked)\n                    or (checked is False and not i.states.is_checked))\n                or (selected is not None and (selected is True and i.states.is_selected)\n                    or (selected is False and not i.states.is_selected))\n                or (enabled is not None and (enabled is True and i.states.is_enabled)\n                    or (enabled is False and not i.states.is_enabled))\n                or (clickable is not None and (clickable is True and i.states.is_clickable)\n                    or (clickable is False and not i.states.is_clickable))\n                or (have_rect is not None and (have_rect is True and i.states.has_rect)\n                    or (have_rect is False and not i.states.has_rect))\n                or (have_text is not None and (have_text is True and i.raw_text)\n                    or (have_text is False and not i.raw_text))\n                or (tag is not None and i.tag == tag.lower())):\n            num += 1\n            if num == index:\n                return i\n\n    return NoneElement(_list._owner, method='filter()', args={'displayed': displayed, 'checked': checked,\n                                                              'selected': selected, 'enabled': enabled,\n                                                              'clickable': clickable, 'have_rect': have_rect,\n                                                              'have_text': have_text, 'tag': tag})\n"
  },
  {
    "path": "DrissionPage/_functions/elements.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom typing import Union, List, Optional, Iterable, Dict\n\nfrom .._base.base import BaseParser\nfrom .._elements.chromium_element import ChromiumElement\nfrom .._elements.session_element import SessionElement\nfrom .._pages.chromium_base import ChromiumBase\nfrom .._pages.chromium_frame import ChromiumFrame\nfrom .._pages.session_page import SessionPage\n\n\nclass SessionElementsList(list):\n    _owner: SessionPage = ...\n\n    def __init__(self,\n                 owner: SessionPage = None,\n                 *args):\n        \"\"\"\n        :param owner: 产生元素列表的页面\n        :param args:\n        \"\"\"\n        ...\n\n    def __next__(self) -> SessionElement: ...\n\n    def __getitem__(self, _i) -> Union[SessionElement, SessionElementsList]: ...\n\n    def __iter__(self) -> List[SessionElement]: ...\n\n    @property\n    def get(self) -> Getter:\n        \"\"\"返回用于属性的对象\"\"\"\n        ...\n\n    @property\n    def filter(self) -> SessionFilter:\n        \"\"\"返回用于筛选多个元素的对象\"\"\"\n        ...\n\n    @property\n    def filter_one(self) -> SessionFilterOne:\n        \"\"\"用于筛选单个元素的对象\"\"\"\n        ...\n\n\nclass ChromiumElementsList(SessionElementsList):\n    _owner: ChromiumBase = ...\n\n    def __init__(self,\n                 owner: ChromiumBase = None,\n                 *args):\n        \"\"\"\n        :param owner: 产生元素列表的页面\n        :param args:\n        \"\"\"\n        ...\n\n    def __next__(self) -> ChromiumElement: ...\n\n    def __getitem__(self, _i) -> Union[ChromiumElement, ChromiumElementsList]: ...\n\n    def __iter__(self) -> List[ChromiumElement]: ...\n\n    @property\n    def filter(self) -> ChromiumFilter:\n        \"\"\"返回用于筛选多个元素的对象\"\"\"\n        ...\n\n    @property\n    def filter_one(self) -> ChromiumFilterOne:\n        \"\"\"用于筛选单个元素的对象\"\"\"\n        ...\n\n    def search(self,\n               displayed: Optional[bool] = None,\n               checked: Optional[bool] = None,\n               selected: Optional[bool] = None,\n               enabled: Optional[bool] = None,\n               clickable: Optional[bool] = None,\n               have_rect: Optional[bool] = None,\n               have_text: Optional[bool] = None,\n               tag: str = None) -> ChromiumFilter:\n        \"\"\"或关系筛选元素\n        :param displayed: 是否显示，bool，None为忽略该项\n        :param checked: 是否被选中，bool，None为忽略该项\n        :param selected: 是否被选择，bool，None为忽略该项\n        :param enabled: 是否可用，bool，None为忽略该项\n        :param clickable: 是否可点击，bool，None为忽略该项\n        :param have_rect: 是否拥有大小和位置，bool，None为忽略该项\n        :param have_text: 是否含有文本，bool，None为忽略该项\n        :param tag: 指定的元素类型\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def search_one(self,\n                   index: int = 1,\n                   displayed: Optional[bool] = None,\n                   checked: Optional[bool] = None,\n                   selected: Optional[bool] = None,\n                   enabled: Optional[bool] = None,\n                   clickable: Optional[bool] = None,\n                   have_rect: Optional[bool] = None,\n                   have_text: Optional[bool] = None,\n                   tag: str = None) -> ChromiumElement:\n        \"\"\"或关系筛选元素，获取一个结果\n        :param index: 元素序号，从1开始\n        :param displayed: 是否显示，bool，None为忽略该项\n        :param checked: 是否被选中，bool，None为忽略该项\n        :param selected: 是否被选择，bool，None为忽略该项\n        :param enabled: 是否可用，bool，None为忽略该项\n        :param clickable: 是否可点击，bool，None为忽略该项\n        :param have_rect: 是否拥有大小和位置，bool，None为忽略该项\n        :param have_text: 是否含有文本，bool，None为忽略该项\n        :param tag: 指定的元素类型\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n\nclass SessionFilterOne(object):\n    _list: SessionElementsList = ...\n    _index: int = ...\n\n    def __init__(self, _list: SessionElementsList):\n        \"\"\"\n        :param _list: 元素列表对象\n        \"\"\"\n        ...\n\n    def __call__(self, index: int = 1) -> SessionFilterOne:\n        \"\"\"返回结果中第几个元素\n        :param index: 元素序号，从1开始\n        :return: 对象自身\n        \"\"\"\n        ...\n\n    def tag(self, name: str, equal: bool = True) -> SessionElement:\n        \"\"\"筛选某种元素\n        :param name: 标签页名称\n        :param equal: True表示匹配这种元素，False表示匹配非这种元素\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def attr(self, name: str, value: str, equal: bool = True) -> SessionElement:\n        \"\"\"以是否拥有某个attribute值为条件筛选元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param equal: True表示匹配name值为value值的元素，False表示匹配name值不为value值的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionElement:\n        \"\"\"以是否含有指定文本为条件筛选元素\n        :param text: 用于匹配的文本\n        :param fuzzy: 是否模糊匹配\n        :param contain: 是否包含该字符串，False表示不包含\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def _get_attr(self,\n                  name: str,\n                  value: str,\n                  method: str,\n                  equal: bool = True) -> SessionElement:\n        \"\"\"返回通过某个方法可获得某个值的元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param method: 方法名称\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n\nclass SessionFilter(SessionFilterOne):\n\n    def __iter__(self) -> Iterable[SessionElement]: ...\n\n    def __next__(self) -> SessionElement: ...\n\n    def __len__(self) -> int: ...\n\n    def __getitem__(self, item: int) -> SessionElement: ...\n\n    @property\n    def get(self) -> Getter:\n        \"\"\"返回用于获取元素属性的对象\"\"\"\n        ...\n\n    def tag(self, name: str, equal: bool = True) -> SessionFilter:\n        \"\"\"筛选某种元素\n        :param name: 标签页名称\n        :param equal: True表示匹配这种元素，False表示匹配非这种元素\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def attr(self, name: str, value: str, equal: bool = True) -> SessionFilter:\n        \"\"\"以是否拥有某个attribute值为条件筛选元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param equal: True表示匹配name值为value值的元素，False表示匹配name值不为value值的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionFilter:\n        \"\"\"以是否含有指定文本为条件筛选元素\n        :param text: 用于匹配的文本\n        :param fuzzy: 是否模糊匹配\n        :param contain: 是否包含该字符串，False表示不包含\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def _get_attr(self,\n                  name: str,\n                  value: str,\n                  method: str,\n                  equal: bool = True) -> SessionFilter:\n        \"\"\"返回通过某个方法可获得某个值的元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param method: 方法名称\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n\nclass ChromiumFilterOne(SessionFilterOne):\n    _list: ChromiumElementsList = ...\n\n    def __init__(self, _list: ChromiumElementsList):\n        \"\"\"\n        :param _list: 元素列表对象\n        \"\"\"\n        ...\n\n    def __call__(self, index: int = 1) -> ChromiumFilterOne:\n        \"\"\"返回结果中第几个元素\n        :param index: 元素序号，从1开始\n        :return: 对象自身\n        \"\"\"\n        ...\n\n    def tag(self, name: str, equal: bool = True) -> SessionElement:\n        \"\"\"筛选某种元素\n        :param name: 标签页名称\n        :param equal: True表示匹配这种元素，False表示匹配非这种元素\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def attr(self, name: str, value: str, equal: bool = True) -> ChromiumElement:\n        \"\"\"以是否拥有某个attribute值为条件筛选元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param equal: True表示匹配name值为value值的元素，False表示匹配name值不为value值的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def text(self,\n             text: str,\n             fuzzy: bool = True,\n             contain: bool = True) -> ChromiumElement:\n        \"\"\"以是否含有指定文本为条件筛选元素\n        :param text: 用于匹配的文本\n        :param fuzzy: 是否模糊匹配\n        :param contain: 是否包含该字符串，False表示不包含\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def displayed(self, equal: bool = True) -> ChromiumElement:\n        \"\"\"以是否显示为条件筛选元素\n        :param equal: 是否匹配显示的元素，False匹配不显示的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def checked(self, equal: bool = True) -> ChromiumElement:\n        \"\"\"以是否被选中为条件筛选元素\n        :param equal: 是否匹配被选中的元素，False匹配不被选中的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def selected(self, equal: bool = True) -> ChromiumElement:\n        \"\"\"以是否被选择为条件筛选元素，用于<select>元素项目\n        :param equal: 是否匹配被选择的元素，False匹配不被选择的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def enabled(self, equal: bool = True) -> ChromiumElement:\n        \"\"\"以是否可用为条件筛选元素\n        :param equal: 是否匹配可用的元素，False表示匹配disabled状态的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def clickable(self, equal: bool = True) -> ChromiumElement:\n        \"\"\"以是否可点击为条件筛选元素\n        :param equal: 是否匹配可点击的元素，False表示匹配不是可点击的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def have_rect(self, equal: bool = True) -> ChromiumElement:\n        \"\"\"以是否有大小为条件筛选元素\n        :param equal: 是否匹配有大小的元素，False表示匹配没有大小的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def style(self, name: str, value: str, equal: bool = True) -> ChromiumElement:\n        \"\"\"以是否拥有某个style值为条件筛选元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param equal: True表示匹配name值为value值的元素，False表示匹配name值不为value值的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def property(self,\n                 name: str,\n                 value: str, equal: bool = True) -> ChromiumElement:\n        \"\"\"以是否拥有某个property值为条件筛选元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param equal: True表示匹配name值为value值的元素，False表示匹配name值不为value值的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def _get_attr(self,\n                  name: str,\n                  value: str,\n                  method: str, equal: bool = True) -> ChromiumElement:\n        \"\"\"返回通过某个方法可获得某个值的元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param method: 方法名称\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def _any_state(self, name: str, equal: bool = True) -> ChromiumElement:\n        \"\"\"\n        :param name: 状态名称\n        :param equal: 是否是指定状态，False表示否定状态\n        :return: 选中的列表\n        \"\"\"\n        ...\n\n\nclass ChromiumFilter(ChromiumFilterOne):\n\n    def __iter__(self) -> Iterable[ChromiumElement]: ...\n\n    def __next__(self) -> ChromiumElement: ...\n\n    def __len__(self) -> int: ...\n\n    def __getitem__(self, item: int) -> ChromiumElement: ...\n\n    @property\n    def get(self) -> Getter:\n        \"\"\"返回用于获取元素属性的对象\"\"\"\n        ...\n\n    def tag(self, name: str, equal: bool = True) -> ChromiumFilter:\n        \"\"\"筛选某种元素\n        :param name: 标签页名称\n        :param equal: True表示匹配这种元素，False表示匹配非这种元素\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def attr(self, name: str, value: str, equal: bool = True) -> ChromiumFilter:\n        \"\"\"以是否拥有某个attribute值为条件筛选元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param equal: True表示匹配name值为value值的元素，False表示匹配name值不为value值的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> ChromiumFilter:\n        \"\"\"以是否含有指定文本为条件筛选元素\n        :param text: 用于匹配的文本\n        :param fuzzy: 是否模糊匹配\n        :param contain: 是否包含该字符串，False表示不包含\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def displayed(self, equal: bool = True) -> ChromiumFilter:\n        \"\"\"以是否显示为条件筛选元素\n        :param equal: 是否匹配显示的元素，False匹配不显示的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def checked(self, equal: bool = True) -> ChromiumFilter:\n        \"\"\"以是否被选中为条件筛选元素\n        :param equal: 是否匹配被选中的元素，False匹配不被选中的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def selected(self, equal: bool = True) -> ChromiumFilter:\n        \"\"\"以是否被选择为条件筛选元素，用于<select>元素项目\n        :param equal: 是否匹配被选择的元素，False匹配不被选择的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def enabled(self, equal: bool = True) -> ChromiumFilter:\n        \"\"\"以是否可用为条件筛选元素\n        :param equal: 是否匹配可用的元素，False表示匹配disabled状态的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def clickable(self, equal: bool = True) -> ChromiumFilter:\n        \"\"\"以是否可点击为条件筛选元素\n        :param equal: 是否匹配可点击的元素，False表示匹配不是可点击的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def have_rect(self, equal: bool = True) -> ChromiumFilter:\n        \"\"\"以是否有大小为条件筛选元素\n        :param equal: 是否匹配有大小的元素，False表示匹配没有大小的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def style(self, name: str, value: str, equal: bool = True) -> ChromiumFilter:\n        \"\"\"以是否拥有某个style值为条件筛选元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param equal: True表示匹配name值为value值的元素，False表示匹配name值不为value值的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def property(self,\n                 name: str,\n                 value: str, equal: bool = True) -> ChromiumFilter:\n        \"\"\"以是否拥有某个property值为条件筛选元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param equal: True表示匹配name值为value值的元素，False表示匹配name值不为value值的\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def search_one(self,\n                   index: int = 1,\n                   displayed: Optional[bool] = None,\n                   checked: Optional[bool] = None,\n                   selected: Optional[bool] = None,\n                   enabled: Optional[bool] = None,\n                   clickable: Optional[bool] = None,\n                   have_rect: Optional[bool] = None,\n                   have_text: Optional[bool] = None,\n                   tag: str = None) -> ChromiumElement:\n        \"\"\"或关系筛选元素，获取一个结果\n        :param index: 元素序号，从1开始\n        :param displayed: 是否显示，bool，None为忽略该项\n        :param checked: 是否被选中，bool，None为忽略该项\n        :param selected: 是否被选择，bool，None为忽略该项\n        :param enabled: 是否可用，bool，None为忽略该项\n        :param clickable: 是否可点击，bool，None为忽略该项\n        :param have_rect: 是否拥有大小和位置，bool，None为忽略该项\n        :param have_text: 是否含有文本，bool，None为忽略该项\n        :param tag: 指定的元素类型\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def search(self,\n               displayed: Optional[bool] = None,\n               checked: Optional[bool] = None,\n               selected: Optional[bool] = None,\n               enabled: Optional[bool] = None,\n               clickable: Optional[bool] = None,\n               have_rect: Optional[bool] = None,\n               have_text: Optional[bool] = None,\n               tag: str = None) -> ChromiumFilter:\n        \"\"\"或关系筛选元素\n        :param displayed: 是否显示，bool，None为忽略该项\n        :param checked: 是否被选中，bool，None为忽略该项\n        :param selected: 是否被选择，bool，None为忽略该项\n        :param enabled: 是否可用，bool，None为忽略该项\n        :param clickable: 是否可点击，bool，None为忽略该项\n        :param have_rect: 是否拥有大小和位置，bool，None为忽略该项\n        :param have_text: 是否含有文本，bool，None为忽略该项\n        :param tag: 指定的元素类型\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def _get_attr(self,\n                  name: str,\n                  value: str,\n                  method: str, equal: bool = True) -> ChromiumFilter:\n        \"\"\"返回通过某个方法可获得某个值的元素\n        :param name: 属性名称\n        :param value: 属性值\n        :param method: 方法名称\n        :return: 筛选结果\n        \"\"\"\n        ...\n\n    def _any_state(self, name: str, equal: bool = True) -> ChromiumFilter:\n        \"\"\"\n        :param name: 状态名称\n        :param equal: 是否是指定状态，False表示否定状态\n        :return: 选中的列表\n        \"\"\"\n        ...\n\n\nclass Getter(object):\n    _list: SessionElementsList = ...\n\n    def __init__(self, _list: SessionElementsList):\n        \"\"\"\n        :param _list: 元素列表对象\n        \"\"\"\n        ...\n\n    def links(self) -> List[str]:\n        \"\"\"返回所有元素的link属性组成的列表\"\"\"\n        ...\n\n    def texts(self) -> List[str]:\n        \"\"\"返回所有元素的text属性组成的列表\"\"\"\n        ...\n\n    def attrs(self, name: str) -> List[str]:\n        \"\"\"返回所有元素指定的attr属性组成的列表\n        :param name: 属性名称\n        :return: 属性文本组成的列表\n        \"\"\"\n        ...\n\n\ndef get_eles(locators: Union[str, tuple, List[Union[str, tuple]]],\n             owner: BaseParser,\n             any_one: bool = False,\n             first_ele: bool = True,\n             timeout: float = 10) -> Union[Dict[str, ChromiumElement], Dict[str, SessionElement],\nDict[str, List[ChromiumElement]], Dict[str, List[SessionElement]]]:\n    \"\"\"传入多个定位符，获取多个ele\n    :param locators: 定位符或它们组成的列表\n    :param owner: 页面或元素对象\n    :param any_one: 是否找到任何一个即返回\n    :param first_ele: 每个定位符是否只获取第一个元素\n    :param timeout: 超时时间（秒）\n    :return: 多个定位符组成的dict，first_only为False返回列表，否则为元素，无结果的返回False\n    \"\"\"\n    ...\n\n\ndef get_frame(owner: BaseParser,\n              loc_ind_ele: Union[str, int, tuple, ChromiumFrame, ChromiumElement],\n              timeout: float = None) -> ChromiumFrame:\n    \"\"\"获取页面中一个frame对象\n    :param owner: 要在其中查找元素的对象\n    :param loc_ind_ele: 定位符、iframe序号、ChromiumFrame对象，序号从1开始，可传入负数获取倒数第几个\n    :param timeout: 查找元素超时时间（秒）\n    :return: ChromiumFrame对象\n    \"\"\"\n    ...\n"
  },
  {
    "path": "DrissionPage/_functions/keys.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom platform import system\r\n\r\nfrom ..errors import AlertExistsError\r\n\r\nmodifierBit = {'\\ue00a': 1, '\\ue009': 2, '\\ue03d': 4, '\\ue008': 8}\r\nsys = system().lower()\r\n\r\n\r\nclass Keys:\r\n    \"\"\"特殊按键\"\"\"\r\n    NULL = '\\ue000'\r\n    CANCEL = '\\ue001'  # ^break\r\n    HELP = '\\ue002'\r\n    BACKSPACE = '\\ue003'\r\n    TAB = '\\ue004'\r\n    CLEAR = '\\ue005'\r\n    RETURN = '\\ue006'\r\n    ENTER = '\\ue007'\r\n    SHIFT = '\\ue008'\r\n    CONTROL = '\\ue009'\r\n    CTRL = '\\ue009'\r\n    ALT = '\\ue00a'\r\n    PAUSE = '\\ue00b'\r\n    ESCAPE = '\\ue00c'\r\n    SPACE = '\\ue00d'\r\n    PAGE_UP = '\\ue00e'\r\n    PAGE_DOWN = '\\ue00f'\r\n    END = '\\ue010'\r\n    HOME = '\\ue011'\r\n    LEFT = '\\ue012'\r\n    UP = '\\ue013'\r\n    RIGHT = '\\ue014'\r\n    DOWN = '\\ue015'\r\n    INSERT = '\\ue016'\r\n    DELETE = '\\ue017'\r\n    DEL = '\\ue017'\r\n    SEMICOLON = '\\ue018'\r\n    EQUALS = '\\ue019'\r\n\r\n    META = '\\ue03d'\r\n    COMMAND = '\\ue03d'\r\n\r\n    CTRL_COMM = '\\ue03d' if sys in ('macos', 'darwin') else '\\ue009'\r\n\r\n    CTRL_A = (CTRL_COMM, 'a')\r\n    CTRL_C = (CTRL_COMM, 'c')\r\n    CTRL_X = (CTRL_COMM, 'x')\r\n    CTRL_V = (CTRL_COMM, 'v')\r\n    CTRL_Z = (CTRL_COMM, 'z')\r\n    CTRL_Y = (CTRL_COMM, 'y')\r\n\r\n    NUMPAD0 = '\\ue01a'  # number pad keys\r\n    NUMPAD1 = '\\ue01b'\r\n    NUMPAD2 = '\\ue01c'\r\n    NUMPAD3 = '\\ue01d'\r\n    NUMPAD4 = '\\ue01e'\r\n    NUMPAD5 = '\\ue01f'\r\n    NUMPAD6 = '\\ue020'\r\n    NUMPAD7 = '\\ue021'\r\n    NUMPAD8 = '\\ue022'\r\n    NUMPAD9 = '\\ue023'\r\n    MULTIPLY = '\\ue024'\r\n    ADD = '\\ue025'\r\n    # SEPARATOR = '\\ue026'\r\n    SUBTRACT = '\\ue027'\r\n    DECIMAL = '\\ue028'\r\n    DIVIDE = '\\ue029'\r\n\r\n    F1 = '\\ue031'  # function  keys\r\n    F2 = '\\ue032'\r\n    F3 = '\\ue033'\r\n    F4 = '\\ue034'\r\n    F5 = '\\ue035'\r\n    F6 = '\\ue036'\r\n    F7 = '\\ue037'\r\n    F8 = '\\ue038'\r\n    F9 = '\\ue039'\r\n    F10 = '\\ue03a'\r\n    F11 = '\\ue03b'\r\n    F12 = '\\ue03c'\r\n    # ZENKAKU_HANKAKU = '\\ue040'\r\n\r\n\r\nkeyDefinitions = {\r\n    '0': {'keyCode': 48, 'key': '0', 'code': 'Digit0'},\r\n    '1': {'keyCode': 49, 'key': '1', 'code': 'Digit1'},\r\n    '2': {'keyCode': 50, 'key': '2', 'code': 'Digit2'},\r\n    '3': {'keyCode': 51, 'key': '3', 'code': 'Digit3'},\r\n    '4': {'keyCode': 52, 'key': '4', 'code': 'Digit4'},\r\n    '5': {'keyCode': 53, 'key': '5', 'code': 'Digit5'},\r\n    '6': {'keyCode': 54, 'key': '6', 'code': 'Digit6'},\r\n    '7': {'keyCode': 55, 'key': '7', 'code': 'Digit7'},\r\n    '8': {'keyCode': 56, 'key': '8', 'code': 'Digit8'},\r\n    '9': {'keyCode': 57, 'key': '9', 'code': 'Digit9'},\r\n    'a': {'keyCode': 65, 'key': 'a', 'code': 'KeyA'},\r\n    'b': {'keyCode': 66, 'key': 'b', 'code': 'KeyB'},\r\n    'c': {'keyCode': 67, 'key': 'c', 'code': 'KeyC'},\r\n    'd': {'keyCode': 68, 'key': 'd', 'code': 'KeyD'},\r\n    'e': {'keyCode': 69, 'key': 'e', 'code': 'KeyE'},\r\n    'f': {'keyCode': 70, 'key': 'f', 'code': 'KeyF'},\r\n    'g': {'keyCode': 71, 'key': 'g', 'code': 'KeyG'},\r\n    'h': {'keyCode': 72, 'key': 'h', 'code': 'KeyH'},\r\n    'i': {'keyCode': 73, 'key': 'i', 'code': 'KeyI'},\r\n    'j': {'keyCode': 74, 'key': 'j', 'code': 'KeyJ'},\r\n    'k': {'keyCode': 75, 'key': 'k', 'code': 'KeyK'},\r\n    'l': {'keyCode': 76, 'key': 'l', 'code': 'KeyL'},\r\n    'm': {'keyCode': 77, 'key': 'm', 'code': 'KeyM'},\r\n    'n': {'keyCode': 78, 'key': 'n', 'code': 'KeyN'},\r\n    'o': {'keyCode': 79, 'key': 'o', 'code': 'KeyO'},\r\n    'p': {'keyCode': 80, 'key': 'p', 'code': 'KeyP'},\r\n    'q': {'keyCode': 81, 'key': 'q', 'code': 'KeyQ'},\r\n    'r': {'keyCode': 82, 'key': 'r', 'code': 'KeyR'},\r\n    's': {'keyCode': 83, 'key': 's', 'code': 'KeyS'},\r\n    't': {'keyCode': 84, 'key': 't', 'code': 'KeyT'},\r\n    'u': {'keyCode': 85, 'key': 'u', 'code': 'KeyU'},\r\n    'v': {'keyCode': 86, 'key': 'v', 'code': 'KeyV'},\r\n    'w': {'keyCode': 87, 'key': 'w', 'code': 'KeyW'},\r\n    'x': {'keyCode': 88, 'key': 'x', 'code': 'KeyX'},\r\n    'y': {'keyCode': 89, 'key': 'y', 'code': 'KeyY'},\r\n    'z': {'keyCode': 90, 'key': 'z', 'code': 'KeyZ'},\r\n    ' ': {'keyCode': 32, 'key': ' ', 'code': 'Space'},\r\n    '*': {'keyCode': 106, 'key': '*', 'code': 'NumpadMultiply', 'location': 3},\r\n    '+': {'keyCode': 107, 'key': '+', 'code': 'NumpadAdd', 'location': 3},\r\n    '-': {'keyCode': 109, 'key': '-', 'code': 'NumpadSubtract', 'location': 3},\r\n    '/': {'keyCode': 111, 'key': '/', 'code': 'NumpadDivide', 'location': 3},\r\n    ';': {'keyCode': 186, 'key': ';', 'code': 'Semicolon'},\r\n    '=': {'keyCode': 187, 'key': '=', 'code': 'Equal'},\r\n    ',': {'keyCode': 188, 'key': ',', 'code': 'Comma'},\r\n    '.': {'keyCode': 190, 'key': '.', 'code': 'Period'},\r\n    '`': {'keyCode': 192, 'key': '`', 'code': 'Backquote'},\r\n    '[': {'keyCode': 219, 'key': '[', 'code': 'BracketLeft'},\r\n    '\\\\': {'keyCode': 220, 'key': '\\\\', 'code': 'Backslash'},\r\n    ']': {'keyCode': 221, 'key': ']', 'code': 'BracketRight'},\r\n    '\\'': {'keyCode': 222, 'key': '\\'', 'code': 'Quote'},\r\n    ')': {'keyCode': 48, 'key': ')', 'code': 'Digit0'},\r\n    '!': {'keyCode': 49, 'key': '!', 'code': 'Digit1'},\r\n    '@': {'keyCode': 50, 'key': '@', 'code': 'Digit2'},\r\n    '#': {'keyCode': 51, 'key': '#', 'code': 'Digit3'},\r\n    '$': {'keyCode': 52, 'key': '$', 'code': 'Digit4'},\r\n    '%': {'keyCode': 53, 'key': '%', 'code': 'Digit5'},\r\n    '^': {'keyCode': 54, 'key': '^', 'code': 'Digit6'},\r\n    '&': {'keyCode': 55, 'key': '&', 'code': 'Digit7'},\r\n    '(': {'keyCode': 57, 'key': '(', 'code': 'Digit9'},\r\n    'A': {'keyCode': 65, 'key': 'A', 'code': 'KeyA'},\r\n    'B': {'keyCode': 66, 'key': 'B', 'code': 'KeyB'},\r\n    'C': {'keyCode': 67, 'key': 'C', 'code': 'KeyC'},\r\n    'D': {'keyCode': 68, 'key': 'D', 'code': 'KeyD'},\r\n    'E': {'keyCode': 69, 'key': 'E', 'code': 'KeyE'},\r\n    'F': {'keyCode': 70, 'key': 'F', 'code': 'KeyF'},\r\n    'G': {'keyCode': 71, 'key': 'G', 'code': 'KeyG'},\r\n    'H': {'keyCode': 72, 'key': 'H', 'code': 'KeyH'},\r\n    'I': {'keyCode': 73, 'key': 'I', 'code': 'KeyI'},\r\n    'J': {'keyCode': 74, 'key': 'J', 'code': 'KeyJ'},\r\n    'K': {'keyCode': 75, 'key': 'K', 'code': 'KeyK'},\r\n    'L': {'keyCode': 76, 'key': 'L', 'code': 'KeyL'},\r\n    'M': {'keyCode': 77, 'key': 'M', 'code': 'KeyM'},\r\n    'N': {'keyCode': 78, 'key': 'N', 'code': 'KeyN'},\r\n    'O': {'keyCode': 79, 'key': 'O', 'code': 'KeyO'},\r\n    'P': {'keyCode': 80, 'key': 'P', 'code': 'KeyP'},\r\n    'Q': {'keyCode': 81, 'key': 'Q', 'code': 'KeyQ'},\r\n    'R': {'keyCode': 82, 'key': 'R', 'code': 'KeyR'},\r\n    'S': {'keyCode': 83, 'key': 'S', 'code': 'KeyS'},\r\n    'T': {'keyCode': 84, 'key': 'T', 'code': 'KeyT'},\r\n    'U': {'keyCode': 85, 'key': 'U', 'code': 'KeyU'},\r\n    'V': {'keyCode': 86, 'key': 'V', 'code': 'KeyV'},\r\n    'W': {'keyCode': 87, 'key': 'W', 'code': 'KeyW'},\r\n    'X': {'keyCode': 88, 'key': 'X', 'code': 'KeyX'},\r\n    'Y': {'keyCode': 89, 'key': 'Y', 'code': 'KeyY'},\r\n    'Z': {'keyCode': 90, 'key': 'Z', 'code': 'KeyZ'},\r\n    ':': {'keyCode': 186, 'key': ':', 'code': 'Semicolon'},\r\n    '<': {'keyCode': 188, 'key': '<', 'code': 'Comma'},\r\n    '_': {'keyCode': 189, 'key': '_', 'code': 'Minus'},\r\n    '>': {'keyCode': 190, 'key': '>', 'code': 'Period'},\r\n    '?': {'keyCode': 191, 'key': '?', 'code': 'Slash'},\r\n    '~': {'keyCode': 192, 'key': '~', 'code': 'Backquote'},\r\n    '{': {'keyCode': 219, 'key': '{', 'code': 'BracketLeft'},\r\n    '|': {'keyCode': 220, 'key': '|', 'code': 'Backslash'},\r\n    # '\\ue026': {'keyCode': 220, 'key': '|', 'code': 'Backslash'},\r\n    '}': {'keyCode': 221, 'key': '}', 'code': 'BracketRight'},\r\n    '\"': {'keyCode': 222, 'key': '\"', 'code': 'Quote'},\r\n    '\\n': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\\r'},\r\n    '\\ue007': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\\r'},\r\n    '\\ue006': {'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\\r', 'location': 3},\r\n    '\\ue003': {'keyCode': 8, 'code': 'Backspace', 'key': 'Backspace'},\r\n    '\\ue00d': {'keyCode': 32, 'code': 'Space', 'key': ' '},\r\n    # 'PageUp': {'keyCode': 33, 'shiftKeyCode': 105, 'key': 'PageUp', 'code': 'Numpad9', 'shiftKey': '9', 'location': 3},\r\n    '\\ue00e': {'keyCode': 33, 'code': 'PageUp', 'key': 'PageUp'},\r\n    # 'PageDown': {'keyCode': 34, 'shiftKeyCode': 99, 'key': 'PageDown', 'code': 'Numpad3', 'shiftKey': '3', 'location': 3},\r\n    '\\ue00f': {'keyCode': 34, 'code': 'PageDown', 'key': 'PageDown'},\r\n    '\\ue008': {'keyCode': 16, 'code': 'ShiftLeft', 'key': 'Shift', 'location': 1},\r\n    # 'ShiftRight': {'keyCode': 16, 'code': 'ShiftRight', 'key': 'Shift', 'location': 2},\r\n    '\\ue009': {'keyCode': 17, 'code': 'ControlLeft', 'key': 'Control', 'location': 1},\r\n    # 'ControlRight': {'keyCode': 17, 'code': 'ControlRight', 'key': 'Control', 'location': 2},\r\n    '\\ue00a': {'keyCode': 18, 'code': 'AltLeft', 'key': 'Alt', 'location': 1},\r\n    # 'AltRight': {'keyCode': 18, 'code': 'AltRight', 'key': 'Alt', 'location': 2},\r\n    '\\ue03d': {'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft'},\r\n    '\\r': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\\r'},\r\n    '\\ue011': {'keyCode': 36, 'code': 'Home', 'key': 'Home'},\r\n    # 'Numpad7': {'keyCode': 36, 'shiftKeyCode': 103, 'key': 'Home', 'code': 'Numpad7', 'shiftKey': '7', 'location': 3},\r\n    '\\ue012': {'keyCode': 37, 'code': 'ArrowLeft', 'key': 'ArrowLeft'},\r\n    # 'Numpad4': {'keyCode': 37, 'shiftKeyCode': 100, 'key': 'ArrowLeft', 'code': 'Numpad4', 'shiftKey': '4', 'location': 3},\r\n    # 'Numpad8': {'keyCode': 38, 'shiftKeyCode': 104, 'key': 'ArrowUp', 'code': 'Numpad8', 'shiftKey': '8', 'location': 3},\r\n    '\\ue013': {'keyCode': 38, 'code': 'ArrowUp', 'key': 'ArrowUp'},\r\n    '\\ue014': {'keyCode': 39, 'code': 'ArrowRight', 'key': 'ArrowRight'},\r\n    # 'Numpad6': {'keyCode': 39, 'shiftKeyCode': 102, 'key': 'ArrowRight', 'code': 'Numpad6', 'shiftKey': '6', 'location': 3},\r\n    # 'Numpad2': {'keyCode': 40, 'shiftKeyCode': 98, 'key': 'ArrowDown', 'code': 'Numpad2', 'shiftKey': '2', 'location': 3},\r\n    '\\ue015': {'keyCode': 40, 'code': 'ArrowDown', 'key': 'ArrowDown'},\r\n\r\n    '\\ue001': {'keyCode': 3, 'code': 'Abort', 'key': 'Cancel'},\r\n    '\\ue002': {'keyCode': 6, 'code': 'Help', 'key': 'Help'},\r\n    '\\ue004': {'keyCode': 9, 'code': 'Tab', 'key': 'Tab'},\r\n    '\\ue005': {'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3},\r\n    '\\ue00b': {'keyCode': 19, 'code': 'Pause', 'key': 'Pause'},\r\n    # 'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'},\r\n    '\\ue00c': {'keyCode': 27, 'code': 'Escape', 'key': 'Escape'},\r\n    # 'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'},\r\n    # 'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'},\r\n    '\\ue010': {'keyCode': 35, 'code': 'End', 'key': 'End'},\r\n    # 'Numpad1': {'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'code': 'Numpad1', 'shiftKey': '1', 'location': 3},\r\n    # 'Select': {'keyCode': 41, 'code': 'Select', 'key': 'Select'},\r\n    # 'Open': {'keyCode': 43, 'code': 'Open', 'key': 'Execute'},\r\n    # 'PrintScreen': {'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen'},\r\n    '\\ue016': {'keyCode': 45, 'code': 'Insert', 'key': 'Insert'},\r\n    # 'Numpad0': {'keyCode': 45, 'shiftKeyCode': 96, 'key': 'Insert', 'code': 'Numpad0', 'shiftKey': '0', 'location': 3},\r\n    '\\ue017': {'keyCode': 46, 'code': 'Delete', 'key': 'Delete'},\r\n    '\\ue028': {'keyCode': 46, 'shiftKeyCode': 110, 'code': 'NumpadDecimal', 'key': '\\u0000', 'shiftKey': '.',\r\n               'location': 3},\r\n    '\\ue01a': {'keyCode': 48, 'code': 'Digit0', 'shiftKey': ')', 'key': '0'},\r\n    '\\ue01b': {'keyCode': 49, 'code': 'Digit1', 'shiftKey': '!', 'key': '1'},\r\n    '\\ue01c': {'keyCode': 50, 'code': 'Digit2', 'shiftKey': '@', 'key': '2'},\r\n    '\\ue01d': {'keyCode': 51, 'code': 'Digit3', 'shiftKey': '#', 'key': '3'},\r\n    '\\ue01e': {'keyCode': 52, 'code': 'Digit4', 'shiftKey': '$', 'key': '4'},\r\n    '\\ue01f': {'keyCode': 53, 'code': 'Digit5', 'shiftKey': '%', 'key': '5'},\r\n    '\\ue020': {'keyCode': 54, 'code': 'Digit6', 'shiftKey': '^', 'key': '6'},\r\n    '\\ue021': {'keyCode': 55, 'code': 'Digit7', 'shiftKey': '&', 'key': '7'},\r\n    '\\ue022': {'keyCode': 56, 'code': 'Digit8', 'shiftKey': '*', 'key': '8'},\r\n    '\\ue023': {'keyCode': 57, 'code': 'Digit9', 'shiftKey': r'\\(', 'key': '9'},\r\n    '\\ue024': {'keyCode': 106, 'code': 'NumpadMultiply', 'key': '*', 'location': 3},\r\n    '\\ue025': {'keyCode': 107, 'code': 'NumpadAdd', 'key': '+', 'location': 3},\r\n    '\\ue027': {'keyCode': 109, 'code': 'NumpadSubtract', 'key': '-', 'location': 3},\r\n    '\\ue029': {'keyCode': 111, 'code': 'NumpadDivide', 'key': '/', 'location': 3},\r\n    '\\ue031': {'keyCode': 112, 'code': 'F1', 'key': 'F1'},\r\n    '\\ue032': {'keyCode': 113, 'code': 'F2', 'key': 'F2'},\r\n    '\\ue033': {'keyCode': 114, 'code': 'F3', 'key': 'F3'},\r\n    '\\ue034': {'keyCode': 115, 'code': 'F4', 'key': 'F4'},\r\n    '\\ue035': {'keyCode': 116, 'code': 'F5', 'key': 'F5'},\r\n    '\\ue036': {'keyCode': 117, 'code': 'F6', 'key': 'F6'},\r\n    '\\ue037': {'keyCode': 118, 'code': 'F7', 'key': 'F7'},\r\n    '\\ue038': {'keyCode': 119, 'code': 'F8', 'key': 'F8'},\r\n    '\\ue039': {'keyCode': 120, 'code': 'F9', 'key': 'F9'},\r\n    '\\ue03a': {'keyCode': 121, 'code': 'F10', 'key': 'F10'},\r\n    '\\ue03b': {'keyCode': 122, 'code': 'F11', 'key': 'F11'},\r\n    '\\ue03c': {'keyCode': 123, 'code': 'F12', 'key': 'F12'},\r\n    '\\ue018': {'keyCode': 186, 'code': 'Semicolon', 'shiftKey': ':', 'key': ';'},\r\n    '\\ue019': {'keyCode': 187, 'code': 'NumpadEqual', 'key': '=', 'location': 3},\r\n    '\\u0000': {'keyCode': 46, 'key': '\\u0000', 'code': 'NumpadDecimal', 'location': 3},\r\n    # 'KeyA': {'keyCode': 65, 'code': 'KeyA', 'shiftKey': 'A', 'key': 'a'},\r\n    # 'KeyB': {'keyCode': 66, 'code': 'KeyB', 'shiftKey': 'B', 'key': 'b'},\r\n    # 'KeyC': {'keyCode': 67, 'code': 'KeyC', 'shiftKey': 'C', 'key': 'c'},\r\n    # 'KeyD': {'keyCode': 68, 'code': 'KeyD', 'shiftKey': 'D', 'key': 'd'},\r\n    # 'KeyE': {'keyCode': 69, 'code': 'KeyE', 'shiftKey': 'E', 'key': 'e'},\r\n    # 'KeyF': {'keyCode': 70, 'code': 'KeyF', 'shiftKey': 'F', 'key': 'f'},\r\n    # 'KeyG': {'keyCode': 71, 'code': 'KeyG', 'shiftKey': 'G', 'key': 'g'},\r\n    # 'KeyH': {'keyCode': 72, 'code': 'KeyH', 'shiftKey': 'H', 'key': 'h'},\r\n    # 'KeyI': {'keyCode': 73, 'code': 'KeyI', 'shiftKey': 'I', 'key': 'i'},\r\n    # 'KeyJ': {'keyCode': 74, 'code': 'KeyJ', 'shiftKey': 'J', 'key': 'j'},\r\n    # 'KeyK': {'keyCode': 75, 'code': 'KeyK', 'shiftKey': 'K', 'key': 'k'},\r\n    # 'KeyL': {'keyCode': 76, 'code': 'KeyL', 'shiftKey': 'L', 'key': 'l'},\r\n    # 'KeyM': {'keyCode': 77, 'code': 'KeyM', 'shiftKey': 'M', 'key': 'm'},\r\n    # 'KeyN': {'keyCode': 78, 'code': 'KeyN', 'shiftKey': 'N', 'key': 'n'},\r\n    # 'KeyO': {'keyCode': 79, 'code': 'KeyO', 'shiftKey': 'O', 'key': 'o'},\r\n    # 'KeyP': {'keyCode': 80, 'code': 'KeyP', 'shiftKey': 'P', 'key': 'p'},\r\n    # 'KeyQ': {'keyCode': 81, 'code': 'KeyQ', 'shiftKey': 'Q', 'key': 'q'},\r\n    # 'KeyR': {'keyCode': 82, 'code': 'KeyR', 'shiftKey': 'R', 'key': 'r'},\r\n    # 'KeyS': {'keyCode': 83, 'code': 'KeyS', 'shiftKey': 'S', 'key': 's'},\r\n    # 'KeyT': {'keyCode': 84, 'code': 'KeyT', 'shiftKey': 'T', 'key': 't'},\r\n    # 'KeyU': {'keyCode': 85, 'code': 'KeyU', 'shiftKey': 'U', 'key': 'u'},\r\n    # 'KeyV': {'keyCode': 86, 'code': 'KeyV', 'shiftKey': 'V', 'key': 'v'},\r\n    # 'KeyW': {'keyCode': 87, 'code': 'KeyW', 'shiftKey': 'W', 'key': 'w'},\r\n    # 'KeyX': {'keyCode': 88, 'code': 'KeyX', 'shiftKey': 'X', 'key': 'x'},\r\n    # 'KeyY': {'keyCode': 89, 'code': 'KeyY', 'shiftKey': 'Y', 'key': 'y'},\r\n    # 'KeyZ': {'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z'},\r\n    # 'MetaLeft': {'keyCode': 91, 'code': 'MetaLeft', 'key': 'Meta'},\r\n    # 'MetaRight': {'keyCode': 92, 'code': 'MetaRight', 'key': 'Meta'},\r\n    # 'ContextMenu': {'keyCode': 93, 'code': 'ContextMenu', 'key': 'ContextMenu'},\r\n    # 'F13': {'keyCode': 124, 'code': 'F13', 'key': 'F13'},\r\n    # 'F14': {'keyCode': 125, 'code': 'F14', 'key': 'F14'},\r\n    # 'F15': {'keyCode': 126, 'code': 'F15', 'key': 'F15'},\r\n    # 'F16': {'keyCode': 127, 'code': 'F16', 'key': 'F16'},\r\n    # 'F17': {'keyCode': 128, 'code': 'F17', 'key': 'F17'},\r\n    # 'F18': {'keyCode': 129, 'code': 'F18', 'key': 'F18'},\r\n    # 'F19': {'keyCode': 130, 'code': 'F19', 'key': 'F19'},\r\n    # 'F20': {'keyCode': 131, 'code': 'F20', 'key': 'F20'},\r\n    # 'F21': {'keyCode': 132, 'code': 'F21', 'key': 'F21'},\r\n    # 'F22': {'keyCode': 133, 'code': 'F22', 'key': 'F22'},\r\n    # 'F23': {'keyCode': 134, 'code': 'F23', 'key': 'F23'},\r\n    # 'F24': {'keyCode': 135, 'code': 'F24', 'key': 'F24'},\r\n    # 'NumLock': {'keyCode': 144, 'code': 'NumLock', 'key': 'NumLock'},\r\n    # 'ScrollLock': {'keyCode': 145, 'code': 'ScrollLock', 'key': 'ScrollLock'},\r\n    # 'AudioVolumeMute': {'keyCode': 173, 'code': 'AudioVolumeMute', 'key': 'AudioVolumeMute'},\r\n    # 'AudioVolumeDown': {'keyCode': 174, 'code': 'AudioVolumeDown', 'key': 'AudioVolumeDown'},\r\n    # 'AudioVolumeUp': {'keyCode': 175, 'code': 'AudioVolumeUp', 'key': 'AudioVolumeUp'},\r\n    # 'MediaTrackNext': {'keyCode': 176, 'code': 'MediaTrackNext', 'key': 'MediaTrackNext'},\r\n    # 'MediaTrackPrevious': {'keyCode': 177, 'code': 'MediaTrackPrevious', 'key': 'MediaTrackPrevious'},\r\n    # 'MediaStop': {'keyCode': 178, 'code': 'MediaStop', 'key': 'MediaStop'},\r\n    # 'MediaPlayPause': {'keyCode': 179, 'code': 'MediaPlayPause', 'key': 'MediaPlayPause'},\r\n    # 'Equal': {'keyCode': 187, 'code': 'Equal', 'shiftKey': '+', 'key': '='},\r\n    # 'Comma': {'keyCode': 188, 'code': 'Comma', 'shiftKey': '<', 'key': ','},\r\n    # 'Minus': {'keyCode': 189, 'code': 'Minus', 'shiftKey': '_', 'key': '-'},\r\n    # 'Period': {'keyCode': 190, 'code': 'Period', 'shiftKey': '>', 'key': '.'},\r\n    # 'Slash': {'keyCode': 191, 'code': 'Slash', 'shiftKey': '?', 'key': '/'},\r\n    # 'Backquote': {'keyCode': 192, 'code': 'Backquote', 'shiftKey': '~', 'key': '`'},\r\n    # 'BracketLeft': {'keyCode': 219, 'code': 'BracketLeft', 'shiftKey': '{', 'key': '['},\r\n    # 'Backslash': {'keyCode': 220, 'code': 'Backslash', 'shiftKey': '|', 'key': '\\\\'},\r\n    # 'BracketRight': {'keyCode': 221, 'code': 'BracketRight', 'shiftKey': '}', 'key': ']'},\r\n    # 'Quote': {'keyCode': 222, 'code': 'Quote', 'shiftKey': '\"', 'key': '\\''},\r\n    # 'AltGraph': {'keyCode': 225, 'code': 'AltGraph', 'key': 'AltGraph'},\r\n    # 'Props': {'keyCode': 247, 'code': 'Props', 'key': 'CrSel'},\r\n    # 'Cancel': {'keyCode': 3, 'key': 'Cancel', 'code': 'Abort'},\r\n    # 'Clear': {'keyCode': 12, 'key': 'Clear', 'code': 'Numpad5', 'location': 3},\r\n    # 'Shift': {'keyCode': 16, 'key': 'Shift', 'code': 'ShiftLeft'},\r\n    # 'Control': {'keyCode': 17, 'key': 'Control', 'code': 'ControlLeft'},\r\n    # 'Alt': {'keyCode': 18, 'key': 'Alt', 'code': 'AltLeft'},\r\n    # 'Accept': {'keyCode': 30, 'key': 'Accept'},\r\n    # 'ModeChange': {'keyCode': 31, 'key': 'ModeChange'},\r\n    # 'Print': {'keyCode': 42, 'key': 'Print'},\r\n    # 'Execute': {'keyCode': 43, 'key': 'Execute', 'code': 'Open'},\r\n    # 'Attn': {'keyCode': 246, 'key': 'Attn'},\r\n    # 'CrSel': {'keyCode': 247, 'key': 'CrSel', 'code': 'Props'},\r\n    # 'ExSel': {'keyCode': 248, 'key': 'ExSel'},\r\n    # 'EraseEof': {'keyCode': 249, 'key': 'EraseEof'},\r\n    # 'Play': {'keyCode': 250, 'key': 'Play'},\r\n    # 'ZoomOut': {'keyCode': 251, 'key': 'ZoomOut'},\r\n    # 'Power': {'key': 'Power', 'code': 'Power'},\r\n    # 'Eject': {'key': 'Eject', 'code': 'Eject'},\r\n}\r\n\r\n\r\ndef keys_to_typing(value):\r\n    typing = []\r\n    modifier = 0\r\n    for val in value:\r\n        if val in ('\\ue009', '\\ue008', '\\ue00a', '\\ue03d'):\r\n            modifier |= modifierBit.get(val, 0)\r\n            continue\r\n        if isinstance(val, (int, float)):\r\n            val = str(val)\r\n            for i in range(len(val)):\r\n                typing.append(val[i])\r\n        else:\r\n            for i in range(len(val)):\r\n                typing.append(val[i])\r\n\r\n    return modifier, ''.join(typing)\r\n\r\n\r\ndef make_input_data(modifiers, key, key_up=False):\r\n    data = keyDefinitions.get(key)\r\n    if not data:\r\n        return None\r\n\r\n    result = {'modifiers': modifiers, 'autoRepeat': False, '_ignore': AlertExistsError}\r\n    shift = modifiers & 8\r\n\r\n    if shift and data.get('shiftKey'):\r\n        result['key'] = data['shiftKey']\r\n        result['text'] = data['shiftKey']\r\n    elif 'key' in data:\r\n        result['key'] = data['key']\r\n\r\n    if len(result.get('key', '')) == 1:  # type: ignore\r\n        result['text'] = data['key']\r\n\r\n    sys_text = 'windowsVirtualKeyCode' if sys == 'windows' else 'nativeVirtualKeyCode'\r\n    if shift and data.get('shiftKeyCode'):\r\n        result[sys_text] = data['shiftKeyCode']\r\n    elif 'keyCode' in data:\r\n        result[sys_text] = data['keyCode']\r\n\r\n    if 'code' in data:\r\n        result['code'] = data['code']\r\n\r\n    if 'location' in data:\r\n        result['location'] = data['location']\r\n        result['isKeypad'] = data['location'] == 3\r\n    else:\r\n        result['location'] = 0\r\n        result['isKeypad'] = False\r\n\r\n    if shift and data.get('shiftText'):\r\n        result['text'] = data['shiftText']\r\n        result['unmodifiedText'] = data['shiftText']\r\n    elif 'text' in data:\r\n        result['text'] = data['text']\r\n        result['unmodifiedText'] = data['text']\r\n\r\n    if modifiers & ~8:\r\n        result['text'] = ''\r\n\r\n    result['type'] = 'keyUp' if key_up else ('keyDown' if result.get('text') else 'rawKeyDown')\r\n    return result\r\n\r\n\r\ndef send_key(page, modifier, key):\r\n    data = make_input_data(modifier, key)\r\n    if data:\r\n        page._run_cdp('Input.dispatchKeyEvent', **data)\r\n        data['type'] = 'keyUp'\r\n        page._run_cdp('Input.dispatchKeyEvent', **data)\r\n\r\n    else:\r\n        page._run_cdp('Input.insertText', text=key, _ignore=AlertExistsError)\r\n\r\n\r\ndef input_text_or_keys(page, text_or_keys):\r\n    if not isinstance(text_or_keys, (tuple, list)):\r\n        text_or_keys = (str(text_or_keys),)\r\n    modifier, text_or_keys = keys_to_typing(text_or_keys)\r\n\r\n    if modifier != 0:  # 包含修饰符\r\n        for key in text_or_keys:\r\n            send_key(page, modifier, key)\r\n        return\r\n\r\n    if text_or_keys.endswith(('\\n', '\\ue007', '\\ue006')):\r\n        page._run_cdp('Input.insertText', text=text_or_keys[:-1], _ignore=AlertExistsError)\r\n        send_key(page, modifier, '\\n')\r\n    else:\r\n        page._run_cdp('Input.insertText', text=text_or_keys, _ignore=AlertExistsError)\r\n"
  },
  {
    "path": "DrissionPage/_functions/keys.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Tuple, Union, Any\r\n\r\nfrom .._pages.chromium_base import ChromiumBase\r\n\r\n\r\nclass Keys:\r\n    \"\"\"特殊按键\"\"\"\r\n    CTRL_A: tuple\r\n    CTRL_C: tuple\r\n    CTRL_X: tuple\r\n    CTRL_V: tuple\r\n    CTRL_Z: tuple\r\n    CTRL_Y: tuple\r\n\r\n    NULL: str\r\n    CANCEL: str\r\n    HELP: str\r\n    BACKSPACE: str\r\n    TAB: str\r\n    CLEAR: str\r\n    RETURN: str\r\n    ENTER: str\r\n    SHIFT: str\r\n    CONTROL: str\r\n    CTRL: str\r\n    ALT: str\r\n    PAUSE: str\r\n    ESCAPE: str\r\n    SPACE: str\r\n    PAGE_UP: str\r\n    PAGE_DOWN: str\r\n    END: str\r\n    HOME: str\r\n    LEFT: str\r\n    UP: str\r\n    RIGHT: str\r\n    DOWN: str\r\n    INSERT: str\r\n    DELETE: str\r\n    DEL: str\r\n    SEMICOLON: str\r\n    EQUALS: str\r\n\r\n    NUMPAD0: str\r\n    NUMPAD1: str\r\n    NUMPAD2: str\r\n    NUMPAD3: str\r\n    NUMPAD4: str\r\n    NUMPAD5: str\r\n    NUMPAD6: str\r\n    NUMPAD7: str\r\n    NUMPAD8: str\r\n    NUMPAD9: str\r\n    MULTIPLY: str\r\n    ADD: str\r\n    SUBTRACT: str\r\n    DECIMAL: str\r\n    DIVIDE: str\r\n\r\n    F1: str\r\n    F2: str\r\n    F3: str\r\n    F4: str\r\n    F5: str\r\n    F6: str\r\n    F7: str\r\n    F8: str\r\n    F9: str\r\n    F10: str\r\n    F11: str\r\n    F12: str\r\n\r\n    META: str\r\n    COMMAND: str\r\n\r\n\r\nkeyDefinitions: dict = ...\r\nmodifierBit: dict = ...\r\n\r\n\r\ndef keys_to_typing(value: Union[str, int, list, tuple]) -> Tuple[int, str]:\r\n    \"\"\"把要输入的内容连成字符串，去掉其中 ctrl 等键。\r\n        返回的modifier表示是否有按下组合键\"\"\"\r\n    ...\r\n\r\n\r\ndef make_input_data(modifiers: int,\r\n                    key: str,\r\n                    key_up: bool = False) -> dict:\r\n    \"\"\"\r\n    :param modifiers: 功能键设置\r\n    :param key: 按键字符\r\n    :param key_up: 是否提起\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef send_key(page: ChromiumBase, modifier: int, key: str) -> None:\r\n    \"\"\"发送一个字，在键盘中的字符触发按键，其它直接发送文本\r\n    :param page: 动作所在页面\r\n    :param modifier: 功能键信息\r\n    :param key: 要是输入的按键\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef input_text_or_keys(page: ChromiumBase, text_or_keys: Any) -> None:\r\n    \"\"\"输入文本，也可输入组合键，组合键用tuple形式输入\r\n    :param page: ChromiumBase对象\r\n    :param text_or_keys: 文本值或按键组合\r\n    :return: self\r\n    \"\"\"\r\n    ...\r\n"
  },
  {
    "path": "DrissionPage/_functions/locator.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom re import split\r\n\r\nfrom .by import By\r\nfrom .._functions.settings import Settings as _S\r\nfrom ..errors import LocatorError\r\n\r\n\r\ndef locator_to_tuple(loc):\r\n    loc = _preprocess(loc)\r\n\r\n    # 多属性查找\r\n    if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):\r\n        args = _get_args(loc)\r\n\r\n    # 单属性查找\r\n    elif loc.startswith('@') and loc != '@':\r\n        arg = _get_arg(loc[1:])\r\n        arg.append(False)\r\n        args = {'and': True, 'args': [arg]}\r\n\r\n    # 根据tag name查找\r\n    elif loc.startswith(('tag:', 'tag=', 'tag^', 'tag$')) and loc not in ('tag:', 'tag=', 'tag^', 'tag$'):\r\n        at_ind = loc.find('@')\r\n        if at_ind == -1:\r\n            args = {'and': True, 'args': [['tag()', '=', loc[4:].lower(), False]]}\r\n        else:\r\n            args_str = loc[at_ind:]\r\n            if args_str.startswith(('@@', '@|', '@!')):\r\n                args = _get_args(args_str)\r\n                args['args'].append([f'tag()', '=', loc[4:at_ind].lower(), False])\r\n            else:  # t:div@aa=bb的格式\r\n                arg = _get_arg(loc[at_ind + 1:])\r\n                arg.append(False)\r\n                args = {'and': True, 'args': [['tag()', '=', loc[4:at_ind].lower(), False], arg]}\r\n\r\n    # 根据文本查找\r\n    elif loc.startswith(('text=', 'text:', 'text^', 'text$')):\r\n        args = {'and': True, 'args': [['text()', loc[4], loc[5:], False]]}\r\n\r\n    # 根据文本模糊查找\r\n    else:\r\n        args = {'and': True, 'args': [['text()', '=', loc, False]]}\r\n\r\n    return args\r\n\r\n\r\ndef _get_args(text: str = '') -> dict:\r\n    \"\"\"解析定位参数字符串生成dict格式数据\r\n    :param text: 待处理的字符串\r\n    :return: 格式： {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]}\r\n    \"\"\"\r\n    arg_list = []\r\n    args = split(r'(@!|@@|@\\|)', text)[1:]\r\n    if '@@' in args and '@|' in args:\r\n        raise LocatorError(_S._lang.SYMBOL_CONFLICT, VALUE=text)\r\n    _and = '@|' not in args\r\n\r\n    for k in range(0, len(args) - 1, 2):\r\n        arg = _get_arg(args[k + 1])\r\n        if arg:\r\n            arg.append(True if args[k] == '@!' else False)  # 是否去除某个属性\r\n        arg_list.append(arg)\r\n\r\n    return {'and': _and, 'args': arg_list}\r\n\r\n\r\ndef _get_arg(text) -> list:\r\n    \"\"\"解析arg=abc格式字符串，生成格式：['属性名称', '匹配方式', '属性值', 是否否定]，不是式子的返回None\"\"\"\r\n    r = split(r'([:=$^])', text, maxsplit=1)\r\n    if not r[0]:\r\n        return [None, None, None, None]\r\n    # !=时只有属性名没有属性内容，查询是否存在该属性\r\n    name = r[0] if r[0] != 'tx()' else 'text()'\r\n    name = name if name != 't()' else 'teg()'\r\n    return [name, None, None] if len(r) != 3 else [name, r[1], r[2]]\r\n\r\n\r\ndef is_str_loc(text):\r\n    return text.startswith(('.', '#', '@', 't:', 't=', 'tag:', 'tag=', 'tx:', 'tx=', 'tx^', 'tx$', 'text:', 'text=',\r\n                            'text^', 'text$', 'xpath:', 'xpath=', 'x:', 'x=', 'css:', 'css=', 'c:', 'c='))\r\n\r\n\r\ndef is_selenium_loc(loc):\r\n    return (isinstance(loc, tuple) and len(loc) == 2 and loc[0].lower() in (\r\n        'id', 'xpath', 'link text', 'partial link text', 'name', 'tag name', 'class name', 'css selector')\r\n            and isinstance(loc[1], str))\r\n\r\n\r\ndef get_loc(loc, translate_css=False, css_mode=False):\r\n    if isinstance(loc, tuple):\r\n        loc = translate_css_loc(loc) if css_mode else translate_loc(loc)\r\n\r\n    elif isinstance(loc, str):\r\n        loc = str_to_css_loc(loc) if css_mode else str_to_xpath_loc(loc)\r\n\r\n    else:\r\n        raise LocatorError(ALLOW_TYPE=_S._lang.LOC_FORMAT, CURR_VAL=loc)\r\n\r\n    if loc[0] == 'css selector' and translate_css:\r\n        from lxml.cssselect import CSSSelector, ExpressionError\r\n        try:\r\n            path = str(CSSSelector(loc[1], translator='html').path)\r\n            path = path[20:] if path.startswith('descendant-or-self::') else path\r\n            loc = 'xpath', path\r\n        except ExpressionError:\r\n            pass\r\n\r\n    return loc\r\n\r\n\r\ndef str_to_xpath_loc(loc):\r\n    loc_by = 'xpath'\r\n    loc = _preprocess(loc)\r\n\r\n    # 多属性查找\r\n    if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):\r\n        loc_str = _make_multi_xpath_str('*', loc)[1]\r\n\r\n    # 单属性查找\r\n    elif loc.startswith('@') and loc != '@':\r\n        loc_str = _make_single_xpath_str('*', loc)[1]\r\n\r\n    # 根据tag name查找\r\n    elif loc.startswith(('tag:', 'tag=', 'tag^', 'tag$')) and loc not in ('tag:', 'tag=', 'tag^', 'tag$'):\r\n        at_ind = loc.find('@')\r\n        if at_ind == -1:\r\n            loc_str = f'//*[name()=\"{loc[4:]}\"]'\r\n        elif loc[at_ind:].startswith(('@@', '@|', '@!')):\r\n            loc_str = _make_multi_xpath_str(loc[4:at_ind], loc[at_ind:])[1]\r\n        else:\r\n            loc_str = _make_single_xpath_str(loc[4:at_ind], loc[at_ind:])[1]\r\n\r\n    # 根据文本查找\r\n    elif loc.startswith('text='):\r\n        loc_str = f'//*[text()={_quotes_escape(loc[5:])}]'\r\n    elif loc.startswith('text:') and loc != 'text:':\r\n        loc_str = f'//*/text()[contains(., {_quotes_escape(loc[5:])})]/..'\r\n    elif loc.startswith('text^') and loc != 'text^':\r\n        loc_str = f'//*/text()[starts-with(., {_quotes_escape(loc[5:])})]/..'\r\n    elif loc.startswith('text$') and loc != 'text$':\r\n        loc_str = (f'//*/text()[substring(., string-length(.) - string-length({_quotes_escape(loc[5:])}) +1) = '\r\n                   f'{_quotes_escape(loc[5:])}]/..')\r\n\r\n    # 用xpath查找\r\n    elif loc.startswith(('xpath:', 'xpath=')) and loc not in ('xpath:', 'xpath='):\r\n        loc_str = loc[6:]\r\n\r\n    # 用css selector查找\r\n    elif loc.startswith(('css:', 'css=')) and loc not in ('css:', 'css='):\r\n        loc_by = 'css selector'\r\n        loc_str = loc[4:]\r\n\r\n    # 根据文本模糊查找\r\n    elif loc:\r\n        loc_str = f'//*/text()[contains(., {_quotes_escape(loc)})]/..'\r\n    else:\r\n        loc_str = '//*'\r\n\r\n    return loc_by, loc_str\r\n\r\n\r\ndef str_to_css_loc(loc):\r\n    loc_by = 'css selector'\r\n    loc = _preprocess(loc)\r\n\r\n    # 多属性查找\r\n    if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):\r\n        loc_str = _make_multi_css_str('*', loc)[1]\r\n\r\n    # 单属性查找\r\n    elif loc.startswith('@') and loc != '@':\r\n        loc_by, loc_str = _make_single_css_str('*', loc)\r\n\r\n    # 根据tag name查找\r\n    elif loc.startswith(('tag:', 'tag=', 'tag^', 'tag$')) and loc not in ('tag:', 'tag=', 'tag^', 'tag$'):\r\n        at_ind = loc.find('@')\r\n        if at_ind == -1:\r\n            loc_str = loc[4:]\r\n        elif loc[at_ind:].startswith(('@@', '@|', '@!')):\r\n            loc_by, loc_str = _make_multi_css_str(loc[4:at_ind], loc[at_ind:])\r\n        else:\r\n            loc_by, loc_str = _make_single_css_str(loc[4:at_ind], loc[at_ind:])\r\n\r\n    # 根据文本查找\r\n    elif loc.startswith(('text=', 'text:', 'text^', 'text$', 'xpath=', 'xpath:')):\r\n        loc_by, loc_str = str_to_xpath_loc(loc)\r\n\r\n    # 用css selector查找\r\n    elif loc.startswith(('css:', 'css=')) and loc not in ('css:', 'css='):\r\n        loc_str = loc[4:]\r\n\r\n    # 根据文本模糊查找\r\n    elif loc:\r\n        loc_by, loc_str = str_to_xpath_loc(loc)\r\n\r\n    else:\r\n        loc_str = '*'\r\n\r\n    return loc_by, loc_str\r\n\r\n\r\ndef _make_single_xpath_str(tag: str, text: str) -> tuple:\r\n    \"\"\"生成单属性xpath语句\r\n    :param tag: 标签名\r\n    :param text: 待处理的字符串\r\n    :return: xpath字符串\r\n    \"\"\"\r\n    arg_list = [] if tag == '*' else [f'name()=\"{tag}\"']\r\n    arg_str = txt_str = ''\r\n\r\n    if text == '@':\r\n        arg_str = 'not(@*)'\r\n\r\n    else:\r\n        r = split(r'([:=$^])', text, maxsplit=1)\r\n        len_r = len(r)\r\n        len_r0 = len(r[0])\r\n        if len_r == 3 and len_r0 > 1:\r\n            if r[0] in ('@tag()', '@t()'):\r\n                arg_str = f'name()=\"{r[2].lower()}\"'\r\n            else:\r\n                symbol = r[1]\r\n                if symbol == '=':  # 精确查找\r\n                    arg = 'text()' if r[0] in ('@text()', '@tx()') else r[0]\r\n                    arg_str = f'{arg}={_quotes_escape(r[2])}'\r\n\r\n                elif symbol == '^':  # 匹配开头\r\n                    if r[0] in ('@text()', '@tx()'):\r\n                        txt_str = f'/text()[starts-with(., {_quotes_escape(r[2])})]/..'\r\n                        arg_str = ''\r\n                    else:\r\n                        arg_str = f\"starts-with({r[0]},{_quotes_escape(r[2])})\"\r\n\r\n                elif symbol == '$':  # 匹配结尾\r\n                    if r[0] in ('@text()', '@tx()'):\r\n                        txt_str = (f'/text()[substring(., string-length(.) - string-length('\r\n                                   f'{_quotes_escape(r[2])}) +1) = {_quotes_escape(r[2])}]/..')\r\n                        arg_str = ''\r\n                    else:\r\n                        arg_str = (f'substring({r[0]}, string-length({r[0]}) - string-length('\r\n                                   f'{_quotes_escape(r[2])}) +1) = {_quotes_escape(r[2])}')\r\n\r\n                elif symbol == ':':  # 模糊查找\r\n                    if r[0] in ('@text()', '@tx()'):\r\n                        txt_str = f'/text()[contains(., {_quotes_escape(r[2])})]/..'\r\n                        arg_str = ''\r\n                    else:\r\n                        arg_str = f\"contains({r[0]},{_quotes_escape(r[2])})\"\r\n\r\n                else:\r\n                    raise LocatorError(_S._lang.INCORRECT_SIGN_, symbol)\r\n\r\n        elif len_r != 3 and len_r0 > 1:\r\n            if r[0] in ('@tag()', '@t()'):\r\n                arg_str = ''\r\n            else:\r\n                arg_str = 'normalize-space(text())' if r[0] in ('@text()', '@tx()') else f'{r[0]}'\r\n\r\n    if arg_str:\r\n        arg_list.append(arg_str)\r\n    arg_str = ' and '.join(arg_list)\r\n    return 'xpath', f'//*[{arg_str}]{txt_str}' if arg_str else f'//*{txt_str}'\r\n\r\n\r\ndef _make_multi_xpath_str(tag: str, text: str) -> tuple:\r\n    \"\"\"生成多属性查找的xpath语句\r\n    :param tag: 标签名\r\n    :param text: 待处理的字符串\r\n    :return: xpath字符串\r\n    \"\"\"\r\n    arg_list = []\r\n    args = split(r'(@!|@@|@\\|)', text)[1:]\r\n    if '@@' in args and '@|' in args:\r\n        raise LocatorError(_S._lang.SYMBOL_CONFLICT, VALUE=text)\r\n    _and = '@|' not in args\r\n    tags = [] if tag == '*' else [f'name()=\"{tag}\"']\r\n    tags_connect = ' or '\r\n\r\n    for k in range(0, len(args) - 1, 2):\r\n        r = split(r'([:=$^])', args[k + 1], maxsplit=1)\r\n        arg_str = ''\r\n        len_r = len(r)\r\n\r\n        if not r[0]:  # 不查询任何属性\r\n            arg_str = 'not(@*)'\r\n\r\n        else:\r\n            ignore = True if args[k] == '@!' else False  # 是否去除某个属性\r\n            if len_r != 3:  # 只有属性名没有属性内容，查询是否存在该属性\r\n                if r[0] in ('tag()', 't()'):\r\n                    continue\r\n                arg_str = 'normalize-space(text())' if r[0] in ('text()', 'tx()') else f'@{r[0]}'\r\n\r\n            elif len_r == 3:  # 属性名和内容都有\r\n                if r[0] in ('tag()', 't()'):\r\n                    if ignore:\r\n                        tags.append(f'not(name()=\"{r[2]}\")')\r\n                        tags_connect = ' and '\r\n                    else:\r\n                        tags.append(f'name()=\"{r[2]}\"')\r\n                    continue\r\n\r\n                symbol = r[1]\r\n                if r[0] in ('text()', 'tx()'):\r\n                    arg = '.'\r\n                    txt = r[2]\r\n                else:\r\n                    arg = f'@{r[0]}'\r\n                    txt = r[2]\r\n\r\n                if symbol == '=':\r\n                    arg_str = f'{arg}={_quotes_escape(txt)}'\r\n\r\n                elif symbol == ':':\r\n                    arg_str = f'contains({arg},{_quotes_escape(txt)})'\r\n\r\n                elif symbol == '^':\r\n                    arg_str = f'starts-with({arg},{_quotes_escape(txt)})'\r\n\r\n                elif symbol == '$':\r\n                    arg_str = (f'substring({arg}, string-length({arg}) - string-length('\r\n                               f'{_quotes_escape(txt)}) +1) = {_quotes_escape(txt)}')\r\n\r\n                else:\r\n                    raise LocatorError(_S._lang.INCORRECT_SIGN_, symbol)\r\n\r\n            if arg_str and ignore:\r\n                arg_str = f'not({arg_str})'\r\n\r\n        if arg_str:\r\n            arg_list.append(arg_str)\r\n\r\n    arg_str = ' and '.join(arg_list) if _and else ' or '.join(arg_list)\r\n    if tags:\r\n        condition = f' and ({arg_str})' if arg_str else ''\r\n        arg_str = f'({tags_connect.join(tags)}){condition}'\r\n\r\n    return 'xpath', f'//*[{arg_str}]' if arg_str else f'//*'\r\n\r\n\r\ndef _quotes_escape(search_str: str) -> str:\r\n    \"\"\"将\"转义，不知何故不能直接用 斜杠 来转义\r\n    :param search_str: 查询字符串\r\n    :return: 把\"转义后的字符串\r\n    \"\"\"\r\n    if '\"' not in search_str:\r\n        return f'\"{search_str}\"'\r\n\r\n    parts = search_str.split('\"')\r\n    parts_num = len(parts)\r\n    search_str = 'concat('\r\n\r\n    for key, i in enumerate(parts):\r\n        search_str += f'\"{i}\"'\r\n        search_str += ',' + '\\'\"\\',' if key < parts_num - 1 else ''\r\n\r\n    search_str += ',\"\")'\r\n    return search_str\r\n\r\n\r\ndef _make_multi_css_str(tag: str, text: str) -> tuple:\r\n    \"\"\"生成多属性查找的css selector语句\r\n    :param tag: 标签名\r\n    :param text: 待处理的字符串\r\n    :return: css selector字符串\r\n    \"\"\"\r\n    arg_list = []\r\n    args = split(r'(@!|@@|@\\|)', text)[1:]\r\n    if '@@' in args and '@|' in args:\r\n        raise LocatorError(_S._lang.SYMBOL_CONFLICT, LOCATOR=text)\r\n    _and = '@|' not in args\r\n\r\n    for k in range(0, len(args) - 1, 2):\r\n        r = split(r'([:=$^])', args[k + 1], maxsplit=1)\r\n        if not r[0] or r[0].startswith(('text()', 'tx()')):\r\n            return _make_multi_xpath_str(tag, text)\r\n\r\n        arg_str = ''\r\n        len_r = len(r)\r\n        ignore = True if args[k] == '@!' else False  # 是否去除某个属性\r\n        if len_r != 3:  # 只有属性名没有属性内容，查询是否存在该属性\r\n            if r[0] in ('tag()', 't()'):\r\n                continue\r\n            arg_str = f'[{r[0]}]'\r\n\r\n        elif len_r == 3:  # 属性名和内容都有\r\n            if r[0] in ('tag()', 't()'):\r\n                if tag == '*':\r\n                    tag = f':not({r[2].lower()})' if ignore else f'{r[2]}'\r\n                else:\r\n                    tag += f',:not({r[2].lower()})' if ignore else f',{r[2]}'\r\n                continue\r\n\r\n            d = {'=': '', '^': '^', '$': '$', ':': '*'}\r\n            arg_str = f'[{r[0]}{d[r[1]]}={css_trans(r[2])}]'\r\n\r\n        if arg_str and ignore:\r\n            arg_str = f':not({arg_str})'\r\n\r\n        if arg_str:\r\n            arg_list.append(arg_str)\r\n\r\n    if _and:\r\n        return 'css selector', f'{tag}{\"\".join(arg_list)}'\r\n\r\n    return 'css selector', f'{tag}{(\",\" + tag).join(arg_list)}'\r\n\r\n\r\ndef _make_single_css_str(tag: str, text: str) -> tuple:\r\n    \"\"\"生成单属性css selector语句\r\n    :param tag: 标签名\r\n    :param text: 待处理的字符串\r\n    :return: css selector字符串\r\n    \"\"\"\r\n    if text == '@' or text.startswith(('@text()', '@tx()')):\r\n        return _make_single_xpath_str(tag, text)\r\n\r\n    r = split(r'([:=$^])', text, maxsplit=1)\r\n    if r[0] in ('@tag()', '@t()'):\r\n        return 'css selector', r[2]\r\n\r\n    if len(r) == 3:\r\n        d = {'=': '', '^': '^', '$': '$', ':': '*'}\r\n        arg_str = f'[{r[0][1:]}{d[r[1]]}={css_trans(r[2])}]'\r\n\r\n    else:\r\n        arg_str = f'[{css_trans(r[0][1:])}]'\r\n\r\n    return 'css selector', f'{tag}{arg_str}'\r\n\r\n\r\ndef translate_loc(loc):\r\n    if len(loc) != 2:\r\n        raise LocatorError(_S._lang.LOC_LEN, LOCATOR=loc)\r\n\r\n    loc_by = By.XPATH\r\n    loc_0 = loc[0].lower()\r\n\r\n    if loc_0 == By.XPATH:\r\n        loc_str = loc[1]\r\n\r\n    elif loc_0 == By.CSS_SELECTOR:\r\n        loc_by = loc_0\r\n        loc_str = loc[1]\r\n\r\n    elif loc_0 == By.ID:\r\n        loc_str = f'//*[@id=\"{loc[1]}\"]'\r\n\r\n    elif loc_0 == By.CLASS_NAME:\r\n        loc_str = f'//*[@class=\"{loc[1]}\"]'\r\n\r\n    elif loc_0 == By.LINK_TEXT:\r\n        loc_str = f'//a[text()=\"{loc[1]}\"]'\r\n\r\n    elif loc_0 == By.NAME:\r\n        loc_str = f'//*[@name=\"{loc[1]}\"]'\r\n\r\n    elif loc_0 == By.TAG_NAME:\r\n        loc_str = f'//*[name()=\"{loc[1]}\"]'\r\n\r\n    elif loc_0 == By.PARTIAL_LINK_TEXT:\r\n        loc_str = f'//a[contains(text(),\"{loc[1]}\")]'\r\n\r\n    else:\r\n        raise LocatorError(_S._lang.INVALID_LOC, LOCATOR=loc)\r\n\r\n    return loc_by, loc_str\r\n\r\n\r\ndef translate_css_loc(loc):\r\n    if len(loc) != 2:\r\n        raise LocatorError(_S._lang.LOC_LEN, LOCATOR=loc)\r\n\r\n    loc_by = By.CSS_SELECTOR\r\n    loc_0 = loc[0].lower()\r\n    if loc_0 == By.XPATH:\r\n        loc_by = By.XPATH\r\n        loc_str = loc[1]\r\n\r\n    elif loc_0 == By.CSS_SELECTOR:\r\n        loc_by = loc_0\r\n        loc_str = loc[1]\r\n\r\n    elif loc_0 == By.ID:\r\n        loc_str = f'#{css_trans(loc[1])}'\r\n\r\n    elif loc_0 == By.CLASS_NAME:\r\n        loc_str = f'.{css_trans(loc[1])}'\r\n\r\n    elif loc_0 == By.LINK_TEXT:\r\n        loc_by = By.XPATH\r\n        loc_str = f'//a[text()=\"{css_trans(loc[1])}\"]'\r\n\r\n    elif loc_0 == By.NAME:\r\n        loc_str = f'*[@name={css_trans(loc[1])}]'\r\n\r\n    elif loc_0 == By.TAG_NAME:\r\n        loc_str = loc[1]\r\n\r\n    elif loc_0 == By.PARTIAL_LINK_TEXT:\r\n        loc_by = By.XPATH\r\n        loc_str = f'//a[contains(text(),\"{loc[1]}\")]'\r\n\r\n    else:\r\n        raise LocatorError(_S._lang.INVALID_LOC, LOCATOR=loc)\r\n\r\n    return loc_by, loc_str\r\n\r\n\r\ndef css_trans(txt):\r\n    c = ('!', '\"', '#', '$', '%', '&', '\\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@',\r\n         '[', '\\\\', ']', '^', '`', ',', '{', '|', '}', '~', ' ')\r\n    return ''.join([fr'\\{i}' if i in c else i for i in txt])\r\n\r\n\r\ndef _preprocess(loc):\r\n    \"\"\"对缩写进行处理，替换回完整写法\"\"\"\r\n    if loc.startswith('.'):\r\n        if loc.startswith(('.=', '.:', '.^', '.$')):\r\n            loc = loc.replace('.', '@class', 1)\r\n        else:\r\n            loc = loc.replace('.', '@class=', 1)\r\n\r\n    elif loc.startswith('#'):\r\n        if loc.startswith(('#=', '#:', '#^', '#$')):\r\n            loc = loc.replace('#', '@id', 1)\r\n        else:\r\n            loc = loc.replace('#', '@id=', 1)\r\n\r\n    elif loc.startswith(('t:', 't=')):\r\n        loc = f'tag:{loc[2:]}'\r\n\r\n    elif loc.startswith(('tx:', 'tx=', 'tx^', 'tx$')):\r\n        loc = f'text{loc[2:]}'\r\n\r\n    elif loc.startswith(('c:', 'c=')):\r\n        loc = f'css:{loc[2:]}'\r\n\r\n    elif loc.startswith(('x:', 'x=')):\r\n        loc = f'xpath:{loc[2:]}'\r\n\r\n    return loc\r\n"
  },
  {
    "path": "DrissionPage/_functions/locator.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Union\r\n\r\n\r\ndef locator_to_tuple(loc: str) -> dict:\r\n    \"\"\"解析定位字符串生成dict格式数据\r\n    :param loc: 待处理的字符串\r\n    :return: 格式： {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]}\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef is_str_loc(text: str) -> bool:\r\n    \"\"\"返回text是否定位符\"\"\"\r\n    ...\r\n\r\n\r\ndef is_selenium_loc(loc: tuple) -> bool:\r\n    \"\"\"返回tuple是否selenium的定位符\"\"\"\r\n    ...\r\n\r\n\r\ndef get_loc(loc: Union[tuple, str],\r\n            translate_css: bool = False,\r\n            css_mode: bool = False) -> tuple:\r\n    \"\"\"接收本库定位语法或selenium定位元组，转换为标准定位元组，可翻译css selector为xpath\r\n    :param loc: 本库定位语法或selenium定位元组\r\n    :param translate_css: 是否翻译css selector为xpath，用于相对定位\r\n    :param css_mode: 是否尽量用css selector方式\r\n    :return: DrissionPage定位元组\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef str_to_xpath_loc(loc: str) -> tuple:\r\n    \"\"\"处理元素查找语句\r\n    :param loc: 查找语法字符串\r\n    :return: 匹配符元组\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef str_to_css_loc(loc: str) -> tuple:\r\n    \"\"\"处理元素查找语句\r\n    :param loc: 查找语法字符串\r\n    :return: 匹配符元组\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef translate_loc(loc: tuple) -> tuple:\r\n    \"\"\"把By类型的loc元组转换为css selector或xpath类型的\r\n    :param loc: By类型的loc元组\r\n    :return: css selector或xpath类型的loc元组\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef translate_css_loc(loc: tuple) -> tuple:\r\n    \"\"\"把By类型的loc元组转换为css selector或xpath类型的\r\n    :param loc: By类型的loc元组\r\n    :return: css selector或xpath类型的loc元组\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef css_trans(txt: str) -> str:\r\n    \"\"\"css字符串中特殊字符转义\r\n    :param txt: 要处理的文本\r\n    :return: 处理后的文本\r\n    \"\"\"\r\n    ...\r\n"
  },
  {
    "path": "DrissionPage/_functions/settings.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\n\r\nfrom .texts import get_txt_class\r\n\r\n\r\nclass Settings(object):\r\n    raise_when_ele_not_found = False\r\n    raise_when_click_failed = False\r\n    raise_when_wait_failed = False\r\n    singleton_tab_obj = True\r\n    cdp_timeout = 30\r\n    browser_connect_timeout = 30\r\n    auto_handle_alert = None\r\n    _lang = get_txt_class(None)\r\n    suffixes_list = str(Path(__file__).parent.absolute() / 'suffixes.dat').replace('\\\\', '/')\r\n\r\n    @classmethod\r\n    def set_raise_when_ele_not_found(cls, on_off=True):\r\n        cls.raise_when_ele_not_found = on_off\r\n        return cls\r\n\r\n    @classmethod\r\n    def set_raise_when_click_failed(cls, on_off=True):\r\n        cls.raise_when_click_failed = on_off\r\n        return cls\r\n\r\n    @classmethod\r\n    def set_raise_when_wait_failed(cls, on_off=True):\r\n        cls.raise_when_wait_failed = on_off\r\n        return cls\r\n\r\n    @classmethod\r\n    def set_singleton_tab_obj(cls, on_off=True):\r\n        cls.singleton_tab_obj = on_off\r\n        return cls\r\n\r\n    @classmethod\r\n    def set_cdp_timeout(cls, second):\r\n        cls.cdp_timeout = second\r\n        return cls\r\n\r\n    @classmethod\r\n    def set_browser_connect_timeout(cls, second):\r\n        cls.browser_connect_timeout = second\r\n        return cls\r\n\r\n    @classmethod\r\n    def set_auto_handle_alert(cls, accept=True):\r\n        cls.auto_handle_alert = accept\r\n        return cls\r\n\r\n    @classmethod\r\n    def set_language(cls, code):\r\n        cls._lang = get_txt_class(code)\r\n        return cls\r\n\r\n    @classmethod\r\n    def set_suffixes_list(cls, path):\r\n        cls.suffixes_list = str(Path(path).absolute()).replace('\\\\', '/')\r\n        return cls\r\n"
  },
  {
    "path": "DrissionPage/_functions/settings.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom pathlib import Path\nfrom typing import Optional, Union, Literal\n\nfrom .texts import Texts\n\n\nclass Settings(object):\n    raise_when_ele_not_found: bool = ...\n    raise_when_click_failed: bool = ...\n    raise_when_wait_failed: bool = ...\n    singleton_tab_obj: bool = ...\n    cdp_timeout: float = ...\n    browser_connect_timeout: float = ...\n    auto_handle_alert: Optional[bool] = ...\n    _lang: Texts = ...\n    suffixes_list: str = ...\n\n    @classmethod\n    def set_raise_when_ele_not_found(cls, on_off: bool = True) -> Settings:\n        \"\"\"设置找不到元素时是否立即抛出异常\n        :param on_off: bool表示开或关\n        :return: None\n        \"\"\"\n        ...\n\n    @classmethod\n    def set_raise_when_click_failed(cls, on_off: bool = True) -> Settings:\n        \"\"\"设置点击失败时是否立即抛出异常\n        :param on_off: bool表示开或关\n        :return: None\n        \"\"\"\n        ...\n\n    @classmethod\n    def set_raise_when_wait_failed(cls, on_off: bool = True) -> Settings:\n        \"\"\"设置等待失败时是否立即抛出异常\n        :param on_off: bool表示开或关\n        :return: None\n        \"\"\"\n        ...\n\n    @classmethod\n    def set_singleton_tab_obj(cls, on_off: bool = True) -> Settings:\n        \"\"\"设置是否开启tab单例模式\n        :param on_off: bool表示开或关\n        :return: None\n        \"\"\"\n        ...\n\n    @classmethod\n    def set_cdp_timeout(cls, second: float) -> Settings:\n        \"\"\"设置csp执行超时时间（秒）\n        :param second: 超时秒数\n        :return: None\n        \"\"\"\n        ...\n\n    @classmethod\n    def set_browser_connect_timeout(cls, second: float) -> Settings:\n        \"\"\"设置等待浏览器连接超时时间（秒）\n        :param second: 超时秒数\n        :return: None\n        \"\"\"\n        ...\n\n    @classmethod\n    def set_auto_handle_alert(cls, accept: Optional[bool] = True) -> Settings:\n        \"\"\"设置是否自动处理js弹出信息\n        :param accept: None为不处理，True为接受，False为取消\n        :return: None\n        \"\"\"\n        ...\n\n    @classmethod\n    def set_language(cls, code: Literal['zh_cn', 'en']) -> Settings:\n        \"\"\"设置报错和提示信息使用的语言\n        :param code: 表示语言的文本'zh_cn', 'en'\n        :return: None\n        \"\"\"\n        ...\n\n    @classmethod\n    def set_suffixes_list(cls, path: Union[str, Path]) -> Settings:\n        \"\"\"设置用于识别域名后缀的文件路径\n        :param path: 文件路径\n        :return: None\n        \"\"\"\n        ...\n"
  },
  {
    "path": "DrissionPage/_functions/texts.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom locale import getlocale\n\nfrom ..version import __version__\n\n\ndef get_txt_class(lang=None):\n    languages = {\n        'zh_cn': Texts,\n        'cn': Texts,\n        'en': English,\n    }\n    if lang is None:\n        locale = str(getlocale()[0]).lower()\n        if locale.startswith('zh') or 'chinese' in locale:\n            lang = 'zh_cn'\n        elif locale.startswith('en') or 'english' in locale:\n            lang = 'en'\n        else:\n            lang = 'zh_cn'\n    else:\n        lang = lang.lower()\n    lang = languages.get(lang, None)\n    if lang is None:\n        raise ValueError(f'lang must be one of {languages.keys()}')\n    return lang\n\n\nclass Texts(object):\n    # --------- 参数名 ---------\n    VERSION = '版本'\n    INFO = '详情'\n    METHOD = '方法'\n    ARGS = '参数'\n    ARG = '参数'\n    BROWSER_VER = '浏览器版本'\n    PATH = '路径'\n    VALUE = '值'\n    ALLOW_VAL = '允许值'\n    CURR_VAL = '当前值'\n    ALLOW_TYPE = '允许类型'\n    CURR_TYPE = '当前类型'\n    TIP = '提示'\n    ADDRESS = '地址'\n    LOCATOR = '定位符'\n    ALL_TABS = '所有标签页'\n\n    # --------- 异常默认文本 ---------\n    ELEMENTNOTFOUNDERROR = '没有找到元素。'\n    ALERTEXISTSERROR = '存在未处理的提示框。'\n    CONTEXTLOSTERROR = '页面被刷新，请操作前尝试等待页面刷新或加载完成。'\n    ELEMENTLOSTERROR = '元素对象已失效。可能是页面整体刷新，或js局部刷新把元素替换或去除了。'\n    CDPERROR = '方法调用错误。'\n    PAGEDISCONNECTEDERROR = '与页面的连接已断开。'\n    JAVASCRIPTERROR = 'JavaScript运行错误。'\n    NORECTERROR = '该元素没有位置及大小。'\n    BROWSERCONNECTERROR = '浏览器连接失败。'\n    NORESOURCEERROR = '该元素无可保存的内容或保存失败。'\n    CANNOTCLICKERROR = '该元素无法滚动到视口或被遮挡，无法点击。'\n    GETDOCUMENTERROR = '获取文档失败。'\n    WAITTIMEOUTERROR = '等待失败。'\n    INCORRECTURLERROR = '无效的url。'\n    LOCATORERROR = '定位符格式不正确。',\n    STORAGEERROR = '无法操作当前存储数据。'\n    COOKIEFORMATERROR = 'cookie格式不正确。'\n    TARGETNOTFOUNDERROR = '找不到指定页面。'\n\n    # --------- 异常信息 ---------\n    NO_AVAILABLE_PORT_FOUND = '未找到可用端口。'\n    WIN_SYS_ONLY = '该方法只能在Windows系统使用。'\n    NEED_LIB_ = '请先安装{}。'\n    INVALID_URL = '无效的url，也许要加上\"http://\"？'\n    INVALID_HEADER_NAME = '无效的header项名。'\n    NOT_A_FUNCTION = '传入的js无法解析成函数。'\n    METHOD_NOT_FOUND = '没有找到对应功能，方法错误或你的浏览器太旧。'\n    NO_RESPONSE = '超时，可能是浏览器卡了。'\n    UNKNOWN_ERR = '出现未知错误。'\n    FEEDBACK = ('出现这个错误可能意味着程序有bug，请把错误信息和重现方法告知作者，谢谢。\\n'\n                '报告网站: https://gitee.com/g1879/DrissionPage/issues')\n    INI_NOT_FOUND = 'ini文件不存在。'\n    EXT_NOT_FOUND = '插件路径不存在。'\n    WAITING_FAILED_ = '等待{}失败（等待{}秒）。'\n    GET_OBJ_FAILED = '获取对象失败。'\n    INCORRECT_VAL_ = '{}参数值错误。'\n    INCORRECT_TYPE_ = '{}参数类型错误。'\n    CONNECT_ERR = '连接异常。'\n    BROWSER_CONNECT_ERR1_ = '浏览器连接失败，请检查{}端口是否浏览器，且已添加\"--remote-debugging-port={}\"启动项。'\n    BROWSER_CONNECT_ERR2 = '浏览器连接失败，请确认浏览器已启动。'\n    BROWSER_EXE_NOT_FOUND = '无法找到浏览器可执行文件路径，请手动配置。'\n    BROWSER_NOT_FOUND = '未找到浏览器。'\n    BROWSER_NOT_EXIST = '浏览器未开启或已关闭。'\n    BROWSER_DISCONNECTED = '浏览器已关闭或链接已断开。'\n    BROWSER_NOT_FOR_CONTROL = '浏览器版本太旧或此浏览器不支持接管。'\n    UNSUPPORTED_CSS_SYNTAX = '此css selector语法不受支持，请换成xpath。'\n    UNSUPPORTED_ARG_TYPE_ = '不支持参数{}的类型: {}。'\n    UPGRADE_WS = '请升级websocket-client库。'\n    INI_NOT_SET = 'ini_path未设置。'\n    INVALID_XPATH_ = '无效的xpath语句: {}'\n    INVALID_CSS_ = '无效的css selector语句: {}'\n    INDEX_FORMAT = '序号必须是数字或切片。'\n    LOC_NOT_FOR_FRAME = '该定位符不是指向frame元素。'\n    NEED_DOWNLOAD_PATH = '此功能需显式设置下载路径。'\n    GET_WINDOW_SIZE_FAILED = '获取窗口信息失败。'\n    SET_FAILED_ = '{}设置失败。'\n    NOT_LISTENING = '监听未启动或已停止。'\n    NOT_BLOB = '该链接非blob类型。'\n    CANNOT_INPUT_FILE = '该输入框无法接管，请改用对<input>元素输入路径的方法设置。'\n    NO_SUCH_KEY_ = '没有这个按键: {}'\n    NO_NEW_TAB = '没有等到新标签页。'\n    NO_SUCH_TAB = '没有找到指定标签页。'\n    NEED_DOMAIN = '需设置domain或url值。如设置url值，需以http开头。'\n    NEED_DOMAIN2 = 'cookie必须带有\"domain\"或\"url\"字段。'\n    NEED_ARG_ = '{}必须设置。'\n    SAVE_PATH_MUST_BE_FOLDER = 'save_path必须为文件夹。'\n    GET_PDF_FAILED = '保存失败，可能浏览器版本不支持。'\n    GET_BLOB_FAILED = '无法获取该资源。'\n    NO_SRC_ATTR = '元素没有src值或该值为空。'\n    D_MODE_ONLY = 'url、domain、path参数只有d模式下有效。'\n    S_MODE_ONLY = '以下参数在s模式下才会生效:'\n    STATUS_CODE_ = '状态码: {}'\n    TAB_OBJ_EXISTS = '该标签页已有非MixTab版本，如需多对象共用标签页请设置Settings.set_singleton_tab_obj(False)。'\n    ONLY_ENGLISH = '转换成视频仅支持英文路径和文件名。'\n    SELECT_ONLY = 'select方法只能在<select>元素使用。'\n    MULTI_SELECT_ONLY = '只能在多选菜单执行此操作。'\n    OPTION_NOT_FOUND = '没有找到指定选项。'\n    STR_FOR_SINGLE_SELECT = '单选列表只能传入str格式。'\n    JS_RUNTIME_ERR = 'js运行环境出错。'\n    TIMEOUT_ = '{}超时（等待{}秒）'\n    JS_RESULT_ERR = 'js结果解析错误。'\n    S_MODE_GET_FAILED = 's模式访问失败，请设置go=False，自行构造连接参数进行访问。'\n    ZERO_PAGE_SIZE = '页面大小为0，请尝试等待页面加载完成。'\n    CONTENT_IS_EMPTY = '返回内容为空。'\n    FIND_ELE_ERR = '查找元素异常。'\n    SYMBOL_CONFLICT = '@@和@|不能同时出现在一个定位语句中。'\n    INVALID_LOC = '无法识别的定位符。'\n    LOC_LEN = '定位符长度必须为2。'\n    INCORRECT_SIGN_ = '符号不正确: {}'\n    DOMAIN_NOT_SET = '未设置域名，请设置cookie的domain参数或先访问一个网站。'\n    PLUGIN_NEED_FOLDER = '插件需解压为文件夹：{}'\n    NEED_FILES_OR_TEXT_ARG = 'files参数和text参数必须至少输入一个。'\n\n    # --------- 参数内容 ---------\n    ELE_DISPLAYED = '元素显示'\n    ELE_LOADED = '元素加载'\n    PAGE_LOADED = '页面加载'\n    NEW_TAB = '新标签页'\n    DATA_PACKET = '数据包'\n    ELE_HIDDEN_DEL = '元素隐藏或被删除'\n    ELE_DEL = '元素被删除'\n    ELE_HAS_RECT = '元素拥有大小及位置'\n    ELE_HIDDEN = '元素隐藏'\n    ELE_CLICKABLE = '元素可点击'\n    ELE_COVERED = '元素被覆盖'\n    ELE_NOT_COVERED = '元素不被覆盖'\n    ELE_AVAILABLE = '元素可用'\n    ELE_NOT_AVAILABLE = '元素不可用'\n    ELE_STOP_MOVING = '元素停止运动'\n    ELE_STATE_CHANGED_ = '等待元素状态改变失败（等待{}秒）。'\n    RUN_BY_ADMIN = '尝试用管理员权限运行。'\n    BROWSER_CONNECT_ERR_INFO = ('\\n1、用户文件夹没有和已打开的浏览器冲突\\n'\n                                '2、如为无界面系统，请添加\\'--headless=new\\'启动参数\\n'\n                                '3、如果是Linux系统，尝试添加\\'--no-sandbox\\'启动参数\\n'\n                                '可使用ChromiumOptions设置端口和用户文件夹路径。')\n    # ALLOW\n    LOC_OR_IND = '定位符（str或长度为2的tuple）或序号'\n    STR_ONLY = 'str格式且不支持xpath和css形式'\n    LOC_FORMAT = 'str或长度为2的tuple'\n    ELE_OR_LOC = '定位符（str或长度为2的tuple）或元素'\n    FRAME_LOC_FORMAT = '定位符、iframe序号、id、name、ChromiumFrame对象'\n    SET_DOWNLOAD_PATH = '使用set.download_path()方法、配置对象或ini文件均可。'\n    SET_WINDOW_NORMAL = '浏览器全屏或最小化状态时请先调用set.window.normal()恢复正常状态。'\n    HTML_ELE_TYPE = '元素、页面对象或html文本'\n    ELE_LOC_FORMAT = '(x, y)格式坐标，或ChromiumElement对象'\n    IP_OR_OPTIONS = 'ip:port格式字符串或ChromiumOptions类型'\n\n    TAB_OR_ID = '标签页对象或id'\n    RUN_JS = '执行js'\n    PAGE_CONNECT = '页面连接'\n    NEW_ELE_INFO = 'str格式的html文本，或tuple格式(tag, {name: value})。'\n    DICT_TO_NEW_ELE = '此网页不支持html格式新建元素，请用dict传入html_or_info参数。'\n\n    # --------- print ---------\n    RETRY = '重试'\n    OPTIONS_HAVE_SAVED = '配置已保存到文件'\n    AUTO_LOAD_TIP = '以后程序可自动从文件加载配置'\n    STOP_RECORDING = '停止录制'\n    START_RECORD = '开始录制'\n    CHOOSE_RECORD_TARGET = '请手动选择要录制的目标。'\n    UNSUPPORTED_USER_PROXY = '你似乎在设置使用账号密码的代理，暂时不支持这种代理，可自行用插件实现需求。'\n    UNSUPPORTED_SOCKS_PROXY = '你似乎在设置使用socks代理，暂时不支持这种代理，可自行用插件实现需求。'\n    NOT_SUPPORT_DOWNLOAD = '浏览器版本太低无法使用下载管理功能。'\n    FILE_NAME = '文件名'\n    FOLDER_PATH = '目录路径'\n    UNKNOWN = '未知'\n    DOWNLOAD_COMPLETED = '下载完成'\n    COMPLETED_AND_RENAME = '完成并重命名'\n    OVERWROTE = '已覆盖'\n    DOWNLOAD_CANCELED = '下载取消'\n    SKIPPED = '已跳过'\n\n    @classmethod\n    def get(cls, item):\n        return getattr(cls, item, item) if isinstance(item, str) and item.isupper() else item\n\n    @classmethod\n    def join(cls, *args, **kwargs):\n        kwargs['VERSION'] = __version__\n        main = ('\\n' + args[0].format(*[i for i in args[1:]])) if args else ''\n        msg = ('\\n' + '\\n'.join([f'{cls.get(k)}: {v}' for k, v in kwargs.items()]))\n        return f'{main}{msg}'\n\n\nclass English(Texts):\n    # --------- 参数名 ---------\n    VERSION = 'Version'\n    INFO = 'Information'\n    METHOD = 'Method'\n    ARGS = 'Arguments'\n    ARG = 'Argument'\n    BROWSER_VER = 'Browser Version'\n    PATH = 'Path'\n    VALUE = 'Value'\n    ALLOW_VAL = 'Allow Value'\n    CURR_VAL = 'Current Value'\n    ALLOW_TYPE = 'Allow Type'\n    CURR_TYPE = 'Current Type'\n    TIP = 'Tip'\n    ADDRESS = 'Address'\n    LOCATOR = 'Locator'\n    ALL_TABS = 'All Tabs'\n\n    # --------- 异常默认文本 ---------\n    ELEMENTNOTFOUNDERROR = 'No element found.'\n    ALERTEXISTSERROR = 'An unprocessed dialog box exists.'\n    CONTEXTLOSTERROR = 'The page is refreshed. Please wait until the page is refreshed or loaded.'\n    ELEMENTLOSTERROR = ('The element object is invalid. This may be an overall refresh of the page, or a partial js '\n                        'refresh to replace or remove elements.')\n    CDPERROR = 'Method call error.'\n    PAGEDISCONNECTEDERROR = 'The connection to the page has been disconnected.'\n    JAVASCRIPTERROR = 'JavaScript running error.'\n    NORECTERROR = 'This element has no location or size.'\n    BROWSERCONNECTERROR = 'The browser connection fails.'\n    NORESOURCEERROR = 'The element has no saved content or failed to save.'\n    CANNOTCLICKERROR = 'The element does not scroll to the viewport or is blocked and cannot be clicked.'\n    GETDOCUMENTERROR = 'Failed to obtain the document. Procedure'\n    WAITTIMEOUTERROR = 'Wait for failure.'\n    INCORRECTURLERROR = 'Invalid url.'\n    LOCATORERROR = 'Invalid locator format.',\n    STORAGEERROR = 'Cannot manipulate the currently stored data.'\n    COOKIEFORMATERROR = 'The cookie format is incorrect.'\n    TARGETNOTFOUNDERROR = 'The specified page cannot be found.'\n    PLUGIN_NEED_FOLDER = 'The plugin needs to be decompressed into a folder：{}'\n    NEED_FILES_OR_TEXT_ARG = 'At least one of the \"files\" parameter and the \"text\" parameter must be entered.'\n\n    # --------- 异常信息 ---------\n    NO_AVAILABLE_PORT_FOUND = 'No available port found.'\n    WIN_SYS_ONLY = 'This method can be used only on Windows.'\n    NEED_LIB_ = 'Please install {} first.'\n    INVALID_URL = 'Invalid url, maybe add \"http://\"?'\n    INVALID_HEADER_NAME = 'Invalid header name.'\n    NOT_A_FUNCTION = 'The passed js cannot be parsed into a function.'\n    METHOD_NOT_FOUND = 'The function is not found, the method is wrong or your browser is too old.'\n    NO_RESPONSE = 'Time out. Maybe the browser is stuck.'\n    UNKNOWN_ERR = 'An unknown error occurred.'\n    FEEDBACK = ('This error may mean that there is a bug in the program, please inform the author of the error '\n                'message and how to reproduce it, thank you.\\n'\n                'Report url: https://gitee.com/g1879/DrissionPage/issues')\n    INI_NOT_FOUND = 'The ini file does not exist.'\n    EXT_NOT_FOUND = 'The plug-in path does not exist.'\n    WAITING_FAILED_ = 'Wait for {} failed ({} seconds).'\n    GET_OBJ_FAILED = 'Failed to obtain the object. Procedure'\n    INCORRECT_VAL_ = 'The {} parameter value is incorrect.'\n    INCORRECT_TYPE_ = 'The {} parameter type is incorrect.'\n    CONNECT_ERR = 'The connection is abnormal.'\n    BROWSER_CONNECT_ERR1_ = ('Browser connect failed, check whether the port {} is a browser and '\n                             '\"--remote-debugging-port={}\" startup item is added.')\n    BROWSER_CONNECT_ERR2 = 'The browser connection failed. Please ensure that the browser is started.'\n    BROWSER_EXE_NOT_FOUND = 'The browser executable file path cannot be found. Please configure it manually.'\n    BROWSER_NOT_FOUND = 'Browser not found.'\n    BROWSER_NOT_EXIST = 'The browser is not started or closed.'\n    BROWSER_DISCONNECTED = 'The browser is closed or the link is broken.'\n    BROWSER_NOT_FOR_CONTROL = 'The browser version is too old or this browser does not support takeover.'\n    UNSUPPORTED_CSS_SYNTAX = 'This css selector syntax is not supported, please replace it with xpath.'\n    UNSUPPORTED_ARG_TYPE_ = 'The type of parameter {} is not supported: {}.'\n    UPGRADE_WS = 'Upgrade the websocket-client library.'\n    INI_NOT_SET = 'ini_path is not set.'\n    INVALID_XPATH_ = 'Invalid xpath statement: {}'\n    INVALID_CSS_ = 'Invalid css selector statement: {}'\n    INDEX_FORMAT = 'The serial number must be a number or slice.'\n    LOC_NOT_FOR_FRAME = 'This locator does not point to the frame element.'\n    NEED_DOWNLOAD_PATH = 'You need to explicitly set the download path for this function.'\n    GET_WINDOW_SIZE_FAILED = 'Failed to obtain window information. Procedure'\n    SET_FAILED_ = 'The argument {} setting failed.'\n    NOT_LISTENING = 'Listening is not started or stopped.'\n    NOT_BLOB = 'The link is not of blob type.'\n    CANNOT_INPUT_FILE = 'This input field cannot handle. Instead, set the input path to the <input> element.'\n    NO_SUCH_KEY_ = 'There is no button: {}'\n    NO_NEW_TAB = 'Failed to wait for new tab.'\n    NO_SUCH_TAB = 'The specified tab was not found.'\n    NEED_DOMAIN = 'You need to set a domain or url value. If the url value is set, it must start with http.'\n    NEED_DOMAIN2 = 'The cookie must have a \"domain\" or \"url\" field.'\n    NEED_ARG_ = '{} must be set.'\n    SAVE_PATH_MUST_BE_FOLDER = 'save_path must be a folder.'\n    GET_PDF_FAILED = 'The save fails because the browser version may not support it.'\n    GET_BLOB_FAILED = 'The resource cannot be retrieved.'\n    NO_SRC_ATTR = 'The element does not have a src value or the value is empty.'\n    D_MODE_ONLY = 'The url, domain, and path parameters are valid only in d mode.'\n    S_MODE_ONLY = 'The following parameters take effect only in s mode:'\n    STATUS_CODE_ = 'Status Code: {}'\n    TAB_OBJ_EXISTS = ('There is already a non-mixtab version of this tab. If multiple objects are common, '\n                      'use Settings.set_singleton_tab_obj(False).')\n    ONLY_ENGLISH = 'Only English path and file name are supported when converting to video.'\n    SELECT_ONLY = 'The select method can only be used on <select> elements.'\n    MULTI_SELECT_ONLY = 'You can only do this from the multiple select element.'\n    OPTION_NOT_FOUND = 'The specified option was not found.'\n    STR_FOR_SINGLE_SELECT = 'Single-choice select element can only be passed in str format.'\n    JS_RUNTIME_ERR = 'The js runtime environment is faulty.'\n    TIMEOUT_ = 'Wait for {} timeout ({} seconds).'\n    JS_RESULT_ERR = 'js result parsing error.'\n    S_MODE_GET_FAILED = 'S mode access fails, please set go=False and construct connection parameters for access.'\n    ZERO_PAGE_SIZE = 'The page size is 0, please try to wait for the page to load.'\n    CONTENT_IS_EMPTY = 'The returned content is empty.'\n    FIND_ELE_ERR = 'Find element exceptions.'\n    SYMBOL_CONFLICT = '\"@@\" and \"@|\" cannot appear in the same location statement.'\n    INVALID_LOC = 'Unrecognized locator.'\n    LOC_LEN = 'The length of the locator must be 2.'\n    INCORRECT_SIGN_ = 'Incorrect symbol: {}'\n    DOMAIN_NOT_SET = 'No domain name is set, please set the domain parameter of the cookie or visit a website first.'\n\n    # --------- 参数内容 ---------\n    ELE_DISPLAYED = 'element display'\n    ELE_LOADED = 'element loaded'\n    PAGE_LOADED = 'page loaded'\n    NEW_TAB = 'new tab'\n    DATA_PACKET = 'data packet'\n    ELE_HIDDEN_DEL = 'element is hidden or deleted'\n    ELE_DEL = 'element be deleted'\n    ELE_HAS_RECT = 'element has size and position'\n    ELE_HIDDEN = 'element hidden'\n    ELE_CLICKABLE = 'element clickable'\n    ELE_COVERED = 'element is covered'\n    ELE_NOT_COVERED = 'element is not covered'\n    ELE_AVAILABLE = 'element available'\n    ELE_NOT_AVAILABLE = 'element not available'\n    ELE_STOP_MOVING = 'element stop moving'\n    ELE_STATE_CHANGED_ = 'Failed to wait for element state change ({} seconds).'\n    RUN_BY_ADMIN = 'Try to run with administrator rights.'\n    BROWSER_CONNECT_ERR_INFO = ('\\n1, the user folder does not conflict with the open browser \\n'\n                                '2, if no interface system, please add \\'--headless=new\\' startup parameter \\n'\n                                '3, if the system is Linux, try adding \\'--no-sandbox\\' boot parameter \\n'\n                                'The port and user folder paths can be set using ChromiumOptions.')\n    # ALLOW\n    LOC_OR_IND = 'A locator (str or tuple of length 2) or serial number.'\n    STR_ONLY = 'Str format and not support xpath or css selector.'\n    LOC_FORMAT = 'str or tuple of length 2'\n    ELE_OR_LOC = 'A locator (str or tuple of length 2) or element.'\n    FRAME_LOC_FORMAT = 'locator, iframe serial number, id, name, ChromiumFrame object'\n    SET_DOWNLOAD_PATH = 'Use the set.download_path() method, configuration object, or ini file.'\n    SET_WINDOW_NORMAL = ('When the browser is in full screen or minimized state, call set.window.normal() '\n                         'first to restore the normal state.')\n    HTML_ELE_TYPE = 'Element, Tab object, or html text'\n    ELE_LOC_FORMAT = '(x, y) format coordinates, or ChromiumElement objects'\n    IP_OR_OPTIONS = 'ip:port format character string or ChromiumOptions type'\n\n    TAB_OR_ID = 'Tab object or tab id'\n    RUN_JS = 'run js'\n    PAGE_CONNECT = 'Page connection'\n    NEW_ELE_INFO = 'html text, or tuple format: (tag, {name: value}).'\n    DICT_TO_NEW_ELE = ('This page does not support new elements in html format. Please pass the html_or_info '\n                       'parameter with dict.')\n\n    # --------- print ---------\n    RETRY = 'Retry'\n    OPTIONS_HAVE_SAVED = 'The configuration is saved to a file'\n    AUTO_LOAD_TIP = 'Later the program can automatically load the configuration from the file'\n    STOP_RECORDING = 'Stop recording.'\n    START_RECORD = 'Start recording.'\n    CHOOSE_RECORD_TARGET = 'Manually select the target you want to record.'\n    UNSUPPORTED_USER_PROXY = ('You seem to be setting up a proxy that uses the account password, which is not '\n                              'supported for the time being, and can be implemented by the plug-in itself.')\n    UNSUPPORTED_SOCKS_PROXY = ('You seem to be setting up the use of socks proxy, this proxy is not supported for the '\n                               'time being, you can use your own plug-in to achieve the requirements.')\n    NOT_SUPPORT_DOWNLOAD = 'The browser version is too low to use the download management function.'\n    FILE_NAME = 'File Name'\n    FOLDER_PATH = 'Folder Path'\n    UNKNOWN = 'Unknown'\n    DOWNLOAD_COMPLETED = 'Complete'\n    COMPLETED_AND_RENAME = 'Renamed'\n    OVERWROTE = 'Overwrote'\n    DOWNLOAD_CANCELED = 'Canceled'\n    SKIPPED = 'Skipped'\n"
  },
  {
    "path": "DrissionPage/_functions/tools.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom pathlib import Path\nfrom platform import system\nfrom shutil import rmtree\nfrom tempfile import gettempdir\nfrom threading import Lock\nfrom time import perf_counter, sleep\n\nfrom .._configs.options_manage import OptionsManager\nfrom .._functions.settings import Settings as _S\nfrom ..errors import (ContextLostError, ElementLostError, PageDisconnectedError, NoRectError, BrowserConnectError,\n                      AlertExistsError, IncorrectURLError, StorageError, CookieFormatError, JavaScriptError, CDPError)\n\n\nclass PortFinder(object):\n    used_port = set()\n    prev_time = 0\n    lock = Lock()\n    checked_paths = set()\n\n    def __init__(self, path=None):\n        tmp = Path(path) if path else Path(gettempdir()) / 'DrissionPage'\n        self.tmp_dir = tmp / 'autoPortData'\n        self.tmp_dir.mkdir(parents=True, exist_ok=True)\n        if str(self.tmp_dir.absolute()) not in PortFinder.checked_paths:\n            for i in self.tmp_dir.iterdir():\n                if i.is_dir() and not port_is_using('127.0.0.1', i.name):\n                    rmtree(i, ignore_errors=True)\n            PortFinder.checked_paths.add(str(self.tmp_dir.absolute()))\n\n    def get_port(self, scope=None):\n        from random import randint\n        with PortFinder.lock:\n            if PortFinder.prev_time and perf_counter() - PortFinder.prev_time > 60:\n                PortFinder.used_port.clear()\n            if scope in (True, None):\n                scope = (9600, 59600)\n            max_times = scope[1] - scope[0]\n            times = 0\n            while times < max_times:\n                times += 1\n                port = randint(*scope)\n                if port in PortFinder.used_port or port_is_using('127.0.0.1', port):\n                    continue\n                path = self.tmp_dir / str(port)\n                if path.exists():\n                    try:\n                        rmtree(path)\n                    except:\n                        continue\n                PortFinder.used_port.add(port)\n                PortFinder.prev_time = perf_counter()\n                return port, str(path)\n            raise BrowserConnectError(_S._lang.NO_AVAILABLE_PORT_FOUND)\n\n\ndef port_is_using(ip, port):\n    from socket import socket, AF_INET, SOCK_STREAM\n    s = socket(AF_INET, SOCK_STREAM)\n    s.settimeout(.1)\n    result = s.connect_ex((ip, int(port)))\n    s.close()\n    return result == 0\n\n\ndef clean_folder(folder_path, ignore=None):\n    ignore = [] if not ignore else ignore\n    p = Path(folder_path)\n\n    for f in p.iterdir():\n        if f.name not in ignore:\n            if f.is_file():\n                f.unlink()\n            elif f.is_dir():\n                rmtree(f, True)\n\n\ndef show_or_hide_browser(tab, hide=True):\n    if not tab.browser.address.startswith(('127.0.0.1', 'localhost')):\n        return\n\n    if system().lower() != 'windows':\n        raise EnvironmentError(_S._lang.WIN_SYS_ONLY)\n\n    try:\n        from win32gui import ShowWindow\n        from win32con import SW_HIDE, SW_SHOW\n    except (ImportError, ModuleNotFoundError):\n        raise EnvironmentError(_S._lang.join(_S._lang.NEED_LIB_, 'pypiwin32', TIP='pip install pypiwin32'))\n\n    pid = tab.browser.process_id\n    if not pid:\n        return None\n    hds = get_hwnds_from_pid(pid, tab.title)\n    sw = SW_HIDE if hide else SW_SHOW\n    for hd in hds:\n        ShowWindow(hd, sw)\n\n\ndef get_browser_progress_id(progress, address):\n    if progress:\n        return progress.pid\n\n    from os import popen\n    port = address.split(':')[-1]\n    txt = ''\n    progresses = popen(f'netstat -nao | findstr :{port}').read().split('\\n')\n    for progress in progresses:\n        if 'LISTENING' in progress:\n            txt = progress\n            break\n    if not txt:\n        return None\n\n    return txt.split(' ')[-1]\n\n\ndef get_hwnds_from_pid(pid, title):\n    try:\n        from win32gui import IsWindow, GetWindowText, EnumWindows\n        from win32process import GetWindowThreadProcessId\n    except (ImportError, ModuleNotFoundError):\n        raise EnvironmentError(_S._lang.join(_S._lang.NEED_LIB_, 'win32gui, win32process',\n                                             TIP='pip install pypiwin32\\npip install win32process'))\n\n    def callback(hwnd, hds):\n        if IsWindow(hwnd) and title in GetWindowText(hwnd):\n            _, found_pid = GetWindowThreadProcessId(hwnd)\n            if str(found_pid) == str(pid):\n                hds.append(hwnd)\n            return True\n\n    hwnds = []\n    EnumWindows(callback, hwnds)\n    return hwnds\n\n\ndef wait_until(function, kwargs=None, timeout=10):\n    if kwargs is None:\n        kwargs = {}\n    end_time = perf_counter() + timeout\n    while perf_counter() < end_time:\n        value = function(**kwargs)\n        if value:\n            return value\n        sleep(.01)\n    raise TimeoutError\n\n\ndef configs_to_here(save_name=None):\n    om = OptionsManager('default')\n    save_name = f'{save_name}.ini' if save_name is not None else 'dp_configs.ini'\n    om.save(save_name)\n\n\ndef raise_error(result, browser, ignore=None, user=False):\n    error = result['error']\n    if error in ('Cannot find context with specified id', 'Inspected target navigated or closed',\n                 'No frame with given id found'):\n        r = ContextLostError()\n    elif error in ('Could not find node with given id', 'Could not find object with given id',\n                   'No node with given id found', 'Node with given id does not belong to the document',\n                   'No node found for given backend id'):\n        r = ElementLostError()\n    elif error in ('connection disconnected', 'No target with given id found'):\n        r = PageDisconnectedError()\n    elif error == 'alert exists.':\n        r = AlertExistsError()\n    elif error in ('Node does not have a layout object', 'Could not compute box model.'):\n        r = NoRectError()\n    elif error == 'Cannot navigate to invalid URL':\n        r = IncorrectURLError(_S._lang.INVALID_URL, url=result[\"args\"][\"url\"])\n    elif error == 'Frame corresponds to an opaque origin and its storage key cannot be serialized':\n        r = StorageError()\n    elif error == 'Sanitizing cookie failed':\n        r = CookieFormatError(cookies=result[\"args\"])\n    elif error == 'Invalid header name':\n        r = ValueError(_S._lang.join(_S._lang.INVALID_HEADER_NAME, headers=result[\"args\"][\"headers\"]))\n    elif error == 'Given expression does not evaluate to a function':\n        r = JavaScriptError(_S._lang.NOT_A_FUNCTION, JS=result[\"args\"][\"functionDeclaration\"])\n    elif error.endswith(\"' wasn't found\"):\n        r = RuntimeError(_S._lang.join(_S._lang.METHOD_NOT_FOUND, BROWSER_VER=browser.version, METHOD=result[\"method\"]))\n    elif result['type'] == 'timeout':\n        r = TimeoutError(_S._lang.join(_S._lang.NO_RESPONSE, INFO=result[\"error\"],\n                                       METHOD=result[\"method\"], ARGS=result[\"args\"]))\n    elif result['type'] == 'call_method_error' and not user:\n        r = CDPError(_S._lang.UNKNOWN_ERR, INFO=result[\"error\"], METHOD=result[\"method\"],\n                     ARGS=result[\"args\"], TIP=_S._lang.FEEDBACK)\n    else:\n        r = RuntimeError(_S._lang.join(_S._lang.UNKNOWN_ERR, INFO=result, TIP=_S._lang.FEEDBACK))\n\n    if not ignore or not isinstance(r, ignore):\n        raise r\n"
  },
  {
    "path": "DrissionPage/_functions/tools.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom os import popen\r\nfrom pathlib import Path\r\nfrom threading import Lock\r\nfrom typing import Union, Tuple\r\n\r\nfrom .._base.chromium import Chromium\r\nfrom .._pages.chromium_base import ChromiumBase\r\n\r\n\r\nclass PortFinder(object):\r\n    used_port: set = ...\r\n    prev_time: float = ...\r\n    lock: Lock = ...\r\n    tmp_dir: Path = ...\r\n    checked_paths: set = ...\r\n\r\n    def __init__(self, path: Union[str, Path] = None):\r\n        \"\"\"\r\n        :param path: 临时文件保存路径，为None时使用系统临时文件夹\r\n        \"\"\"\r\n        ...\r\n\r\n    @staticmethod\r\n    def get_port(scope: Tuple[int, int] = None) -> Tuple[int, str]:\r\n        \"\"\"查找一个可用端口\r\n        :param scope: 指定端口范围，不含最后的数字，为None则使用[9600-59600)\r\n        :return: 可以使用的端口和用户文件夹路径组成的元组\r\n        \"\"\"\r\n        ...\r\n\r\n\r\ndef port_is_using(ip: str, port: Union[str, int]) -> bool:\r\n    \"\"\"检查端口是否被占用\r\n    :param ip: 浏览器地址\r\n    :param port: 浏览器端口\r\n    :return: bool\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef clean_folder(folder_path: Union[str, Path], ignore: Union[tuple, list] = None) -> None:\r\n    \"\"\"清空一个文件夹，除了ignore里的文件和文件夹\r\n    :param folder_path: 要清空的文件夹路径\r\n    :param ignore: 忽略列表\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef show_or_hide_browser(tab: ChromiumBase, hide: bool = True) -> None:\r\n    \"\"\"执行显示或隐藏浏览器窗口\r\n    :param tab: ChromiumTab对象\r\n    :param hide: 是否隐藏\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef get_browser_progress_id(progress: Union[popen, None], address: str) -> Union[str, None]:\r\n    \"\"\"获取浏览器进程id\r\n    :param progress: 已知的进程对象，没有时传入None\r\n    :param address: 浏览器管理地址，含端口\r\n    :return: 进程id或None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef get_hwnds_from_pid(pid: Union[str, int], title: str) -> list:\r\n    \"\"\"通过PID查询句柄ID\r\n    :param pid: 进程id\r\n    :param title: 窗口标题\r\n    :return: 进程句柄组成的列表\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef wait_until(function: callable, kwargs: dict = None, timeout: float = 10):\r\n    \"\"\"等待传入的方法返回值不为假\r\n    :param function: 要执行的方法\r\n    :param kwargs: 方法参数\r\n    :param timeout: 超时时间（秒）\r\n    :return: 执行结果，超时抛出TimeoutError\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef configs_to_here(save_name: Union[Path, str] = None) -> None:\r\n    \"\"\"把默认ini文件复制到当前目录\r\n    :param save_name: 指定文件名，为None则命名为'dp_configs.ini'\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef raise_error(result: dict, browser: Chromium, ignore=None, user: bool = False) -> None:\r\n    \"\"\"抛出error对应报错\r\n    :param result: 包含error的dict\r\n    :param browser: 浏览器对象\r\n    :param ignore: 要忽略的错误\r\n    :param user: 是否用户调用的\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n"
  },
  {
    "path": "DrissionPage/_functions/web.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom html import unescape\r\nfrom os.path import sep\r\nfrom pathlib import Path\r\nfrom re import sub\r\nfrom urllib.parse import urlparse, urljoin, urlunparse\r\n\r\nfrom DataRecorder.tools import make_valid_name\r\nfrom requests.structures import CaseInsensitiveDict\r\n\r\nfrom .._functions.settings import Settings as _S\r\n\r\n\r\ndef get_ele_txt(e):\r\n    # 前面无须换行的元素\r\n    nowrap_list = ('br', 'sub', 'sup', 'em', 'strong', 'a', 'font', 'b', 'span', 's', 'i', 'del', 'ins', 'img', 'td',\r\n                   'th', 'abbr', 'bdi', 'bdo', 'cite', 'code', 'data', 'dfn', 'kbd', 'mark', 'q', 'rp', 'rt', 'ruby',\r\n                   'samp', 'small', 'time', 'u', 'var', 'wbr', 'button', 'slot', 'content')\r\n    # 后面添加换行的元素\r\n    wrap_after_list = ('p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'li', 'blockquote', 'header',\r\n                       'footer', 'address' 'article', 'aside', 'main', 'nav', 'section', 'figcaption', 'summary')\r\n    # 不获取文本的元素\r\n    noText_list = ('script', 'style', 'video', 'audio', 'iframe', 'embed', 'noscript', 'canvas', 'template')\r\n    # 用/t分隔的元素\r\n    tab_list = ('td', 'th')\r\n\r\n    if e.tag in noText_list:\r\n        return e.raw_text\r\n\r\n    def get_node_txt(ele, pre=False) -> list:\r\n        tag = ele.tag\r\n        if tag == 'br':\r\n            return [True]\r\n        if not pre and tag == 'pre':\r\n            pre = True\r\n\r\n        str_list = []\r\n        if tag in noText_list and not pre:  # 标签内的文本不返回\r\n            return str_list\r\n\r\n        nodes = ele.eles('xpath:./text() | *')\r\n        prev_ele = ''\r\n        for el in nodes:\r\n            if isinstance(el, str):  # 字符节点\r\n                if pre:\r\n                    str_list.append(el)\r\n\r\n                else:\r\n                    if sub('[ \\n\\t\\r]', '', el) != '':  # 字符除了回车和空格还有其它内容\r\n                        txt = el\r\n                        if not pre:\r\n                            txt = txt.replace('\\r\\n', ' ').replace('\\n', ' ')\r\n                            txt = sub(r' {2,}', ' ', txt)\r\n                        str_list.append(txt)\r\n\r\n            else:  # 元素节点\r\n                if el.tag not in nowrap_list and str_list and str_list[-1] != '\\n':  # 元素间换行的情况\r\n                    str_list.append('\\n')\r\n                if el.tag in tab_list and prev_ele in tab_list:  # 表格的行\r\n                    str_list.append('\\t')\r\n\r\n                str_list.extend(get_node_txt(el, pre))\r\n                prev_ele = el.tag\r\n\r\n        if tag in wrap_after_list and str_list and str_list[-1] not in ('\\n', True):  # 有些元素后面要添加回车\r\n            str_list.append('\\n')\r\n\r\n        return str_list\r\n\r\n    re_str = get_node_txt(e)\r\n    if re_str and re_str[-1] == '\\n':\r\n        re_str.pop()\r\n\r\n    l = len(re_str)\r\n    if l > 1:\r\n        r = []\r\n        for i in range(l - 1):\r\n            i1 = re_str[i]\r\n            i2 = re_str[i + 1]\r\n            if i1 is True:\r\n                r.append('\\n')\r\n                continue\r\n            elif i2 is True:\r\n                r.append(i1)\r\n                continue\r\n            elif i1.endswith(' ') and i2.startswith(' '):\r\n                i1 = i1[:-1]\r\n            r.append(i1)\r\n        r.append('\\n' if re_str[-1] is True else re_str[-1])\r\n        re_str = ''.join(r)\r\n\r\n    elif not l:\r\n        re_str = ''\r\n    else:\r\n        re_str = re_str[0] if re_str[0] is not True else '\\n'\r\n\r\n    return format_html(re_str.strip())\r\n\r\n\r\ndef format_html(text):\r\n    return unescape(text).replace('\\xa0', ' ') if text else text\r\n\r\n\r\ndef location_in_viewport(page, loc_x, loc_y):\r\n    js = f'''function(){{let x = {loc_x}; let y = {loc_y};\r\n    const scrollLeft = document.documentElement.scrollLeft;\r\n    const scrollTop = document.documentElement.scrollTop;\r\n    const vWidth = document.documentElement.clientWidth;\r\n    const vHeight = document.documentElement.clientHeight;\r\n    if (x< scrollLeft || y < scrollTop || x > vWidth + scrollLeft || y > vHeight + scrollTop){{return false;}}\r\n    return true;}}'''\r\n    return page._run_js(js)\r\n\r\n\r\ndef offset_scroll(ele, offset_x, offset_y):\r\n    loc_x, loc_y = ele.rect.location\r\n    cp_x, cp_y = ele.rect.click_point\r\n    lx = loc_x + offset_x if offset_x else cp_x\r\n    ly = loc_y + offset_y if offset_y else cp_y\r\n    if not location_in_viewport(ele.owner, lx, ly):\r\n        clientWidth = ele.owner._run_js('return document.body.clientWidth;')\r\n        clientHeight = ele.owner._run_js('return document.body.clientHeight;')\r\n        ele.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2)\r\n    cl_x, cl_y = ele.rect.viewport_location\r\n    ccp_x, ccp_y = ele.rect.viewport_click_point\r\n    cx = cl_x + offset_x if offset_x else ccp_x\r\n    cy = cl_y + offset_y if offset_y else ccp_y\r\n    return cx, cy\r\n\r\n\r\ndef make_absolute_link(link, baseURI=None):\r\n    if not link:\r\n        return link\r\n\r\n    link = link.strip().replace('\\\\', '/')\r\n    parsed = urlparse(link)._asdict()\r\n    # if baseURI:\r\n    #     baseURI = baseURI.rstrip('/\\\\')\r\n\r\n    # 是相对路径，与页面url拼接并返回\r\n    if not parsed['netloc']:\r\n        return urljoin(baseURI, link) if baseURI else link\r\n\r\n    # 是绝对路径但缺少协议，从页面url获取协议并修复\r\n    if not parsed['scheme'] and baseURI:\r\n        parsed['scheme'] = urlparse(baseURI).scheme\r\n        parsed = tuple(v for v in parsed.values())\r\n        return urlunparse(parsed)\r\n\r\n    # 绝对路径且不缺协议，直接返回\r\n    return link\r\n\r\n\r\ndef is_js_func(func):\r\n    func = func.strip()\r\n    if (func.startswith('function') or func.startswith('async ')) and func.endswith('}'):\r\n        return True\r\n    # elif '=>' in func:\r\n    #     return True\r\n    return False\r\n\r\n\r\ndef get_blob(page, url, as_bytes=True):\r\n    if not url.startswith('blob'):\r\n        raise ValueError(_S._lang.join(_S._lang.NOT_BLOB, url=url))\r\n    js = \"\"\"\r\n       function fetchData(url) {\r\n      return new Promise((resolve, reject) => {\r\n        let xhr = new XMLHttpRequest();\r\n        xhr.responseType = 'blob';\r\n        xhr.onload = function() {\r\n          let reader  = new FileReader();\r\n          reader.onloadend = function(){resolve(reader.result);}\r\n          reader.readAsDataURL(xhr.response);\r\n        };\r\n        xhr.open('GET', url, true);\r\n        xhr.send();\r\n      });\r\n    }\r\n\"\"\"\r\n    try:\r\n        result = page._run_js(js, url)\r\n    except:\r\n        raise RuntimeError(_S._lang.join(_S._lang.GET_BLOB_FAILED))\r\n    if as_bytes:\r\n        from base64 import b64decode\r\n        return b64decode(result.split(',', 1)[-1])\r\n    else:\r\n        return result\r\n\r\n\r\ndef save_page(tab, path=None, name=None, as_pdf=False, kwargs=None):\r\n    if name:\r\n        if name.endswith('.pdf'):\r\n            name = name[:-4]\r\n            as_pdf = True\r\n        elif name.endswith('.mhtml'):\r\n            name = name[:-6]\r\n            as_pdf = False\r\n\r\n    if path:\r\n        path = Path(path)\r\n        if path.suffix.lower() == '.mhtml':\r\n            name = path.stem\r\n            path = path.parent\r\n            as_pdf = False\r\n        elif path.suffix.lower() == '.pdf':\r\n            name = path.stem\r\n            path = path.parent\r\n            as_pdf = True\r\n\r\n    return get_pdf(tab, path, name, kwargs) if as_pdf else get_mhtml(tab, path, name)\r\n\r\n\r\ndef get_mhtml(page, path=None, name=None):\r\n    r = page._run_cdp('Page.captureSnapshot')['data']\r\n    if path is None and name is None:\r\n        return r\r\n\r\n    path = path or '.'\r\n    Path(path).mkdir(parents=True, exist_ok=True)\r\n    name = make_valid_name(name or page.title)\r\n    with open(f'{path}{sep}{name}.mhtml', 'w', encoding='utf-8', newline='\\n') as f:\r\n        f.write(r.replace('\\r\\n', '\\n'))\r\n    return r\r\n\r\n\r\ndef get_pdf(page, path=None, name=None, kwargs=None):\r\n    if not kwargs:\r\n        kwargs = {}\r\n    kwargs['transferMode'] = 'ReturnAsBase64'\r\n    if 'printBackground' not in kwargs:\r\n        kwargs['printBackground'] = True\r\n    try:\r\n        r = page._run_cdp('Page.printToPDF', **kwargs)['data']\r\n    except:\r\n        raise RuntimeError(_S._lang.join(_S._lang.GET_PDF_FAILED))\r\n    from base64 import b64decode\r\n    r = b64decode(r)\r\n    if path is None and name is None:\r\n        return r\r\n\r\n    path = path or '.'\r\n    Path(path).mkdir(parents=True, exist_ok=True)\r\n    name = make_valid_name(name or page.title)\r\n    with open(f'{path}{sep}{name}.pdf', 'wb', newline='\\n') as f:\r\n        f.write(r)\r\n    return r\r\n\r\n\r\ndef tree(ele_or_page, text=False, show_js=False, show_css=False):\r\n    def _tree(obj, last_one=True, body=''):\r\n        list_ele = obj.children()\r\n        length = len(list_ele)\r\n        body_unit = '    ' if last_one else '│   '\r\n        tail = '├───'\r\n        new_body = body + body_unit\r\n\r\n        if length > 0:\r\n            new_last_one = False\r\n            for i in range(length):\r\n                if i == length - 1:\r\n                    tail = '└───'\r\n                    new_last_one = True\r\n                e = list_ele[i]\r\n\r\n                attrs = ' '.join([f\"{k}='{v}'\" for k, v in e.attrs.items()])\r\n                show_text = f'{new_body}{tail}<{e.tag} {attrs}>'.replace('\\n', ' ')\r\n                if text:\r\n                    t = e('x:/text()')\r\n                    if t:\r\n                        t = t.replace('\\n', ' ')\r\n                        if (e.tag not in ('script', 'style') or (e.tag == 'script' and show_js)\r\n                                or (e.tag == 'style' and show_css)):\r\n                            if text is not True:\r\n                                t = t[:text]\r\n                            show_text = f'{show_text} {t}'\r\n                print(show_text)\r\n\r\n                _tree(e, new_last_one, new_body)\r\n\r\n    ele = ele_or_page.s_ele()\r\n    attrs = ' '.join([f\"{k}='{v}'\" for k, v in ele.attrs.items()])\r\n    show_text = f'<{ele.tag} {attrs}>'.replace('\\n', ' ')\r\n    if text:\r\n        t = ele('x:/text()')\r\n        if t:\r\n            t = t.replace('\\n', ' ')\r\n            if (ele.tag not in ('script', 'style') or (ele.tag == 'script' and show_js)\r\n                    or (ele.tag == 'style' and show_css)):\r\n                if text is not True:\r\n                    t = t[:text]\r\n                show_text = f'{show_text} {t}'\r\n    print(show_text)\r\n    _tree(ele)\r\n\r\n\r\ndef format_headers(txt):\r\n    if isinstance(txt, (dict, CaseInsensitiveDict)):\r\n        for k, v in txt.items():\r\n            if v not in (None, False, True):\r\n                txt[k] = str(v)\r\n        for i in (':method', ':scheme', ':authority', ':path'):\r\n            txt.pop(i, None)\r\n        return txt\r\n    headers = {}\r\n    for header in txt.split('\\n'):\r\n        if header:\r\n            name, value = header.split(': ', maxsplit=1)\r\n            if name not in (':method', ':scheme', ':authority', ':path'):\r\n                headers[name] = value\r\n    return headers\r\n"
  },
  {
    "path": "DrissionPage/_functions/web.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Union, Optional, Tuple\r\n\r\nfrom .._base.base import DrissionElement, BaseParser\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.chromium_page import ChromiumPage\r\nfrom .._pages.chromium_tab import ChromiumTab\r\n\r\n\r\ndef get_ele_txt(e: DrissionElement) -> str:\r\n    \"\"\"获取元素内所有文本\r\n    :param e: 元素对象\r\n    :return: 元素内所有文本\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef format_html(text: str) -> str:\r\n    \"\"\"处理html编码字符\r\n    :param text: html文本\r\n    :return: 格式化后的html文本\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef location_in_viewport(page: ChromiumBase, loc_x: float, loc_y: float) -> bool:\r\n    \"\"\"判断给定的坐标是否在视口中          |n\r\n    :param page: ChromePage对象\r\n    :param loc_x: 页面绝对坐标x\r\n    :param loc_y: 页面绝对坐标y\r\n    :return: bool\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef offset_scroll(ele: ChromiumElement, offset_x: float, offset_y: float) -> Tuple[int, int]:\r\n    \"\"\"接收元素及偏移坐标，把坐标滚动到页面中间，返回该点坐标\r\n    有偏移量时以元素左上角坐标为基准，没有时以click_point为基准\r\n    :param ele: 元素对象\r\n    :param offset_x: 偏移量x\r\n    :param offset_y: 偏移量y\r\n    :return: 相对坐标\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef make_absolute_link(link: str, baseURI: str = None) -> str:\r\n    \"\"\"获取绝对url\r\n    :param link: 超链接\r\n    :param baseURI: 页面或iframe的url\r\n    :return: 绝对链接\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef is_js_func(func: str) -> bool:\r\n    \"\"\"检查文本是否js函数\"\"\"\r\n    ...\r\n\r\n\r\ndef get_blob(page: ChromiumBase, url: str, as_bytes: bool = True) -> bytes:\r\n    \"\"\"获取知道blob资源\r\n    :param page: 资源所在页面对象\r\n    :param url: 资源url\r\n    :param as_bytes: 是否以字节形式返回\r\n    :return: 资源内容\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef save_page(tab: Union[ChromiumPage, ChromiumTab],\r\n              path: Union[Path, str, None] = None,\r\n              name: Optional[str] = None,\r\n              as_pdf: bool = False,\r\n              kwargs: dict = None) -> Union[bytes, str]:\r\n    \"\"\"把当前页面保存为文件，如果path和name参数都为None，只返回文本\r\n    :param tab: Tab或Page对象\r\n    :param path: 保存路径，为None且name不为None时保存在当前路径\r\n    :param name: 文件名，为None且path不为None时用title属性值\r\n    :param as_pdf: 为Ture保存为pdf，否则为mhtml且忽略kwargs参数\r\n    :param kwargs: pdf生成参数\r\n    :return: as_pdf为True时返回bytes，否则返回文件文本\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef get_mhtml(page: Union[ChromiumPage, ChromiumTab],\r\n              path: Optional[Path] = None,\r\n              name: Optional[str] = None) -> Union[bytes, str]:\r\n    \"\"\"把当前页面保存为mhtml文件，如果path和name参数都为None，只返回mhtml文本\r\n    :param page: 要保存的页面对象\r\n    :param path: 保存路径，为None且name不为None时保存在当前路径\r\n    :param name: 文件名，为None且path不为None时用title属性值\r\n    :return: mhtml文本\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef get_pdf(page: Union[ChromiumPage, ChromiumTab],\r\n            path: Optional[Path] = None,\r\n            name: Optional[str] = None,\r\n            kwargs: dict = None) -> Union[bytes, str]:\r\n    \"\"\"把当前页面保存为pdf文件，如果path和name参数都为None，只返回字节\r\n    :param page: 要保存的页面对象\r\n    :param path: 保存路径，为None且name不为None时保存在当前路径\r\n    :param name: 文件名，为None且path不为None时用title属性值\r\n    :param kwargs: pdf生成参数\r\n    :return: pdf文本\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef tree(ele_or_page: BaseParser,\r\n         text: Union[int, bool] = False,\r\n         show_js: bool = False,\r\n         show_css: bool = False) -> None:\r\n    \"\"\"把页面或元素对象DOM结构打印出来\r\n    :param ele_or_page: 页面或元素对象\r\n    :param text: 是否打印文本，输入数字可指定打印文本长度上线\r\n    :param show_js: 打印文本时是否包含<script>内文本，text参数为False时无效\r\n    :param show_css: 打印文本时是否包含<style>内文本，text参数为False时无效\r\n    :return: None\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef format_headers(txt: str) -> dict:\r\n    \"\"\"从浏览器复制的文本生成dict格式headers，文本用换行分隔\r\n    :param txt: 从浏览器复制的原始文本格式headers\r\n    :return: dict格式headers\r\n    \"\"\"\r\n    ...\r\n"
  },
  {
    "path": "DrissionPage/_pages/chromium_base.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom json import loads, JSONDecodeError\r\nfrom os.path import sep\r\nfrom pathlib import Path\r\nfrom re import findall\r\nfrom threading import Thread\r\nfrom time import perf_counter, sleep\r\n\r\nfrom DataRecorder.tools import make_valid_name\r\n\r\nfrom .._base.base import BasePage\r\nfrom .._elements.chromium_element import run_js, make_chromium_eles\r\nfrom .._elements.none_element import NoneElement\r\nfrom .._elements.session_element import make_session_ele\r\nfrom .._functions.cookies import CookiesList\r\nfrom .._functions.elements import SessionElementsList, get_frame, ChromiumElementsList\r\nfrom .._functions.locator import get_loc\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._functions.tools import raise_error\r\nfrom .._functions.web import location_in_viewport\r\nfrom .._units.actions import Actions\r\nfrom .._units.console import Console\r\nfrom .._units.listener import Listener\r\nfrom .._units.rect import TabRect\r\nfrom .._units.screencast import Screencast\r\nfrom .._units.scroller import PageScroller\r\nfrom .._units.setter import ChromiumBaseSetter\r\nfrom .._units.states import PageStates\r\nfrom .._units.waiter import BaseWaiter\r\nfrom ..errors import (ContextLostError, CDPError, PageDisconnectedError, ElementLostError, JavaScriptError,\r\n                      BrowserConnectError, LocatorError)\r\n\r\n__ERROR__ = 'error'\r\n\r\n\r\nclass ChromiumBase(BasePage):\r\n    def __init__(self, browser, target_id=None):\r\n        super().__init__()\r\n        self._browser = browser\r\n        self._is_loading = None\r\n        self._root_id = None  # object id\r\n        self._set = None\r\n        self._screencast = None\r\n        self._actions = None\r\n        self._states = None\r\n        self._has_alert = False\r\n        self._ready_state = None\r\n        self._rect = None\r\n        self._wait = None\r\n        self._scroll = None\r\n        self._console = None\r\n        self._upload_list = None\r\n        self._doc_got = False  # 用于在LoadEventFired和FrameStoppedLoading间标记是否已获取doc\r\n        self._auto_handle_alert = None\r\n        self._load_end_time = 0\r\n        self._init_jss = []\r\n        self._disconnect_flag = False\r\n        self._type = 'ChromiumBase'\r\n        if not hasattr(self, '_listener'):\r\n            self._listener = None\r\n\r\n        self._d_set_runtime_settings()\r\n        self._connect_browser(target_id)\r\n\r\n    def __call__(self, locator, index=1, timeout=None):\r\n        return self.ele(locator, index, timeout)\r\n\r\n    def _d_set_runtime_settings(self):\r\n        pass\r\n\r\n    def _connect_browser(self, target_id=None):\r\n        self._is_reading = False\r\n\r\n        if not target_id:\r\n            if self.browser._ws_only:\r\n                tabs = self._run_cdp('Target.getTargets')['targetInfos']\r\n                _id = 'targetId'\r\n            else:\r\n                tabs = self.browser._driver.get(f'http://{self.browser.address}/json').json()\r\n                _id = 'id'\r\n            tabs = [(i[_id], i['url']) for i in tabs\r\n                    if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]\r\n            dialog = None\r\n            if len(tabs) > 1:\r\n                for k, t in enumerate(tabs):\r\n                    if t[1] == 'chrome://privacy-sandbox-dialog/notice':\r\n                        dialog = k\r\n                    elif not target_id:\r\n                        target_id = t[0]\r\n\r\n                    if target_id and dialog is not None:\r\n                        break\r\n\r\n                if dialog is not None:\r\n                    close_privacy_dialog(self, tabs[dialog][0])\r\n\r\n            else:\r\n                target_id = tabs[0][0]\r\n\r\n        self._driver_init(target_id)\r\n        if self._js_ready_state == 'complete' and self._ready_state is None:\r\n            self._get_document()\r\n            self._ready_state = 'complete'\r\n\r\n    def _driver_init(self, target_id):\r\n        self._is_loading = True\r\n        self._driver = self.browser._get_driver(target_id, self)\r\n\r\n        self._alert = Alert(self._auto_handle_alert)\r\n        self._driver.set_callback('Page.javascriptDialogOpening', self._on_alert_open, immediate=True)\r\n        self._driver.set_callback('Page.javascriptDialogClosed', self._on_alert_close)\r\n\r\n        self._driver.run('DOM.enable')\r\n        self._driver.run('Page.enable')\r\n        self._driver.run('Emulation.setFocusEmulationEnabled', enabled=True)\r\n\r\n        r = self._run_cdp('Page.getFrameTree')\r\n        for i in findall(r\"'id': '(.*?)'\", str(r)):\r\n            self.browser._frames[i] = self.tab_id\r\n        if not hasattr(self, '_frame_id'):\r\n            self._frame_id = r['frameTree']['frame']['id']\r\n\r\n        self._driver.set_callback('Page.frameStartedLoading', self._onFrameStartedLoading)\r\n        self._driver.set_callback('Page.frameNavigated', self._onFrameNavigated)\r\n        self._driver.set_callback('Page.domContentEventFired', self._onDomContentEventFired)\r\n        self._driver.set_callback('Page.loadEventFired', self._onLoadEventFired)\r\n        self._driver.set_callback('Page.frameStoppedLoading', self._onFrameStoppedLoading)\r\n        self._driver.set_callback('Page.frameAttached', self._onFrameAttached)\r\n        self._driver.set_callback('Page.frameDetached', self._onFrameDetached)\r\n\r\n    def _get_document(self, timeout=10):\r\n        if self._is_reading:\r\n            return\r\n        self._is_reading = True\r\n        timeout = max(timeout, 2)\r\n        end_time = perf_counter() + timeout\r\n        while perf_counter() < end_time:\r\n            try:\r\n                b_id = self._run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']\r\n                timeout = end_time - perf_counter()\r\n                timeout = 1 if timeout <= 1 else timeout\r\n                self._root_id = self._run_cdp('DOM.resolveNode', backendNodeId=b_id,\r\n                                              _timeout=timeout)['object']['objectId']\r\n                result = True\r\n                break\r\n\r\n            except PageDisconnectedError:\r\n                result = False\r\n                break\r\n            except:\r\n                timeout = end_time - perf_counter()\r\n                timeout = .5 if timeout <= 0 else timeout\r\n            sleep(.05)\r\n\r\n        else:\r\n            result = False\r\n\r\n        if result:\r\n            r = self._run_cdp('Page.getFrameTree', _ignore=PageDisconnectedError)\r\n            for i in findall(r\"'id': '(.*?)'\", str(r)):\r\n                self.browser._frames[i] = self.tab_id\r\n\r\n        self._is_loading = False\r\n        self._is_reading = False\r\n        return result\r\n\r\n    def _onFrameDetached(self, **kwargs):\r\n        self.browser._frames.pop(kwargs['frameId'], None)\r\n\r\n    def _onFrameAttached(self, **kwargs):\r\n        self.browser._frames[kwargs['frameId']] = self.tab_id\r\n\r\n    def _onFrameStartedLoading(self, **kwargs):\r\n        self.browser._frames[kwargs['frameId']] = self.tab_id\r\n        if kwargs['frameId'] == self._frame_id:\r\n            self._doc_got = False\r\n            self._ready_state = 'connecting'\r\n            self._is_loading = True\r\n            self._load_end_time = perf_counter() + self.timeouts.page_load\r\n            if self._load_mode == 'eager':\r\n                t = Thread(target=self._wait_to_stop)\r\n                t.daemon = True\r\n                t.start()\r\n\r\n    def _onFrameNavigated(self, **kwargs):\r\n        if kwargs['frame']['id'] == self._frame_id:\r\n            self._doc_got = False\r\n            self._ready_state = 'loading'\r\n            self._is_loading = True\r\n            if kwargs.get('type', None) == 'BackForwardCacheRestore':\r\n                self._get_document()\r\n\r\n    def _onDomContentEventFired(self, **kwargs):\r\n        if self._load_mode == 'eager':\r\n            self._run_cdp('Page.stopLoading')\r\n        if self._get_document(self._load_end_time - perf_counter() - .1):\r\n            self._doc_got = True\r\n        self._ready_state = 'interactive'\r\n\r\n    def _onLoadEventFired(self, **kwargs):\r\n        if self._doc_got is False and self._get_document(self._load_end_time - perf_counter() - .1):\r\n            self._doc_got = True\r\n        self._ready_state = 'complete'\r\n\r\n    def _onFrameStoppedLoading(self, **kwargs):\r\n        self.browser._frames[kwargs['frameId']] = self.tab_id\r\n        if kwargs['frameId'] == self._frame_id:\r\n            if self._doc_got is False:\r\n                self._get_document(self._load_end_time - perf_counter() - .1)\r\n            self._ready_state = 'complete'\r\n\r\n    def _onFileChooserOpened(self, **kwargs):\r\n        if self._upload_list:\r\n            if 'backendNodeId' not in kwargs:\r\n                raise RuntimeError(_S._lang.join(_S._lang.CANNOT_INPUT_FILE))\r\n            files = self._upload_list if kwargs['mode'] == 'selectMultiple' else self._upload_list[:1]\r\n            self._run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=kwargs['backendNodeId'])\r\n\r\n            self.driver.set_callback('Page.fileChooserOpened', None)\r\n            self._run_cdp('Page.setInterceptFileChooserDialog', enabled=False)\r\n            self._upload_list = None\r\n\r\n    def _wait_to_stop(self):\r\n        end_time = perf_counter() + self.timeouts.page_load\r\n        while perf_counter() < end_time:\r\n            sleep(.02)\r\n        if self._ready_state in ('interactive', 'complete') and self._is_loading:\r\n            self.stop_loading()\r\n\r\n    # ----------挂件----------\r\n    @property\r\n    def wait(self):\r\n        if self._wait is None:\r\n            self._wait = BaseWaiter(self)\r\n        return self._wait\r\n\r\n    @property\r\n    def set(self):\r\n        if self._set is None:\r\n            self._set = ChromiumBaseSetter(self)\r\n        return self._set\r\n\r\n    @property\r\n    def screencast(self):\r\n        if self._screencast is None:\r\n            self._screencast = Screencast(self)\r\n        return self._screencast\r\n\r\n    @property\r\n    def actions(self):\r\n        if self._actions is None:\r\n            self._actions = Actions(self)\r\n        self.wait.doc_loaded()\r\n        return self._actions\r\n\r\n    @property\r\n    def listen(self):\r\n        if self._listener is None:\r\n            self._listener = Listener(self)\r\n        return self._listener\r\n\r\n    @property\r\n    def states(self):\r\n        if self._states is None:\r\n            self._states = PageStates(self)\r\n        return self._states\r\n\r\n    @property\r\n    def scroll(self):\r\n        self.wait.doc_loaded()\r\n        if self._scroll is None:\r\n            self._scroll = PageScroller(self)\r\n        return self._scroll\r\n\r\n    @property\r\n    def rect(self):\r\n        # self.wait.doc_loaded()\r\n        if self._rect is None:\r\n            self._rect = TabRect(self)\r\n        return self._rect\r\n\r\n    @property\r\n    def console(self):\r\n        if self._console is None:\r\n            self._console = Console(self)\r\n        return self._console\r\n\r\n    @property\r\n    def timeout(self):\r\n        return self._timeouts.base\r\n\r\n    @property\r\n    def timeouts(self):\r\n        return self._timeouts\r\n\r\n    # ----------挂件结束----------\r\n\r\n    @property\r\n    def browser(self):\r\n        return self._browser\r\n\r\n    @property\r\n    def driver(self):\r\n        if self._driver is None:\r\n            raise BrowserConnectError(_S._lang.BROWSER_DISCONNECTED)\r\n        return self._driver\r\n\r\n    @property\r\n    def title(self):\r\n        return self._run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['title']\r\n\r\n    @property\r\n    def url(self):\r\n        return self._run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['url']\r\n\r\n    @property\r\n    def _browser_url(self):\r\n        return self.url\r\n\r\n    @property\r\n    def html(self):\r\n        self.wait.doc_loaded()\r\n        return self._run_cdp('DOM.getOuterHTML', objectId=self._root_id)['outerHTML']\r\n\r\n    @property\r\n    def json(self):\r\n        try:\r\n            return loads(self('t:pre', timeout=.5).text)\r\n        except JSONDecodeError:\r\n            return None\r\n\r\n    @property\r\n    def tab_id(self):\r\n        return self.driver.id\r\n\r\n    @property\r\n    def _target_id(self):\r\n        return self.driver.id\r\n\r\n    @property\r\n    def active_ele(self):\r\n        return self._run_js_loaded('return document.activeElement;')\r\n\r\n    @property\r\n    def load_mode(self):\r\n        return self._load_mode\r\n\r\n    @property\r\n    def user_agent(self):\r\n        return self._run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']\r\n\r\n    @property\r\n    def upload_list(self):\r\n        return self._upload_list\r\n\r\n    @property\r\n    def session(self):\r\n        if self._session is None:\r\n            self._create_session()\r\n        return self._session\r\n\r\n    @property\r\n    def _js_ready_state(self):\r\n        try:\r\n            return self._run_cdp('Runtime.evaluate', expression='document.readyState;', _timeout=3)['result']['value']\r\n        except ContextLostError:\r\n            return None\r\n        except TimeoutError:\r\n            return 'timeout'\r\n\r\n    def run_cdp(self, cmd, **cmd_args):\r\n        r = self.driver.run(cmd, **cmd_args)\r\n        return r if __ERROR__ not in r else raise_error(r, self.browser, user=True)\r\n\r\n    def run_cdp_loaded(self, cmd, **cmd_args):\r\n        self.wait.doc_loaded()\r\n        r = self.driver.run(cmd, **cmd_args)\r\n        return r if __ERROR__ not in r else raise_error(r, self.browser, user=True)\r\n\r\n    def _run_cdp(self, cmd, **cmd_args):\r\n        ignore = cmd_args.pop('_ignore', None)\r\n        r = self.driver.run(cmd, **cmd_args)\r\n        return r if __ERROR__ not in r else raise_error(r, self.browser, ignore)\r\n\r\n    def _run_cdp_loaded(self, cmd, **cmd_args):\r\n        self.wait.doc_loaded()\r\n        return self._run_cdp(cmd, **cmd_args)\r\n\r\n    def run_js(self, script, *args, as_expr=False, timeout=None):\r\n        return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)\r\n\r\n    def run_js_loaded(self, script, *args, as_expr=False, timeout=None):\r\n        self.wait.doc_loaded()\r\n        return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)\r\n\r\n    def _run_js(self, script, *args, as_expr=False, timeout=None):\r\n        return run_js(self, script, as_expr, self.timeouts.script if timeout is None else timeout, args)\r\n\r\n    def _run_js_loaded(self, script, *args, as_expr=False, timeout=None):\r\n        self.wait.doc_loaded()\r\n        return run_js(self, script, as_expr, self.timeouts.script if timeout is None else timeout, args)\r\n\r\n    def run_async_js(self, script, *args, as_expr=False):\r\n        run_js(self, script, as_expr, 0, args)\r\n\r\n    def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None):\r\n        retry, interval, is_file = self._before_connect(url, retry, interval)\r\n        self._url_available = self._d_connect(self._url, times=retry, interval=interval,\r\n                                              show_errmsg=show_errmsg, timeout=timeout)\r\n        return self._url_available\r\n\r\n    def cookies(self, all_domains=False, all_info=False):\r\n        txt = 'Storage' if all_domains else 'Network'\r\n        cookies = self._run_cdp_loaded(f'{txt}.getCookies')['cookies']\r\n\r\n        if all_info:\r\n            r = cookies\r\n        else:\r\n            r = [{'name': cookie['name'], 'value': cookie['value'], 'domain': cookie['domain']} for cookie in cookies]\r\n\r\n        return CookiesList(r)\r\n\r\n    def ele(self, locator, index=1, timeout=None):\r\n        return self._ele(locator, timeout=timeout, index=index, method='ele()')\r\n\r\n    def eles(self, locator, timeout=None):\r\n        return self._ele(locator, timeout=timeout, index=None)\r\n\r\n    def s_ele(self, locator=None, index=1, timeout=None):\r\n        if timeout is None:\r\n            timeout = self.timeout\r\n        return (NoneElement(self, method='s_ele()', args={'locator': locator, 'index': index, 'timeout': timeout})\r\n                if locator and not self.wait.eles_loaded(locator, timeout=timeout)\r\n                else make_session_ele(self, locator, index=index, method='s_ele()'))\r\n\r\n    def s_eles(self, locator, timeout=None):\r\n        return (make_session_ele(self, locator, index=None)\r\n                if self.wait.eles_loaded(locator, timeout=timeout) else SessionElementsList())\r\n\r\n    def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):\r\n        if isinstance(locator, (str, tuple)):\r\n            loc = get_loc(locator)[1]\r\n        elif locator._type in ('ChromiumElement', 'ChromiumFrame'):\r\n            return locator\r\n        else:\r\n            raise LocatorError(_S._lang.join(ALLOW_TYPE=_S._lang.ELE_OR_LOC, CURR_VAL=locator))\r\n\r\n        self.wait.doc_loaded()\r\n        end_time = perf_counter() + timeout\r\n\r\n        search_ids = []\r\n        timeout = .5 if timeout <= 0 else timeout\r\n        result = self.driver.run('DOM.performSearch', query=loc, _timeout=timeout, includeUserAgentShadowDOM=True)\r\n        if not result or __ERROR__ in result:\r\n            num = 0\r\n        else:\r\n            num = result['resultCount']\r\n            search_ids.append(result['searchId'])\r\n\r\n        while True:\r\n            if num > 0:\r\n                from_index = index_arg = 0\r\n                if index is None:\r\n                    end_index = num\r\n                    index_arg = None\r\n                elif index < 0:\r\n                    from_index = index + num\r\n                    end_index = from_index + 1\r\n                else:\r\n                    from_index = index - 1\r\n                    end_index = from_index + 1\r\n\r\n                if from_index <= num - 1:\r\n                    nIds = self._driver.run('DOM.getSearchResults', searchId=result['searchId'],\r\n                                            fromIndex=from_index, toIndex=end_index)\r\n                    if __ERROR__ not in nIds:\r\n                        if nIds['nodeIds'][0] != 0:\r\n                            r = make_chromium_eles(self, _ids=nIds['nodeIds'], index=index_arg,\r\n                                                   is_obj_id=False, ele_only=True)\r\n                            if r is not False:\r\n                                break\r\n\r\n                    elif nIds[__ERROR__] == 'connection disconnected':\r\n                        raise PageDisconnectedError\r\n\r\n            if perf_counter() >= end_time:\r\n                return NoneElement(self) if index is not None else ChromiumElementsList(owner=self)\r\n\r\n            sleep(.01)\r\n            timeout = end_time - perf_counter()\r\n            timeout = .5 if timeout <= 0 else timeout\r\n            result = self.driver.run('DOM.performSearch', query=loc, _timeout=timeout, includeUserAgentShadowDOM=True)\r\n            if result and __ERROR__ not in result:\r\n                num = result['resultCount']\r\n                search_ids.append(result['searchId'])\r\n            elif result and result[__ERROR__] == 'connection disconnected':\r\n                raise PageDisconnectedError\r\n\r\n        for _id in search_ids:\r\n            self._driver.run('DOM.discardSearchResults', searchId=_id)\r\n\r\n        return r\r\n\r\n    def refresh(self, ignore_cache=False):\r\n        self._is_loading = True\r\n        self._run_cdp('Page.reload', ignoreCache=ignore_cache)\r\n        self.wait.load_start()\r\n\r\n    def forward(self, steps=1):\r\n        self._forward_or_back(steps)\r\n\r\n    def back(self, steps=1):\r\n        self._forward_or_back(-steps)\r\n\r\n    def _forward_or_back(self, steps):\r\n        if steps == 0:\r\n            return\r\n\r\n        history = self._run_cdp('Page.getNavigationHistory')\r\n        index = history['currentIndex']\r\n        history = history['entries']\r\n        direction = 1 if steps > 0 else -1\r\n        curr_url = history[index]['url']\r\n        nid = None\r\n        for num in range(abs(steps)):\r\n            for i in history[index::direction]:\r\n                index += direction\r\n                if i['url'] != curr_url:\r\n                    nid = i['id']\r\n                    curr_url = i['url']\r\n                    break\r\n\r\n        if nid:\r\n            self._is_loading = True\r\n            self._run_cdp('Page.navigateToHistoryEntry', entryId=nid)\r\n\r\n    def stop_loading(self):\r\n        try:\r\n            self._run_cdp('Page.stopLoading')\r\n            end_time = perf_counter() + 5\r\n            while self._ready_state != 'complete' and perf_counter() < end_time:\r\n                sleep(.02)\r\n        except (PageDisconnectedError, CDPError):\r\n            pass\r\n        finally:\r\n            self._ready_state = 'complete'\r\n\r\n    def remove_ele(self, loc_or_ele):\r\n        if not loc_or_ele:\r\n            return\r\n        ele = self._ele(loc_or_ele, raise_err=False)\r\n        if ele:\r\n            self._run_cdp('DOM.removeNode', nodeId=ele._node_id, _ignore=ElementLostError)\r\n\r\n    def add_ele(self, html_or_info, insert_to=None, before=None):\r\n        if isinstance(html_or_info, str):\r\n            insert_to = self.ele(insert_to) if insert_to else self.ele('t:body')\r\n            args = [html_or_info, insert_to]\r\n            if before:\r\n                args.append(insert_to.ele(before))\r\n                js = '''\r\n                     ele = document.createElement(null);\r\n                     arguments[1].insertBefore(ele, arguments[2]);\r\n                     ele.outerHTML = arguments[0];\r\n                     return arguments[2].previousElementSibling;\r\n                     '''\r\n            else:\r\n                js = '''\r\n                     ele = document.createElement(null);\r\n                     arguments[1].appendChild(ele);\r\n                     ele.outerHTML = arguments[0];\r\n                     return arguments[1].lastElementChild;\r\n                     '''\r\n\r\n        elif isinstance(html_or_info, tuple):\r\n            args = [html_or_info[0], html_or_info[1]]\r\n            txt = ''\r\n            if insert_to:\r\n                args.append(self.ele(insert_to))\r\n                if before:\r\n                    args.append(self.ele(before))\r\n                    txt = '''\r\n                         arguments[2].insertBefore(ele, arguments[3]);\r\n                         '''\r\n                else:\r\n                    txt = '''\r\n                         arguments[2].appendChild(ele);\r\n                         '''\r\n            js = f'''\r\n                     ele = document.createElement(arguments[0]);\r\n                     for(let k in arguments[1]){{\r\n                        if(k==\"innerHTML\"){{ele.innerHTML=arguments[1][k]}}\r\n                        else if(k==\"innerText\"){{ele.innerText=arguments[1][k]}}\r\n                        else{{ele.setAttribute(k, arguments[1][k]);}}\r\n                     }}\r\n                     {txt}\r\n                     return ele;\r\n                     '''\r\n\r\n        else:\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'html_or_info', ALLOW_TYPE='html, tuple',\r\n                                           TIP=_S._lang.NEW_ELE_INFO, CURR_VAL=html_or_info))\r\n\r\n        try:\r\n            ele = self._run_js(js, *args)\r\n        except JavaScriptError:\r\n            raise RuntimeError(_S._lang.join(_S._lang.DICT_TO_NEW_ELE))\r\n        return ele\r\n\r\n    def get_frame(self, loc_ind_ele, timeout=None):\r\n        return get_frame(self, loc_ind_ele=loc_ind_ele, timeout=timeout)\r\n\r\n    def get_frames(self, locator=None, timeout=None):\r\n        locator = locator or 'xpath://*[name()=\"iframe\" or name()=\"frame\"]'\r\n        frames = self._ele(locator, timeout=timeout, index=None, raise_err=False)\r\n        return ChromiumElementsList(self, frames)\r\n\r\n    def session_storage(self, item=None):\r\n        js = f'sessionStorage.getItem(\"{item}\")' if item else 'sessionStorage'\r\n        return self._run_js_loaded(js, as_expr=True)\r\n\r\n    def local_storage(self, item=None):\r\n        js = f'localStorage.getItem(\"{item}\")' if item else 'localStorage'\r\n        return self._run_js_loaded(js, as_expr=True)\r\n\r\n    def get_screenshot(self, path=None, name=None, as_bytes=None, as_base64=None,\r\n                       full_page=False, left_top=None, right_bottom=None):\r\n        return self._get_screenshot(path=path, name=name, as_bytes=as_bytes, as_base64=as_base64,\r\n                                    full_page=full_page, left_top=left_top, right_bottom=right_bottom)\r\n\r\n    def add_init_js(self, script):\r\n        js_id = self._run_cdp('Page.addScriptToEvaluateOnNewDocument', source=script,\r\n                              includeCommandLineAPI=True)['identifier']\r\n        self._init_jss.append(js_id)\r\n        return js_id\r\n\r\n    def remove_init_js(self, script_id=None):\r\n        if script_id is None:\r\n            for js_id in self._init_jss:\r\n                self._run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=js_id)\r\n            self._init_jss.clear()\r\n\r\n        elif script_id in self._init_jss:\r\n            self._run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=script_id)\r\n            self._init_jss.remove(script_id)\r\n\r\n    def clear_cache(self, session_storage=True, local_storage=True, cache=True, cookies=True):\r\n        if session_storage and local_storage and cache and cookies:\r\n            self._run_cdp_loaded(\"Storage.clearDataForOrigin\", origin=\"*\", storageTypes=\"all\")\r\n\r\n        if session_storage or local_storage:\r\n            self._run_cdp_loaded('DOMStorage.enable')\r\n            i = self._run_cdp('Storage.getStorageKeyForFrame', frameId=self._frame_id)['storageKey']\r\n            if session_storage:\r\n                self._run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': False})\r\n            if local_storage:\r\n                self._run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': True})\r\n            self._run_cdp_loaded('DOMStorage.disable')\r\n\r\n        if cache:\r\n            self._run_cdp_loaded('Network.clearBrowserCache')\r\n\r\n        if cookies:\r\n            self._run_cdp_loaded('Network.clearBrowserCookies')\r\n\r\n    def disconnect(self):\r\n        if self._driver:\r\n            self._disconnect_flag = True\r\n            self._driver.stop()\r\n            self.browser._all_drivers.get(self._driver.id, set()).discard(self._driver)\r\n            self._disconnect_flag = False\r\n\r\n    def reconnect(self, wait=0):\r\n        t_id = self._target_id\r\n        self.disconnect()\r\n        sleep(wait)\r\n        self.browser.reconnect()\r\n        self._driver = self.browser._get_driver(t_id, self)\r\n        self._driver_init(t_id)\r\n        self._get_document()\r\n\r\n    def handle_alert(self, accept=True, send=None, timeout=None, next_one=False):\r\n        if not isinstance(accept, bool):\r\n            return self._handle_alert(accept=accept, send=send, timeout=timeout, next_one=next_one)\r\n        r = self._handle_alert(accept=accept, send=send, timeout=timeout, next_one=next_one)\r\n        while self._has_alert:\r\n            sleep(.0001)\r\n        return r\r\n\r\n    def _handle_alert(self, accept=True, send=None, timeout=None, next_one=False):\r\n        if next_one:\r\n            self._alert.handle_next = accept\r\n            self._alert.next_text = send\r\n            return\r\n        if timeout is None:\r\n            timeout = self.timeout\r\n        timeout = .1 if timeout <= 0 else timeout\r\n        end_time = perf_counter() + timeout\r\n        while not self._alert.activated and perf_counter() < end_time:\r\n            sleep(.01)\r\n        if not self._alert.activated:\r\n            return False\r\n\r\n        res_text = self._alert.text\r\n        if not isinstance(accept, bool):\r\n            return res_text\r\n        d = {'accept': accept, '_timeout': 0}\r\n        if self._alert.type == 'prompt' and send is not None:\r\n            d['promptText'] = send\r\n        self.driver.run('Page.handleJavaScriptDialog', **d)\r\n        return res_text\r\n\r\n    def _on_alert_open(self, **kwargs):\r\n        self._alert.activated = True\r\n        self._alert.text = kwargs['message']\r\n        self._alert.type = kwargs['type']\r\n        self._alert.defaultPrompt = kwargs.get('defaultPrompt', None)\r\n        self._alert.response_accept = None\r\n        self._alert.response_text = None\r\n        self._has_alert = True\r\n\r\n        if self._alert.auto is not None:\r\n            if self._alert.auto != 'close':\r\n                self._handle_alert(self._alert.auto)\r\n        elif _S.auto_handle_alert is not None:\r\n            self._handle_alert(_S.auto_handle_alert)\r\n        elif self._alert.handle_next is not None:\r\n            self._handle_alert(self._alert.handle_next, self._alert.next_text)\r\n            self._alert.handle_next = None\r\n\r\n    def _on_alert_close(self, **kwargs):\r\n        self._alert.activated = False\r\n        self._alert.text = None\r\n        self._alert.type = None\r\n        self._alert.defaultPrompt = None\r\n        self._alert.response_accept = kwargs.get('result')\r\n        self._alert.response_text = kwargs['userInput']\r\n        self._has_alert = False\r\n\r\n    def _wait_loaded(self, timeout=None):\r\n        if timeout is None:\r\n            timeout = self.timeouts.page_load\r\n        end_time = perf_counter() + timeout\r\n        while perf_counter() < end_time:\r\n            if self._ready_state == 'complete':\r\n                return True\r\n            elif self._load_mode == 'eager' and self._ready_state in ('interactive',\r\n                                                                      'complete') and not self._is_loading:\r\n                return True\r\n\r\n            sleep(.01)\r\n\r\n        try:\r\n            self.stop_loading()\r\n        except CDPError:\r\n            pass\r\n        return False\r\n\r\n    def _d_connect(self, to_url, times=0, interval=1, show_errmsg=False, timeout=None):\r\n        err = None\r\n        self._is_loading = True\r\n        timeout = timeout if timeout is not None else self.timeouts.page_load\r\n        for t in range(times + 1):\r\n            err = None\r\n            end_time = perf_counter() + timeout\r\n            try:\r\n                result = self._run_cdp('Page.navigate', frameId=self._frame_id, url=to_url, _timeout=timeout)\r\n                if 'errorText' in result:\r\n                    err = ConnectionError(_S._lang.join(_S._lang.CONNECT_ERR, INFO=result['errorText']))\r\n            except TimeoutError:\r\n                err = TimeoutError(_S._lang.join(_S._lang.TIMEOUT_, _S._lang.PAGE_CONNECT, timeout))\r\n\r\n            if err:\r\n                if t < times:\r\n                    sleep(interval)\r\n                    if show_errmsg:\r\n                        print(f'{_S._lang.RETRY}{t + 1} {to_url}')\r\n                end_time1 = end_time - perf_counter()\r\n                while self._ready_state not in ('loading', 'complete') and perf_counter() < end_time1:  # 等待出错信息显示\r\n                    sleep(.01)\r\n                self.stop_loading()\r\n                continue\r\n\r\n            if self._load_mode == 'none':\r\n                return True\r\n\r\n            yu = end_time - perf_counter()\r\n            ok = self._wait_loaded(1 if yu <= 0 else yu)\r\n            if not ok:\r\n                err = TimeoutError(_S._lang.join(_S._lang.TIMEOUT_, _S._lang.PAGE_CONNECT, timeout))\r\n                if t < times:\r\n                    sleep(interval)\r\n                    if show_errmsg:\r\n                        print(f'{_S._lang.RETRY}{t + 1} {to_url}')\r\n                continue\r\n\r\n            if not err:\r\n                break\r\n\r\n        if err:\r\n            if show_errmsg:\r\n                raise err if err is not None else ConnectionError(_S._lang.join(_S._lang.CONNECT_ERR))\r\n            return False\r\n\r\n        return True\r\n\r\n    def _get_screenshot(self, path=None, name=None, as_bytes=None, as_base64=None,\r\n                        full_page=False, left_top=None, right_bottom=None, ele=None):\r\n        if as_bytes:\r\n            if as_bytes is True:\r\n                pic_type = 'png'\r\n            else:\r\n                if as_bytes not in ('jpg', 'jpeg', 'png', 'webp'):\r\n                    raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'as_bytes',\r\n                                                   ALLOW_VAL='\"jpg\", \"jpeg\", \"png\", \"webp\"', CURR_VAL=as_bytes))\r\n                pic_type = 'jpeg' if as_bytes == 'jpg' else as_bytes\r\n\r\n        elif as_base64:\r\n            if as_base64 is True:\r\n                pic_type = 'png'\r\n            else:\r\n                if as_base64 not in ('jpg', 'jpeg', 'png', 'webp'):\r\n                    raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'as_base64',\r\n                                                   ALLOW_VAL='\"jpg\", \"jpeg\", \"png\", \"webp\"', CURR_VAL=as_base64))\r\n                pic_type = 'jpeg' if as_base64 == 'jpg' else as_base64\r\n\r\n        else:\r\n            path = str(path).rstrip('\\\\/') if path else '.'\r\n            if not path.endswith(('.jpg', '.jpeg', '.png', '.webp')):\r\n                if not name:\r\n                    name = f'{self.title}.jpg'\r\n                elif not name.endswith(('.jpg', '.jpeg', '.png', '.webp')):\r\n                    name = f'{name}.jpg'\r\n                path = f'{path}{sep}{make_valid_name(name)}'\r\n\r\n            path = Path(path)\r\n            pic_type = path.suffix.lower()\r\n            pic_type = 'jpeg' if pic_type == '.jpg' else pic_type[1:]\r\n\r\n        if full_page:\r\n            width, height = self.rect.size\r\n            if width == 0 or height == 0:\r\n                raise RuntimeError(_S._lang.join(_S._lang.ZERO_PAGE_SIZE))\r\n            vp = {'x': 0, 'y': 0, 'width': width, 'height': height, 'scale': 1}\r\n            args = {'format': pic_type, 'captureBeyondViewport': True, 'clip': vp}\r\n        else:\r\n            if left_top or right_bottom:\r\n                if not left_top:\r\n                    left_top = (0, 0)\r\n                if not right_bottom:\r\n                    right_bottom = self.rect.size\r\n                x, y = left_top\r\n                w = right_bottom[0] - x\r\n                h = right_bottom[1] - y\r\n\r\n                v = not (location_in_viewport(self, x, y) and\r\n                         location_in_viewport(self, right_bottom[0], right_bottom[1]))\r\n                if v and (self._run_js('return document.body.scrollHeight > window.innerHeight;') and\r\n                          not self._run_js('return document.body.scrollWidth > window.innerWidth;')):\r\n                    x += 10\r\n\r\n                vp = {'x': x, 'y': y, 'width': w, 'height': h, 'scale': 1}\r\n                args = {'format': pic_type, 'captureBeyondViewport': v, 'clip': vp}\r\n\r\n            else:\r\n                args = {'format': pic_type}\r\n\r\n        if pic_type == 'jpeg':\r\n            args['quality'] = 100\r\n        png = self._run_cdp_loaded('Page.captureScreenshot', **args)['data']\r\n\r\n        if as_base64:\r\n            return png\r\n\r\n        from base64 import b64decode\r\n        png = b64decode(png)\r\n\r\n        if as_bytes:\r\n            return png\r\n\r\n        path.parent.mkdir(parents=True, exist_ok=True)\r\n        with open(path, 'wb') as f:\r\n            f.write(png)\r\n        return str(path.absolute())\r\n\r\n\r\nclass Timeout(object):\r\n    def __init__(self, base=None, page_load=None, script=None):\r\n        self.base = 10 if base is None else base\r\n        self.page_load = 30 if page_load is None else page_load\r\n        self.script = 30 if script is None else script\r\n\r\n    def __repr__(self):\r\n        return str({'base': self.base, 'page_load': self.page_load, 'script': self.script})\r\n\r\n    @property\r\n    def as_dict(self):\r\n        return {'base': self.base, 'page_load': self.page_load, 'script': self.script}\r\n\r\n\r\nclass Alert(object):\r\n    def __init__(self, auto=None):\r\n        self.activated = False\r\n        self.text = None\r\n        self.type = None\r\n        self.defaultPrompt = None\r\n        self.response_accept = None\r\n        self.response_text = None\r\n        self.handle_next = None\r\n        self.next_text = None\r\n        self.auto = auto\r\n\r\n\r\ndef close_privacy_dialog(page, tid):\r\n    \"\"\"关闭隐私声明弹窗\r\n    :param page: ChromiumBase对象\r\n    :param tid: tab id\r\n    :return: None\r\n    \"\"\"\r\n    try:\r\n        driver = page.browser._get_driver(tid)\r\n        driver.run('Runtime.enable')\r\n        driver.run('DOM.enable')\r\n        driver.run('DOM.getDocument')\r\n        sid = driver.run('DOM.performSearch', query='//*[name()=\"privacy-sandbox-notice-dialog-app\"]',\r\n                         includeUserAgentShadowDOM=True)['searchId']\r\n        r = driver.run('DOM.getSearchResults', searchId=sid, fromIndex=0, toIndex=1)['nodeIds'][0]\r\n        end_time = perf_counter() + 3\r\n        while perf_counter() < end_time:\r\n            try:\r\n                r = driver.run('DOM.describeNode', nodeId=r)['node']['shadowRoots'][0]['backendNodeId']\r\n                break\r\n            except KeyError:\r\n                pass\r\n            sleep(.05)\r\n        driver.run('DOM.discardSearchResults', searchId=sid)\r\n        r = driver.run('DOM.resolveNode', backendNodeId=r)['object']['objectId']\r\n        r = driver.run('Runtime.callFunctionOn', objectId=r,\r\n                       functionDeclaration='function(){return this.getElementById(\"ackButton\");}')['result']['objectId']\r\n        driver.run('Runtime.callFunctionOn', objectId=r, functionDeclaration='function(){return this.click();}')\r\n        driver.close()\r\n\r\n    except:\r\n        pass\r\n"
  },
  {
    "path": "DrissionPage/_pages/chromium_base.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom pathlib import Path\nfrom typing import Union, Tuple, Any, Optional, Literal\n\nfrom requests import Session\n\nfrom .chromium_page import ChromiumPage\nfrom .chromium_tab import ChromiumTab\nfrom .mix_tab import MixTab\nfrom .web_page import WebPage\nfrom .._base.base import BasePage\nfrom .._base.chromium import Chromium\nfrom .._base.driver import Driver\nfrom .._elements.chromium_element import ChromiumElement\nfrom .._elements.session_element import SessionElement\nfrom .._functions.cookies import CookiesList\nfrom .._functions.elements import SessionElementsList, ChromiumElementsList\nfrom .._pages.chromium_frame import ChromiumFrame\nfrom .._units.actions import Actions\nfrom .._units.console import Console\nfrom .._units.listener import Listener\nfrom .._units.rect import TabRect, FrameRect\nfrom .._units.screencast import Screencast\nfrom .._units.scroller import Scroller, PageScroller\nfrom .._units.setter import ChromiumBaseSetter\nfrom .._units.states import PageStates\nfrom .._units.waiter import BaseWaiter\n\nPIC_TYPE = Literal['jpg', 'jpeg', 'png', 'webp', True]\n\n\nclass ChromiumBase(BasePage):\n    \"\"\"标签页、Frame、Page基类\"\"\"\n    _tab: Union[ChromiumTab, MixTab, ChromiumFrame, ChromiumPage, WebPage] = ...\n    _browser: Chromium = ...\n    _driver: Optional[Driver] = ...\n    _frame_id: str = ...\n    _is_reading: bool = ...\n    _is_timeout: bool = ...\n    _timeouts: Timeout = ...\n    _first_run: bool = ...\n    _is_loading: Optional[bool] = ...\n    _load_mode: str = ...\n    _scroll: Optional[Scroller] = ...\n    _url: str = ...\n    _root_id: Optional[str] = ...\n    _upload_list: Optional[list] = ...\n    _wait: Optional[BaseWaiter] = ...\n    _set: Optional[ChromiumBaseSetter] = ...\n    _screencast: Optional[Screencast] = ...\n    _actions: Optional[Actions] = ...\n    _listener: Optional[Listener] = ...\n    _states: Optional[PageStates] = ...\n    _alert: Alert = ...\n    _has_alert: bool = ...\n    _auto_handle_alert: Optional[bool] = ...\n    _doc_got: bool = ...\n    _load_end_time: float = ...\n    _init_jss: list = ...\n    _ready_state: Optional[str] = ...\n    _rect: Optional[TabRect] = ...\n    _console: Optional[Console] = ...\n    _disconnect_flag: bool = ...\n    _type: str = ...\n\n    def __init__(self,\n                 browser: Chromium,\n                 tab_id: str = None):\n        \"\"\"\n        :param browser: Chromium\n        :param tab_id: 要控制的tab id，不指定默认为激活的标签页\n        \"\"\"\n        ...\n\n    def __call__(self,\n                 locator: Union[Tuple[str, str], str, ChromiumElement],\n                 index: int = 1,\n                 timeout: float = None) -> ChromiumElement:\n        \"\"\"在内部查找元素\n        例：ele = page('@id=ele_id')\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\n        :param index: 获取第几个元素，从1开始，可传入负数获取倒数第几个\n        :param timeout: 超时时间（秒）\n        :return: ChromiumElement对象\n        \"\"\"\n        ...\n\n    def _d_set_runtime_settings(self) -> None: ...\n\n    def _connect_browser(self, target_id: str = None) -> None:\n        \"\"\"连接浏览器，在第一次时运行\n        :param target_id: 要控制的target id，不指定默认为激活的标签页\n        :return: None\n        \"\"\"\n        ...\n\n    def _driver_init(self, target_id: str) -> None:\n        \"\"\"新建页面、页面刷新后要进行的cdp参数初始化\n        :param target_id: 要跳转到的target id\n        :return: None\n        \"\"\"\n        ...\n\n    def _get_document(self, timeout: float = 10) -> bool:\n        \"\"\"获取页面文档\n        :param timeout: 超时时间（秒）\n        :return: 是否获取成功\n        \"\"\"\n        ...\n\n    def _onFrameDetached(self, **kwargs) -> None: ...\n\n    def _onFrameAttached(self, **kwargs) -> None: ...\n\n    def _onFrameStartedLoading(self, **kwargs):\n        \"\"\"页面开始加载时执行\"\"\"\n        ...\n\n    def _onFrameNavigated(self, **kwargs):\n        \"\"\"页面跳转时执行\"\"\"\n        ...\n\n    def _onDomContentEventFired(self, **kwargs):\n        \"\"\"在页面刷新、变化后重新读取页面内容\"\"\"\n        ...\n\n    def _onLoadEventFired(self, **kwargs):\n        \"\"\"在页面刷新、变化后重新读取页面内容\"\"\"\n        ...\n\n    def _onFrameStoppedLoading(self, **kwargs):\n        \"\"\"页面加载完成后执行\"\"\"\n        ...\n\n    def _onFileChooserOpened(self, **kwargs):\n        \"\"\"文件选择框打开时执行\"\"\"\n        ...\n\n    def _wait_to_stop(self):\n        \"\"\"eager策略超时时使页面停止加载\"\"\"\n        ...\n\n    @property\n    def wait(self) -> BaseWaiter:\n        \"\"\"返回用于等待的对象\"\"\"\n        ...\n\n    @property\n    def set(self) -> ChromiumBaseSetter:\n        \"\"\"返回用于设置的对象\"\"\"\n        ...\n\n    @property\n    def screencast(self) -> Screencast:\n        \"\"\"返回用于录屏的对象\"\"\"\n        ...\n\n    @property\n    def actions(self) -> Actions:\n        \"\"\"返回用于执行动作链的对象\"\"\"\n        ...\n\n    @property\n    def listen(self) -> Listener:\n        \"\"\"返回用于聆听数据包的对象\"\"\"\n        ...\n\n    @property\n    def states(self) -> PageStates:\n        \"\"\"返回用于获取状态信息的对象\"\"\"\n        ...\n\n    @property\n    def scroll(self) -> PageScroller:\n        \"\"\"返回用于滚动滚动条的对象\"\"\"\n        ...\n\n    @property\n    def rect(self) -> Union[TabRect, FrameRect]:\n        \"\"\"返回获取窗口坐标和大小的对象\"\"\"\n        ...\n\n    @property\n    def console(self) -> Console:\n        \"\"\"返回获取控制台信息的对象\"\"\"\n        ...\n\n    @property\n    def timeout(self) -> float:\n        \"\"\"返回timeout设置\"\"\"\n        ...\n\n    @property\n    def timeouts(self) -> Timeout:\n        \"\"\"返回timeouts设置\"\"\"\n        ...\n\n    @property\n    def browser(self) -> Chromium:\n        \"\"\"返回浏览器对象\"\"\"\n        ...\n\n    @property\n    def driver(self) -> Driver:\n        \"\"\"返回用于控制浏览器的Driver对象\"\"\"\n        ...\n\n    @property\n    def title(self) -> str:\n        \"\"\"返回当前页面title\"\"\"\n        ...\n\n    @property\n    def url(self) -> str:\n        \"\"\"返回当前页面url\"\"\"\n        ...\n\n    @property\n    def _browser_url(self) -> str:\n        \"\"\"用于被MixTab覆盖\"\"\"\n        ...\n\n    @property\n    def html(self) -> str:\n        \"\"\"返回当前页面html文本\"\"\"\n        ...\n\n    @property\n    def json(self) -> Union[dict, None]:\n        \"\"\"当返回内容是json格式时，返回对应的字典，非json格式时返回None\"\"\"\n        ...\n\n    @property\n    def tab_id(self) -> str:\n        \"\"\"返回当前标签页id\"\"\"\n        ...\n\n    @property\n    def _target_id(self) -> str:\n        \"\"\"返回当前标签页id\"\"\"\n        ...\n\n    @property\n    def active_ele(self) -> ChromiumElement:\n        \"\"\"返回当前焦点所在元素\"\"\"\n        ...\n\n    @property\n    def load_mode(self) -> Literal['none', 'normal', 'eager']:\n        \"\"\"返回页面加载策略，有3种：'none'、'normal'、'eager'\"\"\"\n        ...\n\n    @property\n    def user_agent(self) -> str:\n        \"\"\"返回user agent\"\"\"\n        ...\n\n    @property\n    def upload_list(self) -> list:\n        \"\"\"返回等待上传文件列表\"\"\"\n        ...\n\n    @property\n    def session(self) -> Session:\n        \"\"\"返回用于转换模式或download的Session对象\"\"\"\n        ...\n\n    @property\n    def _js_ready_state(self) -> str:\n        \"\"\"返回js获取的ready state信息\"\"\"\n        ...\n\n    def run_cdp(self, cmd: str, **cmd_args) -> dict:\n        \"\"\"执行Chrome DevTools Protocol语句\n        :param cmd: 协议项目\n        :param cmd_args: 参数\n        :return: 执行的结果\n        \"\"\"\n        ...\n\n    def run_cdp_loaded(self, cmd: str, **cmd_args) -> dict:\n        \"\"\"执行Chrome DevTools Protocol语句，执行前等待页面加载完毕\n        :param cmd: 协议项目\n        :param cmd_args: 参数\n        :return: 执行的结果\n        \"\"\"\n        ...\n\n    def _run_cdp(self, cmd: str, **cmd_args) -> dict:\n        \"\"\"执行Chrome DevTools Protocol语句\n        :param cmd: 协议项目\n        :param cmd_args: 参数\n        :return: 执行的结果\n        \"\"\"\n        ...\n\n    def _run_cdp_loaded(self, cmd: str, **cmd_args) -> dict:\n        \"\"\"执行Chrome DevTools Protocol语句，执行前等待页面加载完毕\n        :param cmd: 协议项目\n        :param cmd_args: 参数\n        :return: 执行的结果\n        \"\"\"\n        ...\n\n    def run_js(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any:\n        \"\"\"运行javascript代码\n        :param script: js文本或js文件路径\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\n        :param as_expr: 是否作为表达式运行，为True时args无效\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script设置\n        :return: 运行的结果\n        \"\"\"\n        ...\n\n    def run_js_loaded(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any:\n        \"\"\"运行javascript代码，执行前等待页面加载完毕\n        :param script: js文本或js文件路径\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\n        :param as_expr: 是否作为表达式运行，为True时args无效\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script属性值\n        :return: 运行的结果\n        \"\"\"\n        ...\n\n    def _run_js(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any:\n        \"\"\"运行javascript代码\n        :param script: js文本或js文件路径\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\n        :param as_expr: 是否作为表达式运行，为True时args无效\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script设置\n        :return: 运行的结果\n        \"\"\"\n        ...\n\n    def _run_js_loaded(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any:\n        \"\"\"运行javascript代码，执行前等待页面加载完毕\n        :param script: js文本或js文件路径\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\n        :param as_expr: 是否作为表达式运行，为True时args无效\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script属性值\n        :return: 运行的结果\n        \"\"\"\n        ...\n\n    def run_async_js(self, script: Union[str, Path], *args, as_expr: bool = False) -> None:\n        \"\"\"以异步方式执行js代码或js文件路径\n        :param script: js文本\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\n        :param as_expr: 是否作为表达式运行，为True时args无效\n        :return: None\n        \"\"\"\n        ...\n\n    def get(self, url: str, show_errmsg: bool = False, retry: int = None,\n            interval: float = None, timeout: float = None) -> Union[None, bool]:\n        \"\"\"访问url\n        :param url: 目标url\n        :param show_errmsg: 是否显示和抛出异常\n        :param retry: 重试次数，为None时使用页面对象retry_times属性值\n        :param interval: 重试间隔（秒），为None时使用页面对象retry_interval属性值\n        :param timeout: 连接超时时间（秒），为None时使用页面对象timeouts.page_load属性值\n        :return: 目标url是否可用\n        \"\"\"\n        ...\n\n    def cookies(self, all_domains: bool = False, all_info: bool = False) -> CookiesList:\n        \"\"\"返回cookies信息\n        :param all_domains: 是否返回所有域的cookies\n        :param all_info: 是否返回所有信息，为False时只返回name、value、domain\n        :return: cookies信息\n        \"\"\"\n        ...\n\n    def ele(self,\n            locator: Union[Tuple[str, str], str, ChromiumElement, ChromiumFrame],\n            index: int = 1,\n            timeout: float = None) -> ChromiumElement:\n        \"\"\"获取一个符合条件的元素对象\n        :param locator: 定位符或元素对象\n        :param index: 获取第几个元素，从1开始，可传入负数获取倒数第几个\n        :param timeout: 查找超时时间（秒），默认与页面等待时间一致\n        :return: ChromiumElement对象\n        \"\"\"\n        ...\n\n    def eles(self,\n             locator: Union[Tuple[str, str], str],\n             timeout: float = None) -> ChromiumElementsList:\n        \"\"\"获取所有符合条件的元素对象\n        :param locator: 定位符或元素对象\n        :param timeout: 查找超时时间（秒），默认与页面等待时间一致\n        :return: ChromiumElement对象组成的列表\n        \"\"\"\n        ...\n\n    def s_ele(self,\n              locator: Union[Tuple[str, str], str] = None,\n              index: int = 1,\n              timeout: float = None) -> SessionElement:\n        \"\"\"查找一个符合条件的元素以SessionElement形式返回，处理复杂页面时效率很高\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\n        :return: SessionElement对象或属性、文本\n        \"\"\"\n        ...\n\n    def s_eles(self,\n               locator: Union[Tuple[str, str], str],\n               timeout: float = None) -> SessionElementsList:\n        \"\"\"查找所有符合条件的元素以SessionElement列表形式返回\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\n        :return: SessionElement对象组成的列表\n        \"\"\"\n        ...\n\n    def _find_elements(self,\n                       locator: Union[Tuple[str, str], str, ChromiumElement, ChromiumFrame],\n                       timeout: float,\n                       index: Optional[int] = 1,\n                       relative: bool = False,\n                       raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, ChromiumElementsList]:\n        \"\"\"执行元素查找\n        :param locator: 定位符或元素对象\n        :param timeout: 查找超时时间（秒）\n        :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\n        :param relative: MixTab用的表示是否相对定位的参数\n        :param raise_err: 找不到元素是是否抛出异常，为None时根据全局设置\n        :return: ChromiumElement对象或元素对象组成的列表\n        \"\"\"\n        ...\n\n    def refresh(self, ignore_cache: bool = False) -> None:\n        \"\"\"刷新当前页面\n        :param ignore_cache: 是否忽略缓存\n        :return: None\n        \"\"\"\n        ...\n\n    def forward(self, steps: int = 1) -> None:\n        \"\"\"在浏览历史中前进若干步\n        :param steps: 前进步数\n        :return: None\n        \"\"\"\n        ...\n\n    def back(self, steps: int = 1) -> None:\n        \"\"\"在浏览历史中后退若干步\n        :param steps: 后退步数\n        :return: None\n        \"\"\"\n        ...\n\n    def _forward_or_back(self, steps: int) -> None:\n        \"\"\"执行浏览器前进或后退，会跳过url相同的历史记录\n        :param steps: 步数\n        :return: None\n        \"\"\"\n        ...\n\n    def stop_loading(self) -> None:\n        \"\"\"页面停止加载\"\"\"\n        ...\n\n    def remove_ele(self, loc_or_ele: Union[ChromiumElement, ChromiumFrame, str, Tuple[str, str]]) -> None:\n        \"\"\"从页面上删除一个元素\n        :param loc_or_ele: 元素对象或定位符\n        :return: None\n        \"\"\"\n        ...\n\n    def add_ele(self,\n                html_or_info: Union[str, Tuple[str, dict]],\n                insert_to: Union[ChromiumElement, str, Tuple[str, str], None] = None,\n                before: Union[ChromiumElement, str, Tuple[str, str], None] = None) -> Union[\n        ChromiumElement, ChromiumFrame]:\n        \"\"\"新建一个元素\n        :param html_or_info: 新元素的html文本或信息。信息格式为：(tag, {attr1: value, ...})\n        :param insert_to: 插入到哪个元素中，可接收元素对象和定位符，为None且为html添加到body，不为html不插入\n        :param before: 在哪个子节点前面插入，可接收对象和定位符，为None插入到父元素末尾\n        :return: 元素对象\n        \"\"\"\n        ...\n\n    def get_frame(self,\n                  loc_ind_ele: Union[str, int, tuple, ChromiumFrame, ChromiumElement],\n                  timeout: float = None) -> ChromiumFrame:\n        \"\"\"获取页面中一个frame对象\n        :param loc_ind_ele: 定位符、iframe序号、ChromiumFrame对象，序号从1开始，可传入负数获取倒数第几个\n        :param timeout: 查找元素超时时间（秒）\n        :return: ChromiumFrame对象\n        \"\"\"\n        ...\n\n    def get_frames(self,\n                   locator: Union[str, tuple] = None,\n                   timeout: float = None) -> ChromiumElementsList:\n        \"\"\"获取所有符合条件的frame对象\n        :param locator: 定位符，为None时返回所有\n        :param timeout: 查找超时时间（秒）\n        :return: ChromiumFrame对象组成的列表\n        \"\"\"\n        ...\n\n    def session_storage(self, item: str = None) -> Union[str, dict, None]:\n        \"\"\"返回sessionStorage信息，不设置item则获取全部\n        :param item: 要获取的项，不设置则返回全部\n        :return: sessionStorage一个或所有项内容\n        \"\"\"\n        ...\n\n    def local_storage(self, item: str = None) -> Union[str, dict, None]:\n        \"\"\"返回localStorage信息，不设置item则获取全部\n        :param item: 要获取的项目，不设置则返回全部\n        :return: localStorage一个或所有项内容\n        \"\"\"\n        ...\n\n    def get_screenshot(self, path: [str, Path] = None, name: str = None, as_bytes: PIC_TYPE = None,\n                       as_base64: PIC_TYPE = None, full_page: bool = False, left_top: Tuple[int, int] = None,\n                       right_bottom: Tuple[int, int] = None) -> Union[str, bytes]:\n        \"\"\"对页面进行截图，可对整个网页、可见网页、指定范围截图。对可视范围外截图需要90以上版本浏览器支持\n        :param path: 保存路径\n        :param name: 完整文件名，后缀可选 'jpg','jpeg','png','webp'\n        :param as_bytes: 是否以字节形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数和as_base64参数无效\n        :param as_base64: 是否以base64字符串形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数无效\n        :param full_page: 是否整页截图，为True截取整个网页，为False截取可视窗口\n        :param left_top: 截取范围左上角坐标\n        :param right_bottom: 截取范围右下角角坐标\n        :return: 图片完整路径或字节文本\n        \"\"\"\n        ...\n\n    def add_init_js(self, script: str) -> str:\n        \"\"\"添加初始化脚本，在页面加载任何脚本前执行\n        :param script: js文本\n        :return: 添加的脚本的id\n        \"\"\"\n        ...\n\n    def remove_init_js(self, script_id: str = None) -> None:\n        \"\"\"删除初始化脚本，js_id传入None时删除所有\n        :param script_id: 脚本的id\n        :return: None\n        \"\"\"\n        ...\n\n    def clear_cache(self, session_storage: bool = True, local_storage: bool = True, cache: bool = True,\n                    cookies: bool = True) -> None:\n        \"\"\"清除缓存，可选要清除的项\n        :param session_storage: 是否清除sessionStorage\n        :param local_storage: 是否清除localStorage\n        :param cache: 是否清除cache\n        :param cookies: 是否清除cookies\n        :return: None\n        \"\"\"\n        ...\n\n    def disconnect(self) -> None:\n        \"\"\"断开与页面的连接，不关闭页面\"\"\"\n        ...\n\n    def reconnect(self, wait: float = 0) -> None:\n        \"\"\"断开与页面原来的页面，重新建立连接\n        :param wait: 断开后等待若干秒再连接\n        :return: None\n        \"\"\"\n        ...\n\n    def handle_alert(self,\n                     accept: Optional[bool] = True,\n                     send: str = None,\n                     timeout: float = None,\n                     next_one: bool = False) -> Union[str, False]:\n        \"\"\"处理提示框，可以自动等待提示框出现\n        :param accept: True表示确认，False表示取消，为None不会按按钮但依然返回文本值\n        :param send: 处理prompt提示框时可输入文本\n        :param timeout: 等待提示框出现的超时时间（秒），为None则使用self.timeout属性的值\n        :param next_one: 是否处理下一个出现的提示框，为True时timeout参数无效\n        :return: 提示框内容文本，未等到提示框则返回False\n        \"\"\"\n        ...\n\n    def _handle_alert(self,\n                      accept: Optional[bool] = True,\n                      send: str = None,\n                      timeout: float = None,\n                      next_one: bool = False) -> Union[str, False]:\n        \"\"\"处理提示框，可以自动等待提示框出现\n        :param accept: True表示确认，False表示取消，其它值不会按按钮但依然返回文本值\n        :param send: 处理prompt提示框时可输入文本\n        :param timeout: 等待提示框出现的超时时间（秒），为None则使用self.timeout属性的值\n        :param next_one: 是否处理下一个出现的提示框，为True时timeout参数无效\n        :return: 提示框内容文本，未等到提示框则返回False\n        \"\"\"\n        ...\n\n    def _on_alert_open(self, **kwargs):\n        \"\"\"alert出现时触发的方法\"\"\"\n        ...\n\n    def _on_alert_close(self, **kwargs):\n        \"\"\"alert关闭时触发的方法\"\"\"\n        ...\n\n    def _wait_loaded(self, timeout: float = None) -> bool:\n        \"\"\"等待页面加载完成，超时触发停止加载\n        :param timeout: 超时时间（秒）\n        :return: 是否成功，超时返回False\n        \"\"\"\n        ...\n\n    def _d_connect(self, to_url: str, times: int = 0, interval: float = 1, show_errmsg: bool = False,\n                   timeout: float = None) -> Union[bool, None]:\n        \"\"\"尝试连接，重试若干次\n        :param to_url: 要访问的url\n        :param times: 重试次数\n        :param interval: 重试间隔（秒）\n        :param show_errmsg: 是否抛出异常\n        :param timeout: 连接超时时间（秒）\n        :return: 是否成功，返回None表示不确定\n        \"\"\"\n        ...\n\n    def _get_screenshot(self, path: [str, Path] = None, name: str = None, as_bytes: PIC_TYPE = None,\n                        as_base64: PIC_TYPE = None, full_page: bool = False, left_top: Tuple[float, float] = None,\n                        right_bottom: Tuple[float, float] = None, ele: ChromiumElement = None) -> Union[str, bytes]:\n        \"\"\"对页面进行截图，可对整个网页、可见网页、指定范围截图。对可视范围外截图需要90以上版本浏览器支持\n        :param path: 保存路径\n        :param name: 完整文件名，后缀可选 'jpg','jpeg','png','webp'\n        :param as_bytes: 是否以字节形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数和as_base64参数无效\n        :param as_base64: 是否以base64字符串形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数无效\n        :param full_page: 是否整页截图，为True截取整个网页，为False截取可视窗口\n        :param left_top: 截取范围左上角坐标\n        :param right_bottom: 截取范围右下角角坐标\n        :param ele: 为异域iframe内元素截图设置\n        :return: 图片完整路径或字节文本\n        \"\"\"\n        ...\n\n\nclass Timeout(object):\n    \"\"\"用于保存d模式timeout信息的类\"\"\"\n    base: float = ...\n    page_load: float = ...\n    script: float = ...\n\n    def __init__(self, base=None, page_load=None, script=None):\n        \"\"\"\n        :param base: 默认超时时间\n        :param page_load: 页面加载超时时间\n        :param script: js超时时间\n        \"\"\"\n        ...\n\n    @property\n    def as_dict(self) -> dict:\n        \"\"\"以dict格式返回timeout设置\"\"\"\n        ...\n\n\nclass Alert(object):\n    \"\"\"用于保存alert信息的类\"\"\"\n    activated: Optional[bool] = ...\n    text: Optional[str] = ...\n    type: Optional[str] = ...\n    defaultPrompt: Optional[str] = ...\n    response_accept: Optional[str] = ...\n    response_text: Optional[str] = ...\n    handle_next: Optional[bool] = ...\n    next_text: Optional[str] = ...\n    auto: Optional[bool] = ...\n\n    def __init__(self, auto: bool = None): ...\n"
  },
  {
    "path": "DrissionPage/_pages/chromium_frame.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom copy import copy\r\nfrom re import search, findall, DOTALL\r\nfrom time import sleep, perf_counter\r\n\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._units.listener import FrameListener\r\nfrom .._units.rect import FrameRect\r\nfrom .._units.scroller import FrameScroller\r\nfrom .._units.setter import ChromiumFrameSetter\r\nfrom .._units.states import FrameStates\r\nfrom .._units.waiter import FrameWaiter\r\nfrom ..errors import ContextLostError, ElementLostError, PageDisconnectedError, JavaScriptError\r\n\r\n\r\nclass ChromiumFrame(ChromiumBase):\r\n    _Frames = {}\r\n\r\n    def __new__(cls, owner, ele, info=None):\r\n        fid = info['node']['frameId'] if info else owner._run_cdp('DOM.describeNode',\r\n                                                                  backendNodeId=ele._backend_id)['node']['frameId']\r\n        if _S.singleton_tab_obj and fid in cls._Frames:\r\n            r = cls._Frames[fid]\r\n            while not hasattr(r, '_type') or r._type != 'ChromiumFrame':\r\n                sleep(.01)\r\n            return r\r\n        r = object.__new__(cls)\r\n        cls._Frames[fid] = r\r\n        return r\r\n\r\n    def __init__(self, owner, ele, info=None):\r\n        if _S.singleton_tab_obj and hasattr(self, '_created'):\r\n            return\r\n        self._created = True\r\n\r\n        self._tab = owner._tab\r\n        self._target_page = owner\r\n        self._backend_id = ele._backend_id\r\n        self._frame_ele = ele\r\n        self._reloading = False\r\n\r\n        try:\r\n            node = info['node'] if info else owner._run_cdp('DOM.describeNode', backendNodeId=ele._backend_id)['node']\r\n            self._frame_id = node['frameId']\r\n            if self._is_inner_frame():\r\n                self._is_diff_domain = False\r\n                self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])\r\n                super().__init__(owner.browser, owner.driver.id)\r\n            else:\r\n                self._is_diff_domain = True\r\n                delattr(self, '_frame_id')\r\n                super().__init__(owner.browser, node['frameId'])\r\n                obj_id = super()._run_js('document;', as_expr=True)['objectId']\r\n                self.doc_ele = ChromiumElement(self, obj_id=obj_id)\r\n\r\n        except Exception as e:\r\n            ChromiumFrame._Frames.pop(self._frame_id, None)\r\n            raise e\r\n\r\n        self._type = 'ChromiumFrame'\r\n\r\n    def __call__(self, locator, index=1, timeout=None):\r\n        return self.ele(locator, index=index, timeout=timeout)\r\n\r\n    def __repr__(self):\r\n        attrs = [f\"{k}='{v}'\" for k, v in self._frame_ele.attrs.items()]\r\n        return f'<ChromiumFrame {self.frame_ele.tag} {\" \".join(attrs)}>'\r\n\r\n    def __eq__(self, other):\r\n        return self._frame_id == getattr(other, '_frame_id', None)\r\n\r\n    def _d_set_runtime_settings(self):\r\n        if not hasattr(self, '_timeouts'):\r\n            self._timeouts = copy(self._target_page.timeouts)\r\n            self.retry_times = self._target_page.retry_times\r\n            self.retry_interval = self._target_page.retry_interval\r\n            self._download_path = self._target_page.download_path\r\n            self._auto_handle_alert = self._target_page._auto_handle_alert\r\n        self._load_mode = self._target_page._load_mode if not self._is_diff_domain else 'normal'\r\n\r\n    def _driver_init(self, target_id, is_init=True):\r\n        try:\r\n            super()._driver_init(target_id)\r\n        except:\r\n            if not self.browser._ws_only:\r\n                self.browser._driver.get(f'http://{self._browser.address}/json')\r\n            super()._driver_init(target_id)\r\n        self._driver.set_callback('Inspector.detached', self._onInspectorDetached, immediate=True)\r\n        self._driver.set_callback('Page.frameDetached', None)\r\n        self._driver.set_callback('Page.frameDetached', self._onFrameDetached, immediate=True)\r\n\r\n    def _reload(self):\r\n        self._is_loading = True\r\n        self._reloading = True\r\n        self._doc_got = False\r\n\r\n        self._driver.stop()\r\n        try:\r\n            self._frame_ele = ChromiumElement(self._target_page, backend_id=self._backend_id)\r\n            end_time = perf_counter() + 2\r\n            while perf_counter() < end_time:\r\n                node = self._target_page._run_cdp('DOM.describeNode', backendNodeId=self._frame_ele._backend_id)['node']\r\n                if 'frameId' in node:\r\n                    break\r\n                sleep(.05)\r\n\r\n            else:\r\n                return\r\n\r\n        except (ElementLostError, PageDisconnectedError):\r\n            return\r\n\r\n        if self._is_inner_frame():\r\n            self._is_diff_domain = False\r\n            self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])\r\n            self._frame_id = node['frameId']\r\n            if self._listener:\r\n                self._listener._to_target(self._target_page.tab_id, self._browser._ws_address, self)\r\n            super().__init__(self._browser, self._target_page.tab_id)\r\n\r\n        else:\r\n            self._is_diff_domain = True\r\n            if self._listener:\r\n                self._listener._to_target(node['frameId'], self._browser.address, self)\r\n            end_time = perf_counter() + self.timeouts.page_load\r\n            super().__init__(self._browser, node['frameId'])\r\n            timeout = end_time - perf_counter()\r\n            if timeout <= 0:\r\n                timeout = .5\r\n            self._wait_loaded(timeout)\r\n\r\n        self._is_loading = False\r\n        self._reloading = False\r\n\r\n    def _get_document(self, timeout=10):\r\n        if self._is_reading:\r\n            return\r\n\r\n        self._is_reading = True\r\n        try:\r\n            if self._is_diff_domain is False:\r\n                node = self._target_page._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']\r\n                self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])\r\n\r\n            else:\r\n                timeout = max(timeout, 2)\r\n                b_id = self._run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']\r\n                self.doc_ele = ChromiumElement(self, backend_id=b_id)\r\n\r\n            self._root_id = self.doc_ele._obj_id\r\n\r\n            r = self._run_cdp('Page.getFrameTree', _ignore=PageDisconnectedError)\r\n            for i in findall(r\"'id': '(.*?)'\", str(r)):\r\n                self.browser._frames[i] = self.tab_id\r\n                return True\r\n\r\n        except:\r\n            return False\r\n\r\n        finally:\r\n            if not self._reloading:  # 阻止reload时标识\r\n                self._is_loading = False\r\n            self._is_reading = False\r\n\r\n    def _onInspectorDetached(self, **kwargs):\r\n        # 异域转同域或退出\r\n        self._reload()\r\n\r\n    def _onFrameDetached(self, **kwargs):\r\n        # 同域变异域\r\n        self.browser._frames.pop(kwargs['frameId'], None)\r\n        ChromiumFrame._Frames.pop(kwargs['frameId'], None)\r\n        if kwargs['frameId'] == self._frame_id:\r\n            self._reload()\r\n\r\n    # ----------挂件----------\r\n    @property\r\n    def scroll(self):\r\n        self.wait.doc_loaded()\r\n        if self._scroll is None:\r\n            self._scroll = FrameScroller(self)\r\n        return self._scroll\r\n\r\n    @property\r\n    def set(self):\r\n        if self._set is None:\r\n            self._set = ChromiumFrameSetter(self)\r\n        return self._set\r\n\r\n    @property\r\n    def states(self):\r\n        if self._states is None:\r\n            self._states = FrameStates(self)\r\n        return self._states\r\n\r\n    @property\r\n    def wait(self):\r\n        if self._wait is None:\r\n            self._wait = FrameWaiter(self)\r\n        return self._wait\r\n\r\n    @property\r\n    def rect(self):\r\n        if self._rect is None:\r\n            self._rect = FrameRect(self)\r\n        return self._rect\r\n\r\n    @property\r\n    def listen(self):\r\n        if self._listener is None:\r\n            self._listener = FrameListener(self)\r\n        return self._listener\r\n\r\n    # ----------挂件结束----------\r\n\r\n    @property\r\n    def _obj_id(self):\r\n        return self.frame_ele._obj_id\r\n\r\n    @property\r\n    def _node_id(self):\r\n        return self.frame_ele._node_id\r\n\r\n    @property\r\n    def owner(self):\r\n        return self.frame_ele.owner\r\n\r\n    @property\r\n    def frame_ele(self):\r\n        return self._frame_ele\r\n\r\n    @property\r\n    def tag(self):\r\n        return self.frame_ele.tag\r\n\r\n    @property\r\n    def url(self):\r\n        try:\r\n            return self.doc_ele._run_js('return this.location.href;')\r\n        except JavaScriptError:\r\n            return None\r\n\r\n    @property\r\n    def html(self):\r\n        tag = self.tag\r\n        out_html = self._target_page._run_cdp('DOM.getOuterHTML', backendNodeId=self.frame_ele._backend_id)['outerHTML']\r\n        sign = search(rf'<{tag}.*?>', out_html, DOTALL).group(0)\r\n        return f'{sign}{self.inner_html}</{tag}>'\r\n\r\n    @property\r\n    def inner_html(self):\r\n        return self.doc_ele._run_js('return this.documentElement.outerHTML;')\r\n\r\n    @property\r\n    def link(self):\r\n        return self.frame_ele.link\r\n\r\n    @property\r\n    def title(self):\r\n        r = self._ele('t:title', raise_err=False)\r\n        return r.text if r else None\r\n\r\n    @property\r\n    def attrs(self):\r\n        return self.frame_ele.attrs\r\n\r\n    @property\r\n    def active_ele(self):\r\n        return self.doc_ele._run_js('return this.activeElement;')\r\n\r\n    @property\r\n    def xpath(self):\r\n        return self.frame_ele.xpath\r\n\r\n    @property\r\n    def css_path(self):\r\n        return self.frame_ele.css_path\r\n\r\n    @property\r\n    def tab(self):\r\n        return self._tab\r\n\r\n    @property\r\n    def tab_id(self):\r\n        return self.tab.tab_id\r\n\r\n    @property\r\n    def download_path(self):\r\n        return self._download_path\r\n\r\n    @property\r\n    def sr(self):\r\n        return self.frame_ele.sr\r\n\r\n    @property\r\n    def shadow_root(self):\r\n        return self.frame_ele.sr\r\n\r\n    @property\r\n    def child_count(self):\r\n        return int(self._ele('xpath:count(./*)'))\r\n\r\n    @property\r\n    def _js_ready_state(self):\r\n        if self._is_diff_domain:\r\n            return super()._js_ready_state\r\n\r\n        else:\r\n            try:\r\n                return self.doc_ele._run_js('return this.readyState;')\r\n            except ContextLostError:\r\n                try:\r\n                    node = self._run_cdp('DOM.describeNode', backendNodeId=self.frame_ele._backend_id)['node']\r\n                    doc = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])\r\n                    return doc._run_js('return this.readyState;')\r\n                except:\r\n                    return None\r\n\r\n    def refresh(self):\r\n        self.doc_ele._run_js('this.location.reload();')\r\n\r\n    def property(self, name):\r\n        return self.frame_ele.property(name)\r\n\r\n    def attr(self, name):\r\n        return self.frame_ele.attr(name)\r\n\r\n    def remove_attr(self, name):\r\n        self.frame_ele.remove_attr(name)\r\n\r\n    def style(self, style, pseudo_ele=''):\r\n        return self.frame_ele.style(style=style, pseudo_ele=pseudo_ele)\r\n\r\n    def run_js(self, script, *args, as_expr=False, timeout=None):\r\n        return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)\r\n\r\n    def _run_js(self, script, *args, as_expr=False, timeout=None):\r\n        if script.startswith('this.scrollIntoView'):\r\n            return self.frame_ele._run_js(script, *args, as_expr=as_expr, timeout=timeout)\r\n        else:\r\n            return self.doc_ele._run_js(script, *args, as_expr=as_expr, timeout=timeout)\r\n\r\n    def parent(self, level_or_loc=1, index=1, timeout=0):\r\n        return self.frame_ele.parent(level_or_loc, index, timeout=timeout)\r\n\r\n    def prev(self, locator='', index=1, timeout=0, ele_only=True):\r\n        return self.frame_ele.prev(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def next(self, locator='', index=1, timeout=0, ele_only=True):\r\n        return self.frame_ele.next(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def before(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return self.frame_ele.before(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def after(self, locator='', index=1, timeout=None, ele_only=True):\r\n        return self.frame_ele.after(locator, index, timeout, ele_only=ele_only)\r\n\r\n    def prevs(self, locator='', timeout=0, ele_only=True):\r\n        return self.frame_ele.prevs(locator, timeout, ele_only=ele_only)\r\n\r\n    def nexts(self, locator='', timeout=0, ele_only=True):\r\n        return self.frame_ele.nexts(locator, timeout, ele_only=ele_only)\r\n\r\n    def befores(self, locator='', timeout=None, ele_only=True):\r\n        return self.frame_ele.befores(locator, timeout, ele_only=ele_only)\r\n\r\n    def afters(self, locator='', timeout=None, ele_only=True):\r\n        return self.frame_ele.afters(locator, timeout, ele_only=ele_only)\r\n\r\n    def get_screenshot(self, path=None, name=None, as_bytes=None, as_base64=None):\r\n        return self.frame_ele.get_screenshot(path=path, name=name, as_bytes=as_bytes, as_base64=as_base64)\r\n\r\n    def _get_screenshot(self, path=None, name=None, as_bytes: [bool, str] = None, as_base64: [bool, str] = None,\r\n                        full_page=False, left_top=None, right_bottom=None, ele=None):\r\n        if not self._is_diff_domain:\r\n            return super().get_screenshot(path=path, name=name, as_bytes=as_bytes, as_base64=as_base64,\r\n                                          full_page=full_page, left_top=left_top, right_bottom=right_bottom)\r\n\r\n        if as_bytes:\r\n            if as_bytes is True:\r\n                pic_type = 'png'\r\n            else:\r\n                if as_bytes not in ('jpg', 'jpeg', 'png', 'webp'):\r\n                    raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'as_bytes',\r\n                                     ALLOW_VAL='\"jpg\", \"jpeg\", \"png\", \"webp\"', CURR_VAL=as_bytes))\r\n                pic_type = 'jpeg' if as_bytes == 'jpg' else as_bytes\r\n\r\n        elif as_base64:\r\n            if as_base64 is True:\r\n                pic_type = 'png'\r\n            else:\r\n                if as_base64 not in ('jpg', 'jpeg', 'png', 'webp'):\r\n                    raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'as_base64',\r\n                                     ALLOW_VAL='\"jpg\", \"jpeg\", \"png\", \"webp\"', CURR_VAL=as_base64))\r\n                pic_type = 'jpeg' if as_base64 == 'jpg' else as_base64\r\n\r\n        else:\r\n            path = str(path).rstrip('\\\\/') if path else '.'\r\n            if path and path.endswith(('.jpg', '.jpeg', '.png', '.webp')):\r\n                pic_type = path.rsplit('.', 1)[-1]\r\n\r\n            elif name and name.endswith(('.jpg', '.jpeg', '.png', '.webp')):\r\n                pic_type = name.rsplit('.', 1)[-1]\r\n\r\n            else:\r\n                pic_type = 'jpeg'\r\n\r\n            if pic_type == 'jpg':\r\n                pic_type = 'jpeg'\r\n\r\n        self.frame_ele.scroll.to_see(center=True)\r\n        self.scroll.to_see(ele, center=True)\r\n        cx, cy = ele.rect.viewport_location\r\n        w, h = ele.rect.size\r\n        img_data = f'data:image/{pic_type};base64,{self.frame_ele.get_screenshot(as_base64=True)}'\r\n        body = self.tab('t:body')\r\n        first_child = body('c::first-child')\r\n        if not isinstance(first_child, ChromiumElement):\r\n            first_child = first_child.frame_ele\r\n        js = f'''\r\n        img = document.createElement('img');\r\n        img.src = \"{img_data}\";\r\n        img.style.setProperty(\"z-index\",9999999);\r\n        img.style.setProperty(\"position\",\"fixed\");\r\n        arguments[0].insertBefore(img, this);\r\n        return img;'''\r\n        new_ele = first_child._run_js(js, body)\r\n        new_ele.scroll.to_see(center=True)\r\n        top = int(self.frame_ele.style('border-top').split('px')[0])\r\n        left = int(self.frame_ele.style('border-left').split('px')[0])\r\n\r\n        r = self.tab._run_cdp('Page.getLayoutMetrics')['visualViewport']\r\n        sx = r['pageX']\r\n        sy = r['pageY']\r\n        r = self.tab.get_screenshot(path=path, name=name, as_bytes=as_bytes, as_base64=as_base64,\r\n                                    left_top=(cx + left + sx, cy + top + sy),\r\n                                    right_bottom=(cx + w + left + sx, cy + h + top + sy))\r\n        self.tab.remove_ele(new_ele)\r\n        return r\r\n\r\n    def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):\r\n        if isinstance(locator, ChromiumElement):\r\n            return locator\r\n        self.wait.doc_loaded()\r\n        return self.doc_ele._ele(locator, index=index, timeout=timeout,\r\n                                 raise_err=raise_err) if index is not None else self.doc_ele.eles(locator, timeout)\r\n\r\n    def _is_inner_frame(self):\r\n        return self._frame_id in str(self._target_page._run_cdp('Page.getFrameTree')['frameTree'])\r\n"
  },
  {
    "path": "DrissionPage/_pages/chromium_frame.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Union, Tuple, List, Any, Optional, Literal\r\n\r\nfrom .chromium_base import ChromiumBase\r\nfrom .chromium_tab import ChromiumTab\r\nfrom .mix_tab import MixTab\r\nfrom .._elements.chromium_element import ChromiumElement, ShadowRoot\r\nfrom .._functions.elements import ChromiumElementsList\r\nfrom .._units.listener import FrameListener\r\nfrom .._units.rect import FrameRect\r\nfrom .._units.scroller import FrameScroller\r\nfrom .._units.setter import ChromiumFrameSetter\r\nfrom .._units.states import FrameStates\r\nfrom .._units.waiter import FrameWaiter\r\n\r\n\r\nclass ChromiumFrame(ChromiumBase):\r\n    _Frames: dict = ...\r\n    _target_page: Union[ChromiumTab, ChromiumFrame] = ...\r\n    _tab: Union[MixTab, ChromiumTab] = ...\r\n    _set: ChromiumFrameSetter = ...\r\n    _frame_ele: ChromiumElement = ...\r\n    _backend_id: int = ...\r\n    _doc_ele: ChromiumElement = ...\r\n    _is_diff_domain: bool = ...\r\n    doc_ele: ChromiumElement = ...\r\n    _states: FrameStates = ...\r\n    _reloading: bool = ...\r\n    _rect: Optional[FrameRect] = ...\r\n    _listener: FrameListener = ...\r\n\r\n    def __init__(self,\r\n                 owner: Union[ChromiumTab, ChromiumFrame],\r\n                 ele: ChromiumElement,\r\n                 info: dict = None):\r\n        \"\"\"\r\n        :param owner: frame所在的页面对象\r\n        :param ele: frame所在元素\r\n        :param info: frame所在元素信息\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self,\r\n                 locator: Union[Tuple[str, str], str],\r\n                 index: int = 1,\r\n                 timeout: float = None) -> ChromiumElement:\r\n        \"\"\"在内部查找元素\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 超时时间（秒）\r\n        :return: ChromiumElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def __repr__(self) -> str: ...\r\n\r\n    def __eq__(self, other: ChromiumFrame) -> bool: ...\r\n\r\n    def _d_set_runtime_settings(self) -> None:\r\n        \"\"\"重写设置浏览器运行参数方法\"\"\"\r\n        ...\r\n\r\n    def _driver_init(self, target_id: str, is_init: bool = True) -> None:\r\n        \"\"\"避免出现服务器500错误\r\n        :param target_id: 要跳转到的target id\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _reload(self) -> None:\r\n        \"\"\"重新获取document\"\"\"\r\n        ...\r\n\r\n    def _get_document(self, timeout: float = 10) -> bool:\r\n        \"\"\"刷新cdp使用的document数据\r\n        :param timeout: 超时时间（秒）\r\n        :return: 是否获取成功\r\n        \"\"\"\r\n        ...\r\n\r\n    def _onFrameStoppedLoading(self, **kwargs): ...\r\n\r\n    def _onInspectorDetached(self, **kwargs): ...\r\n\r\n    @property\r\n    def scroll(self) -> FrameScroller:\r\n        \"\"\"返回用于滚动的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def set(self) -> ChromiumFrameSetter:\r\n        \"\"\"返回用于设置的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def states(self) -> FrameStates:\r\n        \"\"\"返回用于获取状态信息的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def wait(self) -> FrameWaiter:\r\n        \"\"\"返回用于等待的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def rect(self) -> FrameRect:\r\n        \"\"\"返回获取坐标和大小的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def listen(self) -> FrameListener:\r\n        \"\"\"返回用于聆听数据包的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def _obj_id(self) -> str: ...\r\n\r\n    @property\r\n    def _node_id(self) -> int: ...\r\n\r\n    @property\r\n    def owner(self) -> ChromiumBase:\r\n        \"\"\"返回所属页面对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def frame_ele(self) -> ChromiumElement:\r\n        \"\"\"返回总页面上的frame元素\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def tag(self) -> str:\r\n        \"\"\"返回元素tag\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def url(self) -> str:\r\n        \"\"\"返回frame当前访问的url\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def html(self) -> str:\r\n        \"\"\"返回元素outerHTML文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def inner_html(self) -> str:\r\n        \"\"\"返回元素innerHTML文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def link(self) -> str:\r\n        \"\"\"返回href或src绝对url\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def title(self) -> str:\r\n        \"\"\"返回页面title\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def attrs(self) -> dict:\r\n        \"\"\"返回frame元素所有attribute属性\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def active_ele(self) -> ChromiumElement:\r\n        \"\"\"返回当前焦点所在元素\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def xpath(self) -> str:\r\n        \"\"\"返回frame的xpath绝对路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def css_path(self) -> str:\r\n        \"\"\"返回frame的css selector绝对路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def tab(self) -> Union[ChromiumTab, MixTab]:\r\n        \"\"\"返回frame所在的tab对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def tab_id(self) -> str:\r\n        \"\"\"返回frame所在tab的id\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def download_path(self) -> str:\r\n        \"\"\"返回下载文件保存路径\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def sr(self) -> Union[None, ShadowRoot]:\r\n        \"\"\"返回iframe的shadow-root元素对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def shadow_root(self) -> Union[None, ShadowRoot]:\r\n        \"\"\"返回iframe的shadow-root元素对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def child_count(self) -> int:\r\n        \"\"\"返回直接子元素的个数\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def _js_ready_state(self) -> Literal['loading', 'interactive', 'complete']:\r\n        \"\"\"返回当前页面加载状态\"\"\"\r\n        ...\r\n\r\n    def refresh(self) -> None:\r\n        \"\"\"刷新frame页面\"\"\"\r\n        ...\r\n\r\n    def property(self, name: str) -> Union[str, None]:\r\n        \"\"\"返回frame元素一个property属性值\r\n        :param name: 属性名\r\n        :return: 属性值文本，没有该属性返回None\r\n        \"\"\"\r\n        ...\r\n\r\n    def attr(self, name: str) -> Union[str, None]:\r\n        \"\"\"返回frame元素一个attribute属性值\r\n        :param name: 属性名\r\n        :return: 属性值文本，没有该属性返回None\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove_attr(self, name: str) -> None:\r\n        \"\"\"删除frame元素attribute属性\r\n        :param name: 属性名\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def style(self, style: str, pseudo_ele: str = '') -> str:\r\n        \"\"\"返回frame元素样式属性值，可获取伪元素属性值\r\n        :param style: 样式属性名称\r\n        :param pseudo_ele: 伪元素名称（如有）\r\n        :return: 样式属性的值\r\n        \"\"\"\r\n        ...\r\n\r\n    def run_js(self,\r\n               script: str,\r\n               *args,\r\n               as_expr: bool = False,\r\n               timeout: float = None) -> Any:\r\n        \"\"\"运行javascript代码\r\n        :param script: js文本\r\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\r\n        :param as_expr: 是否作为表达式运行，为True时args无效\r\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script设置\r\n        :return: 运行的结果\r\n        \"\"\"\r\n        ...\r\n\r\n    def _run_js(self,\r\n                script: str,\r\n                *args,\r\n                as_expr: bool = False,\r\n                timeout: float = None) -> Any:\r\n        \"\"\"运行javascript代码\r\n        :param script: js文本\r\n        :param args: 参数，按顺序在js文本中对应arguments[0]、arguments[1]...\r\n        :param as_expr: 是否作为表达式运行，为True时args无效\r\n        :param timeout: js超时时间（秒），为None则使用页面timeouts.script设置\r\n        :return: 运行的结果\r\n        \"\"\"\r\n        ...\r\n\r\n    def parent(self,\r\n               level_or_loc: Union[Tuple[str, str], str, int] = 1,\r\n               index: int = 1,\r\n               timeout: float = 0) -> ChromiumElement:\r\n        \"\"\"返回上面某一级父元素，可指定层数或用查询语法定位\r\n        :param level_or_loc: 第几级父元素，1开始，或定位符\r\n        :param index: 当level_or_loc传入定位符，使用此参数选择第几个结果，1开始\r\n        :param timeout: 查找超时时间（秒）\r\n        :return: 上级元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def prev(self,\r\n             locator: Union[Tuple[str, str], str, int] = '',\r\n             index: int = 1,\r\n             timeout: float = 0,\r\n             ele_only: bool = True) -> Union[ChromiumElement, str]:\r\n        \"\"\"返回当前元素前面一个符合条件的同级元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 同级元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def next(self,\r\n             locator: Union[Tuple[str, str], str, int] = '',\r\n             index: int = 1,\r\n             timeout: float = 0,\r\n             ele_only: bool = True) -> Union[ChromiumElement, str]:\r\n        \"\"\"返回当前元素后面一个符合条件的同级元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 后面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 同级元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def before(self,\r\n               locator: Union[Tuple[str, str], str, int] = '',\r\n               index: int = 1,\r\n               timeout: float = None,\r\n               ele_only: bool = True) -> Union[ChromiumElement, str]:\r\n        \"\"\"返回文档中当前元素前面符合条件的一个元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 前面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def after(self,\r\n              locator: Union[Tuple[str, str], str, int] = '',\r\n              index: int = 1,\r\n              timeout: float = None,\r\n              ele_only: bool = True) -> Union[ChromiumElement, str]:\r\n        \"\"\"返回文档中此当前元素后面符合条件的一个元素，可用查询语法筛选，可指定返回筛选结果的第几个\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param index: 后面第几个查询结果，1开始\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素后面的某个元素或节点\r\n        \"\"\"\r\n        ...\r\n\r\n    def prevs(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              timeout: float = 0,\r\n              ele_only: bool = True) -> List[Union[ChromiumElement, str]]:\r\n        \"\"\"返回当前元素前面符合条件的同级元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 同级元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def nexts(self,\r\n              locator: Union[Tuple[str, str], str] = '',\r\n              timeout: float = 0,\r\n              ele_only: bool = True) -> List[Union[ChromiumElement, str]]:\r\n        \"\"\"返回当前元素后面符合条件的同级元素或节点组成的列表，可用查询语法筛选\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 同级元素或节点文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def befores(self,\r\n                locator: Union[Tuple[str, str], str] = '',\r\n                timeout: float = None,\r\n                ele_only: bool = True) -> List[Union[ChromiumElement, str]]:\r\n        \"\"\"返回文档中当前元素前面符合条件的元素或节点组成的列表，可用查询语法筛选\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def afters(self,\r\n               locator: Union[Tuple[str, str], str] = '',\r\n               timeout: float = None,\r\n               ele_only: bool = True) -> List[Union[ChromiumElement, str]]:\r\n        \"\"\"返回文档中当前元素后面符合条件的元素或节点组成的列表，可用查询语法筛选\r\n        查找范围不限同级元素，而是整个DOM文档\r\n        :param locator: 用于筛选的查询语法\r\n        :param timeout: 查找节点的超时时间（秒）\r\n        :param ele_only: 是否只获取元素，为False时把文本、注释节点也纳入\r\n        :return: 本元素前面的元素或节点组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_screenshot(self,\r\n                       path: [str, Path] = None,\r\n                       name: str = None,\r\n                       as_bytes: [bool, str] = None,\r\n                       as_base64: [bool, str] = None) -> Union[str, bytes]:\r\n        \"\"\"对页面进行截图，可对整个网页、可见网页、指定范围截图。对可视范围外截图需要90以上版本浏览器支持\r\n        :param path: 文件保存路径\r\n        :param name: 完整文件名，后缀可选 'jpg','jpeg','png','webp'\r\n        :param as_bytes: 是否以字节形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数和as_base64参数无效\r\n        :param as_base64: 是否以base64字符串形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数无效\r\n        :return: 图片完整路径或字节文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def _get_screenshot(self,\r\n                        path: [str, Path] = None,\r\n                        name: str = None,\r\n                        as_bytes: [bool, str] = None,\r\n                        as_base64: [bool, str] = None,\r\n                        full_page: bool = False,\r\n                        left_top: Tuple[int, int] = None,\r\n                        right_bottom: Tuple[int, int] = None,\r\n                        ele: ChromiumElement = None) -> Union[str, bytes]:\r\n        \"\"\"实现截图\r\n        :param path: 文件保存路径\r\n        :param name: 完整文件名，后缀可选 'jpg','jpeg','png','webp'\r\n        :param as_bytes: 是否以字节形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数和as_base64参数无效\r\n        :param as_base64: 是否以base64字符串形式返回图片，可选 'jpg','jpeg','png','webp'，生效时path参数无效\r\n        :param full_page: 是否整页截图，为True截取整个网页，为False截取可视窗口\r\n        :param left_top: 截取范围左上角坐标\r\n        :param right_bottom: 截取范围右下角角坐标\r\n        :param ele: 为异域iframe内元素截图设置\r\n        :return: 图片完整路径或字节文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def _find_elements(self,\r\n                       locator: Union[Tuple[str, str], str, ChromiumElement, ChromiumFrame],\r\n                       timeout: float,\r\n                       index: Optional[int] = 1,\r\n                       relative: bool = False,\r\n                       raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, None, ChromiumElementsList]:\r\n        \"\"\"在frame内查找单个元素\r\n        :param locator: 定位符或元素对象\r\n        :param timeout: 查找超时时间（秒）\r\n        :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\r\n        :param relative: MixTab用的表示是否相对定位的参数\r\n        :param raise_err: 找不到元素是是否抛出异常，为None时根据全局设置\r\n        :return: ChromiumElement对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _is_inner_frame(self) -> bool:\r\n        \"\"\"返回当前frame是否同域\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_pages/chromium_page.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom time import sleep\r\n\r\nfrom .._base.chromium import Chromium\r\nfrom .._functions.settings import Settings\r\nfrom .._functions.web import save_page\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._units.setter import ChromiumPageSetter\r\nfrom .._units.waiter import ChromiumPageWaiter\r\n\r\n\r\nclass ChromiumPage(ChromiumBase):\r\n    _PAGES = {}\r\n\r\n    def __new__(cls, addr_or_opts=None, tab_id=None, timeout=None):\r\n        # 即将废弃timeout\r\n        browser = Chromium(addr_or_opts=addr_or_opts)\r\n        if browser.id in cls._PAGES:\r\n            r = cls._PAGES[browser.id]\r\n            while not hasattr(r, '_frame_id'):\r\n                sleep(.05)\r\n            return r\r\n\r\n        r = object.__new__(cls)\r\n        r._browser = browser\r\n        cls._PAGES[browser.id] = r\r\n        return r\r\n\r\n    def __init__(self, addr_or_opts=None, tab_id=None, timeout=None):\r\n        # 即将废弃timeout\r\n        if hasattr(self, '_created'):\r\n            return\r\n        self._created = True\r\n\r\n        self.tab = self\r\n        super().__init__(self.browser, tab_id)\r\n        self._type = 'ChromiumPage'\r\n        self.set.timeouts(base=timeout)  # 即将废弃\r\n        self._tab = self\r\n        self._browser._dl_mgr._page_id = self.tab_id\r\n\r\n    def __repr__(self):\r\n        return f'<ChromiumPage browser_id={self.browser.id} tab_id={self.tab_id}>'\r\n\r\n    def _d_set_runtime_settings(self):\r\n        \"\"\"设置运行时用到的属性\"\"\"\r\n        self._timeouts = self.browser.timeouts\r\n        self._load_mode = self.browser._load_mode\r\n        self._download_path = self.browser.download_path\r\n        self.retry_times = self.browser.retry_times\r\n        self.retry_interval = self.browser.retry_interval\r\n        self._auto_handle_alert = self.browser._auto_handle_alert\r\n\r\n    @property\r\n    def set(self):\r\n        if self._set is None:\r\n            self._set = ChromiumPageSetter(self)\r\n        return self._set\r\n\r\n    @property\r\n    def wait(self):\r\n        if self._wait is None:\r\n            self._wait = ChromiumPageWaiter(self)\r\n        return self._wait\r\n\r\n    @property\r\n    def browser(self):\r\n        return self._browser\r\n\r\n    @property\r\n    def tabs_count(self):\r\n        return self.browser.tabs_count\r\n\r\n    @property\r\n    def tab_ids(self):\r\n        return self.browser.tab_ids\r\n\r\n    @property\r\n    def latest_tab(self):\r\n        return self.browser._get_tab(id_or_num=self.tab_ids[0], as_id=not Settings.singleton_tab_obj)\r\n\r\n    @property\r\n    def process_id(self):\r\n        return self.browser.process_id\r\n\r\n    @property\r\n    def browser_version(self):\r\n        return self._browser.version\r\n\r\n    @property\r\n    def address(self):\r\n        return self.browser.address\r\n\r\n    @property\r\n    def download_path(self):\r\n        return self.browser.download_path\r\n\r\n    def save(self, path=None, name=None, as_pdf=False, **kwargs):\r\n        return save_page(self, path, name, as_pdf, kwargs)\r\n\r\n    def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', as_id=False):\r\n        return self.browser._get_tab(id_or_num=id_or_num, title=title, url=url,\r\n                                     tab_type=tab_type, mix=False, as_id=as_id)\r\n\r\n    def get_tabs(self, title=None, url=None, tab_type='page', as_id=False):\r\n        return self.browser._get_tabs(title=title, url=url, tab_type=tab_type, mix=False, as_id=as_id)\r\n\r\n    def new_tab(self, url=None, new_window=False, background=False, new_context=False):\r\n        return self.browser._new_tab(False, url=url, new_window=new_window,\r\n                                     background=background, new_context=new_context)\r\n\r\n    def activate_tab(self, id_ind_tab):\r\n        self.browser.activate_tab(id_ind_tab)\r\n\r\n    def close(self):\r\n        self.browser._close_tab(self)\r\n\r\n    def close_tabs(self, tabs_or_ids, others=False):\r\n        self.browser.close_tabs(tabs_or_ids=tabs_or_ids, others=others)\r\n\r\n    def quit(self, timeout=5, force=True, del_data=False):\r\n        self.browser.quit(timeout, force, del_data=del_data)\r\n\r\n    def _on_disconnect(self):\r\n        ChromiumPage._PAGES.pop(self._browser.id, None)\r\n"
  },
  {
    "path": "DrissionPage/_pages/chromium_page.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Union, Tuple, List, Optional\r\n\r\nfrom .._base.chromium import Chromium\r\nfrom .._configs.chromium_options import ChromiumOptions\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.chromium_tab import ChromiumTab\r\nfrom .._units.rect import TabRect\r\nfrom .._units.setter import ChromiumPageSetter\r\nfrom .._units.waiter import ChromiumPageWaiter\r\n\r\n\r\nclass ChromiumPage(ChromiumBase):\r\n    \"\"\"用于管理浏览器和一个标签页的类\"\"\"\r\n    _PAGES: dict = ...\r\n    tab: ChromiumPage = ...\r\n    _browser: Chromium = ...\r\n    _rect: Optional[TabRect] = ...\r\n    _is_exist: bool = ...\r\n\r\n    def __new__(cls,\r\n                addr_or_opts: Union[str, int, ChromiumOptions] = None,\r\n                tab_id: str = None):\r\n        \"\"\"\r\n        :param addr_or_opts: 浏览器地址:端口、ws地址、ChromiumOptions对象或端口数字（int）\r\n        :param tab_id: 要控制的标签页id，不指定默认为激活的\r\n        \"\"\"\r\n        ...\r\n\r\n    def __init__(self,\r\n                 addr_or_opts: Union[str, int, ChromiumOptions] = None,\r\n                 tab_id: str = None):\r\n        \"\"\"\r\n        :param addr_or_opts: 浏览器地址:端口、ws地址、ChromiumOptions对象或端口数字（int）\r\n        :param tab_id: 要控制的标签页id，不指定默认为激活的\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def set(self) -> ChromiumPageSetter:\r\n        \"\"\"返回用于设置的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def wait(self) -> ChromiumPageWaiter:\r\n        \"\"\"返回用于等待的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def browser(self) -> Chromium:\r\n        \"\"\"返回浏览器对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def tabs_count(self) -> int:\r\n        \"\"\"返回标签页数量\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def tab_ids(self) -> List[str]:\r\n        \"\"\"返回所有标签页id组成的列表\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def latest_tab(self) -> Union[ChromiumTab, ChromiumPage, str]:\r\n        \"\"\"返回最新的标签页，最新标签页指最后创建或最后被激活的\r\n        当Settings.singleton_tab_obj==True时返回Tab对象，否则返回tab id\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def process_id(self) -> Optional[int]:\r\n        \"\"\"返回浏览器进程id\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def browser_version(self) -> str:\r\n        \"\"\"返回所控制的浏览器版本号\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def address(self) -> str:\r\n        \"\"\"返回浏览器地址ip:port\"\"\"\r\n        ...\r\n\r\n    def save(self,\r\n             path: Union[str, Path] = None,\r\n             name: str = None,\r\n             as_pdf: bool = False,\r\n             landscape: bool = ...,\r\n             displayHeaderFooter: bool = ...,\r\n             printBackground: bool = ...,\r\n             scale: float = ...,\r\n             paperWidth: float = ...,\r\n             paperHeight: float = ...,\r\n             marginTop: float = ...,\r\n             marginBottom: float = ...,\r\n             marginLeft: float = ...,\r\n             marginRight: float = ...,\r\n             pageRanges: str = ...,\r\n             headerTemplate: str = ...,\r\n             footerTemplate: str = ...,\r\n             preferCSSPageSize: bool = ...,\r\n             generateTaggedPDF: bool = ...,\r\n             generateDocumentOutline: bool = ...) -> Union[bytes, str]:\r\n        \"\"\"把当前页面保存为文件，如果path和name参数都为None，只返回文本\r\n        :param path: 保存路径，为None且name不为None时保存在当前路径\r\n        :param name: 文件名，为None且path不为None时用title属性值\r\n        :param as_pdf: 为Ture保存为pdf，否则为mhtml且忽略kwargs参数\r\n        :param landscape: 纸张方向，as_pdf为True时才生效\r\n        :param displayHeaderFooter: 是否显示页头页脚，as_pdf为True时才生效\r\n        :param printBackground: 是否打印背景图片，as_pdf为True时才生效\r\n        :param scale: 缩放比例，as_pdf为True时才生效\r\n        :param paperWidth: 页面宽度（英寸），as_pdf为True时才生效\r\n        :param paperHeight: 页面高度（英寸），as_pdf为True时才生效\r\n        :param marginTop: 上边距（英寸），as_pdf为True时才生效\r\n        :param marginBottom: 下边距（英寸），as_pdf为True时才生效\r\n        :param marginLeft: 左边距（英寸），as_pdf为True时才生效\r\n        :param marginRight: 右边距（英寸），as_pdf为True时才生效\r\n        :param pageRanges: 页面范围，格式'1-5, 8, 11-13'，as_pdf为True时才生效\r\n        :param headerTemplate: 页头HTML模板，as_pdf为True时才生效\r\n                模板可包含以下class：\r\n                - date：日期\r\n                - title：文档标题\r\n                - url：文档url\r\n                - pageNumber：当前页码\r\n                - totalPages：总页数\r\n                示例：<span class=title></span>\r\n        :param footerTemplate: 页脚HTML模板，格式与页头的一样，as_pdf为True时才生效\r\n        :param preferCSSPageSize: 是否使用css定义的页面大小，as_pdf为True时才生效\r\n        :param generateTaggedPDF: 是否生成带标签的(可访问的)PDF。默认为嵌入器选择，as_pdf为True时才生效\r\n        :param generateDocumentOutline: 是否将文档大纲嵌入到PDF中。，as_pdf为True时才生效\r\n        :return: as_pdf为True时返回bytes，否则返回文件文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_tab(self,\r\n                id_or_num: Union[str, ChromiumTab, int] = None,\r\n                title: str = None,\r\n                url: str = None,\r\n                tab_type: Union[str, list, tuple] = 'page',\r\n                as_id: bool = False) -> Union[ChromiumTab, str, None]:\r\n        \"\"\"获取一个标签页对象，id_or_num不为None时，后面几个参数无效\r\n        :param id_or_num: 要获取的标签页id或序号，序号从1开始，可传入负数获取倒数第几个，不是视觉排列顺序，而是激活顺序\r\n        :param title: 要匹配title的文本，模糊匹配，为None则匹配所有\r\n        :param url: 要匹配url的文本，模糊匹配，为None则匹配所有\r\n        :param tab_type: tab类型，可用列表输入多个，如 'page', 'iframe' 等，为None则匹配所有\r\n        :param as_id: 是否返回标签页id而不是标签页对象\r\n        :return: ChromiumTab对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_tabs(self,\r\n                 title: str = None,\r\n                 url: str = None,\r\n                 tab_type: Union[str, list, tuple] = 'page',\r\n                 as_id: bool = False) -> Union[List[ChromiumTab], List[str]]:\r\n        \"\"\"查找符合条件的tab，返回它们组成的列表\r\n        :param title: 要匹配title的文本，模糊匹配，为None则匹配所有\r\n        :param url: 要匹配url的文本，模糊匹配，为None则匹配所有\r\n        :param tab_type: tab类型，可用列表输入多个，如 'page', 'iframe' 等，为None则匹配所有\r\n        :param as_id: 是否返回标签页id而不是标签页对象\r\n        :return: ChromiumTab对象组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def new_tab(self,\r\n                url: str = None,\r\n                new_window: bool = False,\r\n                background: bool = False,\r\n                new_context: bool = False) -> ChromiumTab:\r\n        \"\"\"新建一个标签页\r\n        :param url: 新标签页跳转到的网址\r\n        :param new_window: 是否在新窗口打开标签页\r\n        :param background: 是否不激活新标签页，如new_window为True则无效\r\n        :param new_context: 是否创建新的上下文\r\n        :return: 新标签页对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def activate_tab(self,\r\n                     id_ind_tab: Union[int, str, ChromiumTab]) -> None:\r\n        \"\"\"使标签页变为活动状态\r\n        :param id_ind_tab: 标签页id（str）、Tab对象或标签页序号（int），序号从1开始\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def close(self) -> None:\r\n        \"\"\"关闭Page管理的标签页\"\"\"\r\n        ...\r\n\r\n    def close_tabs(self,\r\n                   tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]],\r\n                                Tuple[Union[str, ChromiumTab]]],\r\n                   others: bool = False) -> None:\r\n        \"\"\"关闭传入的标签页，可传入多个\r\n        :param tabs_or_ids: 要关闭的标签页对象或id，可传入列表或元组\r\n        :param others: 是否关闭指定标签页之外的\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def quit(self,\r\n             timeout: float = 5,\r\n             force: bool = True,\r\n             del_data: bool = False) -> None:\r\n        \"\"\"关闭浏览器\r\n        :param timeout: 等待浏览器关闭超时时间（秒）\r\n        :param force: 关闭超时是否强制终止进程\r\n        :param del_data: 是否删除用户文件夹\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _on_disconnect(self) -> None:\r\n        \"\"\"浏览器退出时执行\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_pages/chromium_tab.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom copy import copy\r\nfrom time import sleep\r\n\r\nfrom .._functions.settings import Settings\r\nfrom .._functions.web import save_page\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._units.setter import TabSetter\r\nfrom .._units.waiter import TabWaiter\r\n\r\n\r\nclass ChromiumTab(ChromiumBase):\r\n    _TABS = {}\r\n\r\n    def __new__(cls, browser, tab_id):\r\n        if Settings.singleton_tab_obj and tab_id in cls._TABS:\r\n            r = cls._TABS[tab_id]\r\n            while not hasattr(r, '_frame_id'):\r\n                sleep(.05)\r\n            return r\r\n        r = object.__new__(cls)\r\n        cls._TABS[tab_id] = r\r\n        return r\r\n\r\n    def __init__(self, browser, tab_id):\r\n        if Settings.singleton_tab_obj and hasattr(self, '_created'):\r\n            return\r\n        self._created = True\r\n\r\n        super().__init__(browser, tab_id)\r\n        self._tab = self\r\n        self._type = 'ChromiumTab'\r\n\r\n    def __repr__(self):\r\n        return f'<ChromiumTab browser_id={self.browser.id} tab_id={self.tab_id}>'\r\n\r\n    def _d_set_runtime_settings(self):\r\n        self._timeouts = copy(self.browser.timeouts)\r\n        self.retry_times = self.browser.retry_times\r\n        self.retry_interval = self.browser.retry_interval\r\n        self._load_mode = self.browser._load_mode\r\n        self._download_path = self.browser.download_path\r\n        self._auto_handle_alert = self.browser._auto_handle_alert\r\n        self._none_ele_return_value = self.browser._none_ele_return_value\r\n        self._none_ele_value = self.browser._none_ele_value\r\n\r\n    def close(self, others=False):\r\n        if others:\r\n            self.browser.close_tabs(self.tab_id, others=True)\r\n        else:\r\n            self.browser._close_tab(self)\r\n\r\n    @property\r\n    def set(self):\r\n        if self._set is None:\r\n            self._set = TabSetter(self)\r\n        return self._set\r\n\r\n    @property\r\n    def wait(self):\r\n        if self._wait is None:\r\n            self._wait = TabWaiter(self)\r\n        return self._wait\r\n\r\n    def save(self, path=None, name=None, as_pdf=False, **kwargs):\r\n        return save_page(self, path, name, as_pdf, kwargs)\r\n\r\n    def _on_disconnect(self):\r\n        if not self._disconnect_flag:\r\n            ChromiumTab._TABS.pop(self.tab_id, None)\r\n"
  },
  {
    "path": "DrissionPage/_pages/chromium_tab.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Union, Optional\r\n\r\nfrom .chromium_base import ChromiumBase\r\nfrom .._base.chromium import Chromium\r\nfrom .._units.rect import TabRect\r\nfrom .._units.setter import TabSetter\r\nfrom .._units.waiter import TabWaiter\r\n\r\n\r\nclass ChromiumTab(ChromiumBase):\r\n    \"\"\"实现浏览器标签页的类\"\"\"\r\n    _TABS: dict = ...\r\n    _tab: ChromiumTab = ...\r\n    _rect: Optional[TabRect] = ...\r\n\r\n    def __new__(cls, browser: Chromium, tab_id: str):\r\n        \"\"\"\r\n        :param browser: Browser对象\r\n        :param tab_id: 标签页id\r\n        \"\"\"\r\n        ...\r\n\r\n    def __init__(self, browser: Chromium, tab_id: str):\r\n        \"\"\"\r\n        :param browser: Browser对象\r\n        :param tab_id: 标签页id\r\n        \"\"\"\r\n        ...\r\n\r\n    def _d_set_runtime_settings(self) -> None:\r\n        \"\"\"重写设置浏览器运行参数方法\"\"\"\r\n        ...\r\n\r\n    def close(self, others: bool = False) -> None:\r\n        \"\"\"关闭标签页\r\n        :param others: 是否关闭其它，保留自己\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def set(self) -> TabSetter:\r\n        \"\"\"返回用于设置的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def wait(self) -> TabWaiter:\r\n        \"\"\"返回用于等待的对象\"\"\"\r\n        ...\r\n\r\n    def save(self,\r\n             path: Union[str, Path] = None,\r\n             name: str = None,\r\n             as_pdf: bool = False,\r\n             landscape: bool = False,\r\n             displayHeaderFooter: bool = False,\r\n             printBackground: bool = False,\r\n             scale: float = 1,\r\n             paperWidth: float = 8.5,\r\n             paperHeight: float = 11,\r\n             marginTop: float = 11,\r\n             marginBottom: float = 1,\r\n             marginLeft: float = 1,\r\n             marginRight: float = 1,\r\n             pageRanges: str = '',\r\n             headerTemplate: str = '',\r\n             footerTemplate: str = '',\r\n             preferCSSPageSize: bool = False,\r\n             generateTaggedPDF: bool = ...,\r\n             generateDocumentOutline: bool = ...) -> Union[bytes, str]:\r\n        \"\"\"把当前页面保存为文件，如果path和name参数都为None，只返回文本\r\n        :param path: 保存路径，为None且name不为None时保存在当前路径\r\n        :param name: 文件名，为None且path不为None时用title属性值\r\n        :param as_pdf: 为Ture保存为pdf，否则为mhtml且忽略kwargs参数\r\n        :param landscape: 纸张方向，as_pdf为True时才生效\r\n        :param displayHeaderFooter: 是否显示页头页脚，as_pdf为True时才生效\r\n        :param printBackground: 是否打印背景图片，as_pdf为True时才生效\r\n        :param scale: 缩放比例，as_pdf为True时才生效\r\n        :param paperWidth: 页面宽度（英寸），as_pdf为True时才生效\r\n        :param paperHeight: 页面高度（英寸），as_pdf为True时才生效\r\n        :param marginTop: 上边距（英寸），as_pdf为True时才生效\r\n        :param marginBottom: 下边距（英寸），as_pdf为True时才生效\r\n        :param marginLeft: 左边距（英寸），as_pdf为True时才生效\r\n        :param marginRight: 右边距（英寸），as_pdf为True时才生效\r\n        :param pageRanges: 页面范围，格式'1-5, 8, 11-13'，as_pdf为True时才生效\r\n        :param headerTemplate: 页头HTML模板，as_pdf为True时才生效\r\n                模板可包含以下class：\r\n                - date：日期\r\n                - title：文档标题\r\n                - url：文档url\r\n                - pageNumber：当前页码\r\n                - totalPages：总页数\r\n                示例：<span class=title></span>\r\n        :param footerTemplate: 页脚HTML模板，格式与页头的一样，as_pdf为True时才生效\r\n        :param preferCSSPageSize: 是否使用css定义的页面大小，as_pdf为True时才生效\r\n        :param generateTaggedPDF: 是否生成带标签的(可访问的)PDF。默认为嵌入器选择，as_pdf为True时才生效\r\n        :param generateDocumentOutline: 是否将文档大纲嵌入到PDF中。，as_pdf为True时才生效\r\n        :return: as_pdf为True时返回bytes，否则返回文件文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def _on_disconnect(self): ...\r\n"
  },
  {
    "path": "DrissionPage/_pages/mix_tab.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom .chromium_tab import ChromiumTab\nfrom .._base.base import BasePage\nfrom .._configs.session_options import SessionOptions\nfrom .._functions.cookies import set_session_cookies, set_tab_cookies\nfrom .._functions.settings import Settings as _S\nfrom .._pages.session_page import SessionPage\nfrom .._units.setter import MixTabSetter\n\n\nclass MixTab(SessionPage, ChromiumTab, BasePage):\n    def __init__(self, browser, tab_id):\n        if _S.singleton_tab_obj and hasattr(self, '_created'):\n            return\n        self._d_mode = True\n        self._session_options = None\n        self._headers = None\n        self._response = None\n        self._session = None\n        self._encoding = None\n        self._timeout = 10\n        super(SessionPage, self).__init__(browser=browser, tab_id=tab_id)\n        self._type = 'MixTab'\n\n    def __call__(self, locator, index=1, timeout=None):\n        return super(SessionPage, self).__call__(locator, index=index, timeout=timeout) if self._d_mode \\\n            else super().__call__(locator, index=index)\n\n    def __repr__(self):\n        return f'<MixTab browser_id={self.browser.id} tab_id={self.tab_id}>'\n\n    @property\n    def set(self):\n        if self._set is None:\n            self._set = MixTabSetter(self)\n        return self._set\n\n    @property\n    def url(self):\n        return self._browser_url if self._d_mode else self._session_url\n\n    @property\n    def _browser_url(self):\n        return super(SessionPage, self).url if self._driver else None\n\n    @property\n    def title(self):\n        return super(SessionPage, self).title if self._d_mode else super().title\n\n    @property\n    def raw_data(self):\n        return super(SessionPage, self).html if self._d_mode else super().raw_data\n\n    @property\n    def html(self):\n        return super(SessionPage, self).html if self._d_mode else super().html\n\n    @property\n    def json(self):\n        return super(SessionPage, self).json if self._d_mode else super().json\n\n    @property\n    def response(self):\n        return self._response\n\n    @property\n    def mode(self):\n        return 'd' if self._d_mode else 's'\n\n    @property\n    def user_agent(self):\n        return super(SessionPage, self).user_agent if self._d_mode else super().user_agent\n\n    @property\n    def _session_url(self):\n        return self._response.url if self._response else None\n\n    @property\n    def timeout(self):\n        return self.timeouts.base if self._d_mode else self._timeout\n\n    def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):\n        if self._d_mode:\n            if kwargs:\n                raise ValueError(_S._lang.join(_S._lang.S_MODE_ONLY, ARGS=\", \".join(kwargs.keys())))\n            return super(SessionPage, self).get(url, show_errmsg, retry, interval, timeout)\n\n        if timeout is None:\n            timeout = self.timeouts.page_load\n        return super().get(url, show_errmsg, retry, interval, timeout, **kwargs)\n\n    def post(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):\n        if self.mode == 'd':\n            self.cookies_to_session()\n        if timeout is None:\n            kwargs['timeout'] = self.timeouts.page_load\n        if self._session is None:\n            self._create_session()\n        super().post(url, show_errmsg, retry, interval, **kwargs)\n        return self.response\n\n    def ele(self, locator, index=1, timeout=None):\n        return (super(SessionPage, self).ele(locator, index=index, timeout=timeout)\n                if self._d_mode else super().ele(locator, index=index))\n\n    def eles(self, locator, timeout=None):\n        return super(SessionPage, self).eles(locator, timeout=timeout) if self._d_mode else super().eles(locator)\n\n    def s_ele(self, locator=None, index=1, timeout=None):\n        return (super(SessionPage, self).s_ele(locator, index=index, timeout=timeout)\n                if self._d_mode else super().s_ele(locator, index=index, timeout=timeout))\n\n    def s_eles(self, locator, timeout=None):\n        return (super(SessionPage, self).s_eles(locator, timeout=timeout)\n                if self._d_mode else super().s_eles(locator, timeout=timeout))\n\n    def change_mode(self, mode=None, go=True, copy_cookies=True):\n        if mode:\n            mode = mode.lower()\n        if mode is not None and ((mode == 'd' and self._d_mode) or (mode == 's' and not self._d_mode)):\n            return\n\n        self._d_mode = not self._d_mode\n\n        # s模式转d模式\n        if self._d_mode:\n            if self._driver is None or not self._driver.is_running:\n                self._driver_init(self.tab_id)\n                self._get_document()\n\n            self._url = super(SessionPage, self).url\n            if self._session_url:\n                if copy_cookies:\n                    self.cookies_to_browser()\n                if go:\n                    self.get(self._session_url)\n\n            return\n\n        # d模式转s模式\n        if self._session is None:\n            self._set_session_options(\n                self.browser._session_options or SessionOptions(read_file=self.browser._session_options is None))\n            self._create_session()\n\n        self._url = self._session_url\n        if self._driver:\n            if copy_cookies:\n                self.cookies_to_session()\n\n            if go:\n                url = super(SessionPage, self).url\n                if url.startswith('http'):\n                    self.get(url)\n\n    def cookies_to_session(self, copy_user_agent=True):\n        if not self._session:\n            return\n\n        if copy_user_agent:\n            user_agent = self._run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']\n            self._headers.update({\"User-Agent\": user_agent})\n\n        set_session_cookies(self.session, super(SessionPage, self).cookies())\n\n    def cookies_to_browser(self):\n        if self._driver is None or not self._driver.is_running:\n            return\n        set_tab_cookies(self, super().cookies())\n\n    def cookies(self, all_domains=False, all_info=False):\n        return super(SessionPage, self).cookies(all_domains, all_info) if self._d_mode \\\n            else super().cookies(all_domains, all_info)\n\n    def close(self, others=False, session=False):\n        if others:\n            self.browser.close_tabs(self.tab_id, others=True)\n        else:\n            self.browser._close_tab(self)\n        if session and self._session:\n            self._session.close()\n            if self._response is not None:\n                self._response.close()\n\n    def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):\n        return super(SessionPage, self)._find_elements(locator, timeout=timeout, index=index, relative=relative) \\\n            if self._d_mode else super()._find_elements(locator, index=index, timeout=timeout)\n\n    def _set_session_options(self, session_or_options=None):\n        if session_or_options is None:\n            session_or_options = self.browser._session_options or SessionOptions(\n                read_file=self.browser._session_options is None)\n        super()._set_session_options(session_or_options)\n"
  },
  {
    "path": "DrissionPage/_pages/mix_tab.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom http.cookiejar import CookieJar\nfrom typing import Union, Tuple, Any, Optional, Literal\n\nfrom requests import Session, Response\n\nfrom .chromium_frame import ChromiumFrame\nfrom .chromium_tab import ChromiumTab\nfrom .session_page import SessionPage\nfrom .._base.chromium import Chromium\nfrom .._elements.chromium_element import ChromiumElement\nfrom .._elements.session_element import SessionElement\nfrom .._functions.cookies import CookiesList\nfrom .._functions.elements import SessionElementsList, ChromiumElementsList\nfrom .._units.setter import MixTabSetter\nfrom .._units.waiter import MixTabWaiter\n\n\nclass MixTab(SessionPage, ChromiumTab):\n    _tab: MixTab = ...\n    _d_mode: bool = ...\n    _set: MixTabSetter = ...\n\n    def __init__(self, browser: Chromium, tab_id: str):\n        \"\"\"\n        :param browser: Chromium对象\n        :param tab_id: 标签页id\n        \"\"\"\n        ...\n\n    def __call__(self,\n                 locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement],\n                 index: int = 1,\n                 timeout: float = None) -> Union[ChromiumElement, SessionElement]:\n        \"\"\"在内部查找元素\n        例：ele = page('@id=ele_id')\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\n        :param timeout: 超时时间（秒）\n        :return: 子元素对象\n        \"\"\"\n        ...\n\n    @property\n    def set(self) -> MixTabSetter:\n        \"\"\"返回用于设置的对象\"\"\"\n        ...\n\n    @property\n    def wait(self) -> MixTabWaiter:\n        \"\"\"返回用于等待的对象\"\"\"\n        ...\n\n    @property\n    def url(self) -> Union[str, None]:\n        \"\"\"返回浏览器当前url\"\"\"\n        ...\n\n    @property\n    def _browser_url(self) -> Union[str, None]:\n        \"\"\"返回浏览器当前url\"\"\"\n        ...\n\n    @property\n    def title(self) -> str:\n        \"\"\"返回当前页面title\"\"\"\n        ...\n\n    @property\n    def raw_data(self) -> Union[str, bytes]:\n        \"\"\"返回页码原始数据数据\"\"\"\n        ...\n\n    @property\n    def html(self) -> str:\n        \"\"\"返回页面html文本\"\"\"\n        ...\n\n    @property\n    def json(self) -> dict:\n        \"\"\"当返回内容是json格式时，返回对应的字典\"\"\"\n        ...\n\n    @property\n    def response(self) -> Response:\n        \"\"\"返回 s 模式获取到的 Response 对象，切换到 s 模式\"\"\"\n        ...\n\n    @property\n    def mode(self) -> Literal['s', 'd']:\n        \"\"\"返回当前模式，'s'或'd' \"\"\"\n        ...\n\n    @property\n    def user_agent(self) -> str:\n        \"\"\"返回user agent\"\"\"\n        ...\n\n    @property\n    def session(self) -> Session:\n        \"\"\"返回Session对象，如未初始化则按配置信息创建\"\"\"\n        ...\n\n    @property\n    def _session_url(self) -> str:\n        \"\"\"返回 session 保存的url\"\"\"\n        ...\n\n    @property\n    def timeout(self) -> float:\n        \"\"\"返回通用timeout设置\"\"\"\n        ...\n\n    def get(self,\n            url: str,\n            show_errmsg: bool = False,\n            retry: Optional[int] = None,\n            interval: Optional[float] = None,\n            timeout: Optional[float] = None,\n            params: Optional[dict] = None,\n            data: Any = None,\n            json: Any = None,\n            headers: Optional[dict] = None,\n            cookies: Union[CookieJar, dict] = None,\n            files: Optional[Any] = None,\n            auth: Optional[Any] = None,\n            allow_redirects: bool = True,\n            proxies: Optional[dict] = None,\n            hooks: Optional[Any] = None,\n            stream: bool = None,\n            verify: Union[bool, str] = None,\n            cert: [str, Tuple[str, str]] = None) -> Union[bool, None]:\n        \"\"\"跳转到一个url\n        :param url: 目标url\n        :param show_errmsg: 是否显示和抛出异常\n        :param retry: 重试次数，为None时使用页面对象retry_times属性值\n        :param interval: 重试间隔（秒），为None时使用页面对象retry_interval属性值\n        :param timeout: 连接超时时间\n        :param params: url中的参数\n        :param data: 携带的数据\n        :param json: 要发送的 JSON 数据，会自动设置 Content-Type 为 application/json\n        :param headers: 请求头\n        :param cookies: cookies信息\n        :param files: 要上传的文件，可以是一个字典，其中键是文件名，值是文件对象或文件路径\n        :param auth: 身份认证信息\n        :param allow_redirects: 是否允许重定向\n        :param proxies: 代理信息\n        :param hooks: 回调方法\n        :param stream: 是否使用流式传输\n        :param verify: 是否验证 SSL 证书\n        :param cert: SSL客户端证书文件的路径(.pem格式)，或('cert', 'key')元组\n        :return: s模式时返回url是否可用，d模式时返回获取到的Response对象\n        \"\"\"\n        ...\n\n    def post(self,\n             url: str,\n             show_errmsg: bool = False,\n             retry: Optional[int] = None,\n             interval: Optional[float] = None,\n             timeout: Optional[float] = None,\n             params: Optional[dict] = None,\n             data: Any = None,\n             json: Any = None,\n             headers: Optional[dict] = None,\n             cookies: Union[CookieJar, dict] = None,\n             files: Optional[Any] = None,\n             auth: Optional[Any] = None,\n             allow_redirects: bool = True,\n             proxies: Optional[dict] = None,\n             hooks: Optional[Any] = None,\n             stream: bool = None,\n             verify: Union[bool, str] = None,\n             cert: [str, Tuple[str, str]] = None) -> Response:\n        \"\"\"用post方式跳转到url\n        :param url: 目标url\n        :param show_errmsg: 是否显示和抛出异常\n        :param retry: 重试次数，为None时使用页面对象retry_times属性值\n        :param interval: 重试间隔（秒），为None时使用页面对象retry_interval属性值\n        :param timeout: 连接超时时间\n        :param params: url中的参数\n        :param data: 携带的数据\n        :param json: 要发送的 JSON 数据，会自动设置 Content-Type 为 application/json\n        :param headers: 请求头\n        :param cookies: cookies信息\n        :param files: 要上传的文件，可以是一个字典，其中键是文件名，值是文件对象或文件路径\n        :param auth: 身份认证信息\n        :param allow_redirects: 是否允许重定向\n        :param proxies: 代理信息\n        :param hooks: 回调方法\n        :param stream: 是否使用流式传输\n        :param verify: 是否验证 SSL 证书\n        :param cert: SSL客户端证书文件的路径(.pem格式)，或('cert', 'key')元组\n        :return: 获取到的Response对象\n        \"\"\"\n        ...\n\n    def ele(self,\n            locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement],\n            index: int = 1,\n            timeout: float = None) -> Union[ChromiumElement, SessionElement]:\n        \"\"\"返回第一个符合条件的元素、属性或节点文本\n        :param locator: 元素的定位信息，可以是元素对象，loc元组，或查询字符串\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\n        :return: 元素对象或属性、文本节点文本\n        \"\"\"\n        ...\n\n    def eles(self,\n             locator: Union[Tuple[str, str], str],\n             timeout: float = None) -> Union[ChromiumElementsList, SessionElementsList]:\n        \"\"\"返回页面中所有符合条件的元素、属性或节点文本\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\n        :return: 元素对象或属性、文本组成的列表\n        \"\"\"\n        ...\n\n    def s_ele(self,\n              locator: Union[Tuple[str, str], str] = None,\n              index: int = 1,\n              timeout: float = None) -> SessionElement:\n        \"\"\"查找第一个符合条件的元素以SessionElement形式返回，d模式处理复杂页面时效率很高\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\n        :return: SessionElement对象或属性、文本\n        \"\"\"\n        ...\n\n    def s_eles(self, locator: Union[Tuple[str, str], str], timeout: float = None) -> SessionElementsList:\n        \"\"\"查找所有符合条件的元素以SessionElement形式返回，d模式处理复杂页面时效率很高\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\n        :return: SessionElement对象或属性、文本组成的列表\n        \"\"\"\n        ...\n\n    def change_mode(self, mode: str = None, go: bool = True, copy_cookies: bool = True) -> None:\n        \"\"\"切换模式，接收's'或'd'，除此以外的字符串会切换为 d 模式\n        如copy_cookies为True，切换时会把当前模式的cookies复制到目标模式\n        切换后，如果go是True，调用相应的get函数使访问的页面同步\n        :param mode: 模式字符串\n        :param go: 是否跳转到原模式的url\n        :param copy_cookies: 是否复制cookies到目标模式\n        :return: None\n        \"\"\"\n        ...\n\n    def cookies_to_session(self, copy_user_agent: bool = True) -> None:\n        \"\"\"把浏览器的cookies复制到session对象\n        :param copy_user_agent: 是否复制ua信息\n        :return: None\n        \"\"\"\n        ...\n\n    def cookies_to_browser(self) -> None:\n        \"\"\"把session对象的cookies复制到浏览器\"\"\"\n        ...\n\n    def cookies(self, all_domains: bool = False, all_info: bool = False) -> CookiesList:\n        \"\"\"返回cookies\n        :param all_domains: 是否返回所有域的cookies\n        :param all_info: 是否返回所有信息，False则只返回name、value、domain\n        :return: cookies信息\n        \"\"\"\n        ...\n\n    def close(self, others: bool = False) -> None:\n        \"\"\"关闭标签页\n        :param others: 是否关闭其它，保留自己\n        :return: None\n        \"\"\"\n        ...\n\n    def _find_elements(self,\n                       locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement, ChromiumFrame],\n                       timeout: float,\n                       index: Optional[int] = 1,\n                       relative: bool = False,\n                       raise_err: bool = None) \\\n            -> Union[ChromiumElement, SessionElement, ChromiumFrame, SessionElementsList, ChromiumElementsList]:\n        \"\"\"返回页面中符合条件的元素、属性或节点文本，默认返回第一个\n        :param locator: 元素的定位信息，可以是元素对象，loc元组，或查询字符串\n        :param timeout: 查找元素超时时间（秒），d模式专用\n        :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\n        :param relative: MixTab用的表示是否相对定位的参数\n        :param raise_err: 找不到元素是是否抛出异常，为None时根据全局设置\n        :return: 元素对象或属性、文本节点文本\n        \"\"\"\n        ...\n"
  },
  {
    "path": "DrissionPage/_pages/session_page.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom re import search, DOTALL\r\nfrom time import sleep\r\nfrom urllib.parse import urlparse\r\n\r\nfrom requests import Response\r\nfrom requests.structures import CaseInsensitiveDict\r\nfrom tldextract import TLDExtract\r\n\r\nfrom .._base.base import BasePage\r\nfrom .._elements.session_element import SessionElement, make_session_ele\r\nfrom .._functions.cookies import cookie_to_dict, CookiesList\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._functions.web import format_headers\r\nfrom .._units.setter import SessionPageSetter\r\n\r\n\r\nclass SessionPage(BasePage):\r\n    def __init__(self, session_or_options=None, timeout=None):\r\n        super().__init__()\r\n        self._response = None\r\n        self._set = None\r\n        self._encoding = None\r\n        self._type = 'SessionPage'\r\n        self._page = self\r\n        self._set_session_options(session_or_options)\r\n        self._s_set_runtime_settings()\r\n        if timeout is not None:  # 即将废弃\r\n            self._timeout = timeout\r\n        if not self._session:\r\n            self._create_session()\r\n\r\n    def __repr__(self):\r\n        return f'<SessionPage url={self.url}>'\r\n\r\n    def _s_set_runtime_settings(self):\r\n        self._timeout = self._session_options.timeout\r\n        self._download_path = str(Path(self._session_options.download_path or '.').absolute())\r\n        self.retry_times = self._session_options.retry_times\r\n        self.retry_interval = self._session_options.retry_interval\r\n\r\n    def __call__(self, locator, index=1, timeout=None):\r\n        return self.ele(locator, index=index)\r\n\r\n    # -----------------共有属性和方法-------------------\r\n    @property\r\n    def title(self):\r\n        ele = self._ele('xpath://title', raise_err=False)\r\n        return ele.text if ele else None\r\n\r\n    @property\r\n    def url(self):\r\n        return self._url\r\n\r\n    @property\r\n    def _session_url(self):\r\n        return self._url\r\n\r\n    @property\r\n    def raw_data(self):\r\n        return self.response.content if self.response else b''\r\n\r\n    @property\r\n    def html(self):\r\n        return self.response.text if self.response else ''\r\n\r\n    @property\r\n    def json(self):\r\n        try:\r\n            return self.response.json()\r\n        except Exception:\r\n            return None\r\n\r\n    @property\r\n    def user_agent(self):\r\n        return self._headers.get('user-agent', '')\r\n\r\n    @property\r\n    def session(self):\r\n        return self._session\r\n\r\n    @property\r\n    def response(self):\r\n        return self._response\r\n\r\n    @property\r\n    def encoding(self):\r\n        return self._encoding\r\n\r\n    @property\r\n    def set(self):\r\n        if self._set is None:\r\n            self._set = SessionPageSetter(self)\r\n        return self._set\r\n\r\n    @property\r\n    def timeout(self):\r\n        return self._timeout\r\n\r\n    def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):\r\n        \"\"\"用get方式跳转到url，可输入文件路径\r\n        :param url: 目标url，可指定本地文件路径\r\n        :param show_errmsg: 是否显示和抛出异常\r\n        :param retry: 重试次数，为None时使用页面对象retry_times属性值\r\n        :param interval: 重试间隔（秒），为None时使用页面对象retry_interval属性值\r\n        :param timeout: 连接超时时间（秒），为None时使用页面对象timeout属性值\r\n        :param kwargs: 连接参数\r\n        :return: url是否可用\r\n        \"\"\"\r\n        retry, interval, is_file = self._before_connect(url.replace('file:///', '', 1), retry, interval)\r\n        if is_file:\r\n            with open(self._url, 'rb') as f:\r\n                r = Response()\r\n                r._content = f.read()\r\n                r.status_code = 200\r\n                r.url = self._url\r\n                self._response = r\r\n            return True\r\n        return self._s_connect(self._url, 'get', show_errmsg, retry, interval, **kwargs)\r\n\r\n    def post(self, url, show_errmsg=False, retry=None, interval=None, **kwargs):\r\n        \"\"\"用post方式跳转到url\r\n        :param url: 目标url\r\n        :param show_errmsg: 是否显示和抛出异常\r\n        :param retry: 重试次数，为None时使用页面对象retry_times属性值\r\n        :param interval: 重试间隔（秒），为None时使用页面对象timeout属性值\r\n        :param kwargs: 连接参数\r\n        :return: url是否可用\r\n        \"\"\"\r\n        return self._s_connect(url, 'post', show_errmsg, retry, interval, **kwargs)\r\n\r\n    def ele(self, locator, index=1, timeout=None):\r\n        return self._ele(locator, index=index, method='ele()')\r\n\r\n    def eles(self, locator, timeout=None):\r\n        return self._ele(locator, index=None)\r\n\r\n    def s_ele(self, locator=None, index=1):\r\n        return make_session_ele(self) if locator is None else self._ele(locator, index=index, method='s_ele()')\r\n\r\n    def s_eles(self, locator):\r\n        return self._ele(locator, index=None)\r\n\r\n    def _find_elements(self, locator, timeout, index=1, relative=True, raise_err=None):\r\n        return locator if isinstance(locator, SessionElement) else make_session_ele(self, locator, index=index)\r\n\r\n    def cookies(self, all_domains=False, all_info=False):\r\n        if all_domains:\r\n            cookies = self.session.cookies\r\n        else:\r\n            if self.url:\r\n                ex_url = TLDExtract(\r\n                    suffix_list_urls=[\"https://publicsuffix.org/list/public_suffix_list.dat\",\r\n                                      f\"file:///{_S.suffixes_list}\"]).extract_str(self._session_url)\r\n                domain = f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain\r\n                cookies = tuple(c for c in self.session.cookies if domain in c.domain or c.domain == '')\r\n            else:\r\n                cookies = tuple(c for c in self.session.cookies)\r\n\r\n        if all_info:\r\n            r = CookiesList()\r\n            for c in cookies:\r\n                r.append(cookie_to_dict(c))\r\n        else:\r\n            r = CookiesList()\r\n            for c in cookies:\r\n                c = cookie_to_dict(c)\r\n                r.append({'name': c['name'], 'value': c['value'], 'domain': c['domain']})\r\n        return r\r\n\r\n    def close(self):\r\n        self._session.close()\r\n        if self._response is not None:\r\n            self._response.close()\r\n\r\n    def _s_connect(self, url, mode, show_errmsg=False, retry=None, interval=None, **kwargs):\r\n        retry, interval, is_file = self._before_connect(url, retry, interval)\r\n        self._response = self._make_response(self._url, mode, retry, interval, show_errmsg, **kwargs)\r\n\r\n        if self._response is None:\r\n            self._url_available = False\r\n\r\n        else:\r\n            if self._response.ok:\r\n                self._url_available = True\r\n\r\n            else:\r\n                if show_errmsg:\r\n                    raise ConnectionError(_S._lang.join(_S._lang.STATUS_CODE_, self._response.status_code))\r\n                self._url_available = False\r\n\r\n        return self._url_available\r\n\r\n    def _make_response(self, url, mode='get', retry=None, interval=None, show_errmsg=False, **kwargs):\r\n        kwargs = CaseInsensitiveDict(kwargs)\r\n        if 'headers' in kwargs:\r\n            kwargs['headers'] = CaseInsensitiveDict(format_headers(kwargs['headers']))\r\n        else:\r\n            kwargs['headers'] = CaseInsensitiveDict()\r\n\r\n        # 设置referer和host值\r\n        parsed_url = urlparse(url)\r\n        hostname = parsed_url.netloc\r\n        scheme = parsed_url.scheme\r\n        if not check_headers(kwargs['headers'], self._headers, 'Referer'):\r\n            kwargs['headers']['Referer'] = self.url if self.url else f'{scheme}://{hostname}'\r\n        elif not kwargs['headers']['Referer']:\r\n            kwargs['headers'].pop('Referer')\r\n        if not check_headers(kwargs['headers'], self._headers, 'Host'):\r\n            kwargs['headers']['Host'] = hostname\r\n        elif not kwargs['headers']['Host']:\r\n            kwargs['headers'].pop('Host')\r\n        if not check_headers(kwargs, self._headers, 'timeout'):\r\n            kwargs['timeout'] = self.timeout\r\n\r\n        h = CaseInsensitiveDict(self._headers)\r\n        for k, v in kwargs['headers'].items():\r\n            h[k] = v\r\n        kwargs['headers'] = h\r\n\r\n        r = err = None\r\n        retry = retry if retry is not None else self.retry_times\r\n        interval = interval if interval is not None else self.retry_interval\r\n        for i in range(retry + 1):\r\n            try:\r\n                if mode == 'get':\r\n                    r = self.session.get(url, **kwargs)\r\n                elif mode == 'post':\r\n                    r = self.session.post(url, **kwargs)\r\n\r\n                if r and r.content:\r\n                    if self._encoding:\r\n                        r.encoding = self._encoding\r\n                        return r\r\n                    return set_charset(r)\r\n\r\n            except Exception as e:\r\n                err = e\r\n\r\n            # if r and r.status_code in (403, 404):\r\n            #     break\r\n\r\n            if i < retry:\r\n                sleep(interval)\r\n                if show_errmsg:\r\n                    print(f'{_S._lang.RETRY} {url}')\r\n\r\n        if show_errmsg:\r\n            if err:\r\n                raise err\r\n            elif r is not None:\r\n                raise (ConnectionError(_S._lang.join(_S._lang.STATUS_CODE_, r.status_code)) if r.content\r\n                       else ConnectionError(_S._lang.join(_S._lang.CONTENT_IS_EMPTY)))\r\n            else:\r\n                raise ConnectionError(_S._lang.join(_S._lang.CONNECT_ERR))\r\n\r\n        else:\r\n            if r is not None:\r\n                return r if r.content else None\r\n            else:\r\n                return None\r\n\r\n\r\ndef check_headers(kwargs, headers, arg):\r\n    return arg in kwargs or arg in headers\r\n\r\n\r\ndef set_charset(response):\r\n    # 在headers中获取编码\r\n    content_type = response.headers.get('content-type', '').lower()\r\n    if not content_type.endswith(';'):\r\n        content_type += ';'\r\n    charset = search(r'charset[=: ]*(.*)?;?', content_type)\r\n\r\n    if charset:\r\n        response.encoding = charset.group(1)\r\n\r\n    # 在headers中获取不到编码，且如果是网页\r\n    elif content_type.replace(' ', '').startswith('text/html'):\r\n        re_result = search(b'<meta.*?charset=[ \\\\\\'\"]*([^\"\\\\\\' />]+).*?>', response.content, DOTALL)\r\n\r\n        if re_result:\r\n            charset = re_result.group(1).decode()\r\n        else:\r\n            charset = response.apparent_encoding\r\n\r\n        response.encoding = charset\r\n\r\n    return response\r\n"
  },
  {
    "path": "DrissionPage/_pages/session_page.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Any, Union, Tuple, Optional\r\n\r\nfrom requests import Session, Response\r\nfrom requests.structures import CaseInsensitiveDict\r\n\r\nfrom .._base.base import BasePage\r\nfrom .._configs.session_options import SessionOptions\r\nfrom .._elements.session_element import SessionElement\r\nfrom .._functions.cookies import CookiesList\r\nfrom .._functions.elements import SessionElementsList\r\nfrom .._units.setter import SessionPageSetter\r\n\r\n\r\nclass SessionPage(BasePage):\r\n    \"\"\"SessionPage封装了页面操作的常用功能，使用requests来获取、解析网页\"\"\"\r\n    _session_options: Optional[SessionOptions] = ...\r\n    _url: str = ...\r\n    _response: Optional[Response] = ...\r\n    _url_available: bool = ...\r\n    _timeout: float = ...\r\n    retry_times: int = ...\r\n    retry_interval: float = ...\r\n    _set: Optional[SessionPageSetter] = ...\r\n    _encoding: Optional[str] = ...\r\n    _page: SessionPage = ...\r\n\r\n    def __init__(self, session_or_options: Union[Session, SessionOptions] = None):\r\n        \"\"\"\r\n        :param session_or_options: Session对象或SessionOptions对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _s_set_runtime_settings(self) -> None:\r\n        \"\"\"设置运行时用到的属性\"\"\"\r\n        ...\r\n\r\n    def __call__(self,\r\n                 locator: Union[Tuple[str, str], str, SessionElement],\r\n                 index: int = 1,\r\n                 timeout: float = None) -> SessionElement:\r\n        \"\"\"在内部查找元素\r\n        例：ele2 = ele1('@id=ele_id')\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 不起实际作用，用于和ChromiumElement对应，便于无差别调用\r\n        :return: SessionElement对象或属性文本\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def title(self) -> str:\r\n        \"\"\"返回网页title\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def url(self) -> str:\r\n        \"\"\"返回当前访问url\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def _session_url(self) -> str:\r\n        \"\"\"返回当前访问url\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def raw_data(self) -> Union[str, bytes]:\r\n        \"\"\"返回页面原始数据\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def html(self) -> str:\r\n        \"\"\"返回页面的html文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def json(self) -> Union[dict, None]:\r\n        \"\"\"当返回内容是json格式时，返回对应的字典，非json格式时返回None\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def user_agent(self) -> str:\r\n        \"\"\"返回user agent\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def session(self) -> Session:\r\n        \"\"\"返回Session对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def response(self) -> Response:\r\n        \"\"\"返回访问url得到的Response对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def encoding(self) -> str:\r\n        \"\"\"返回设置的编码，s模式专用\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def set(self) -> SessionPageSetter:\r\n        \"\"\"返回用于设置的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def timeout(self) -> float:\r\n        \"\"\"返回超时设置\"\"\"\r\n        ...\r\n\r\n    def get(self,\r\n            url: Union[Path, str],\r\n            show_errmsg: bool | None = False,\r\n            retry: int | None = None,\r\n            interval: float | None = None,\r\n            timeout: float | None = None,\r\n            params: dict | None = ...,\r\n            data: Any = ...,\r\n            json: Any = ...,\r\n            headers: Union[dict, str, None] = ...,\r\n            cookies: Any | None = ...,\r\n            files: Any | None = ...,\r\n            auth: Any | None = ...,\r\n            allow_redirects: bool = ...,\r\n            proxies: dict | None = ...,\r\n            hooks: Any | None = ...,\r\n            stream: Any | None = ...,\r\n            verify: Any | None = ...,\r\n            cert: Any | None = ...) -> bool:\r\n        \"\"\"用get方式跳转到url\r\n        :param url: 目标url\r\n        :param show_errmsg: 是否显示和抛出异常\r\n        :param retry: 重试次数，为None时使用页面对象retry_times属性值\r\n        :param interval: 重试间隔（秒），为None时使用页面对象retry_interval属性值\r\n        :param timeout: 连接超时时间\r\n        :param params: url中的参数\r\n        :param data: 携带的数据\r\n        :param json: 要发送的 JSON 数据，会自动设置 Content-Type 为 application/json\r\n        :param headers: 请求头\r\n        :param cookies: cookies信息\r\n        :param files: 要上传的文件，可以是一个字典，其中键是文件名，值是文件对象或文件路径\r\n        :param auth: 身份认证信息\r\n        :param allow_redirects: 是否允许重定向\r\n        :param proxies: 代理信息\r\n        :param hooks: 回调方法\r\n        :param stream: 是否使用流式传输\r\n        :param verify: 是否验证 SSL 证书\r\n        :param cert: SSL客户端证书文件的路径(.pem格式)，或('cert', 'key')元组\r\n        :return: s模式时返回url是否可用，d模式时返回获取到的Response对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def post(self,\r\n             url: str,\r\n             show_errmsg: bool = False,\r\n             retry: int | None = None,\r\n             interval: float | None = None,\r\n             timeout: float | None = ...,\r\n             params: dict | None = ...,\r\n             data: Any = ...,\r\n             json: Any = ...,\r\n             headers: Union[dict, str, None] = ...,\r\n             cookies: Any | None = ...,\r\n             files: Any | None = ...,\r\n             auth: Any | None = ...,\r\n             allow_redirects: bool = ...,\r\n             proxies: dict | None = ...,\r\n             hooks: Any | None = ...,\r\n             stream: Any | None = ...,\r\n             verify: Any | None = ...,\r\n             cert: Any | None = ...) -> bool:\r\n        \"\"\"用post方式跳转到url\r\n        :param url: 目标url\r\n        :param show_errmsg: 是否显示和抛出异常\r\n        :param retry: 重试次数，为None时使用页面对象retry_times属性值\r\n        :param interval: 重试间隔（秒），为None时使用页面对象retry_interval属性值\r\n        :param timeout: 连接超时时间\r\n        :param params: url中的参数\r\n        :param data: 携带的数据\r\n        :param json: 要发送的 JSON 数据，会自动设置 Content-Type 为 application/json\r\n        :param headers: 请求头\r\n        :param cookies: cookies信息\r\n        :param files: 要上传的文件，可以是一个字典，其中键是文件名，值是文件对象或文件路径\r\n        :param auth: 身份认证信息\r\n        :param allow_redirects: 是否允许重定向\r\n        :param proxies: 代理信息\r\n        :param hooks: 回调方法\r\n        :param stream: 是否使用流式传输\r\n        :param verify: 是否验证 SSL 证书\r\n        :param cert: SSL客户端证书文件的路径(.pem格式)，或('cert', 'key')元组\r\n        :return: s模式时返回url是否可用，d模式时返回获取到的Response对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def ele(self,\r\n            locator: Union[Tuple[str, str], str, SessionElement],\r\n            index: int = 1,\r\n            timeout: float = None) -> SessionElement:\r\n        \"\"\"返回页面中符合条件的一个元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是元素对象，loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 不起实际作用，用于和ChromiumElement对应，便于无差别调用\r\n        :return: SessionElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def eles(self,\r\n             locator: Union[Tuple[str, str], str],\r\n             timeout: float = None) -> SessionElementsList:\r\n        \"\"\"返回页面中所有符合条件的元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 不起实际作用，用于和ChromiumElement对应，便于无差别调用\r\n        :return: SessionElement对象或属性、文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_ele(self,\r\n              locator: Union[Tuple[str, str], str, SessionElement] = None,\r\n              index: int = 1) -> SessionElement:\r\n        \"\"\"返回页面中符合条件的一个元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是元素对象，loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :return: SessionElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList:\r\n        \"\"\"返回页面中符合条件的所有元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是元素对象，loc元组，或查询字符串\r\n        :return: SessionElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def _find_elements(self,\r\n                       locator: Union[Tuple[str, str], str, SessionElement],\r\n                       timeout: float,\r\n                       index: Optional[int] = 1,\r\n                       relative: bool = True,\r\n                       raise_err: bool = None) -> Union[SessionElement, SessionElementsList]:\r\n        \"\"\"返回页面中符合条件的元素、属性或节点文本，默认返回第一个\r\n        :param locator: 元素的定位信息，可以是元素对象，loc元组，或查询字符串\r\n        :param timeout: 不起实际作用，用于和父类对应\r\n        :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\r\n        :param raise_err: 找不到元素是是否抛出异常，为None时根据全局设置\r\n        :return: SessionElement对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def cookies(self,\r\n                all_domains: bool = False,\r\n                all_info: bool = False) -> CookiesList:\r\n        \"\"\"返回cookies\r\n        :param all_domains: 是否返回所有域的cookies\r\n        :param all_info: 是否返回所有信息，False则只返回name、value、domain\r\n        :return: cookies组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def close(self) -> None:\r\n        \"\"\"关闭Session对象\"\"\"\r\n        ...\r\n\r\n    def _s_connect(self,\r\n                   url: str,\r\n                   mode: str,\r\n                   show_errmsg: bool = False,\r\n                   retry: int = None,\r\n                   interval: float = None,\r\n                   **kwargs) -> bool:\r\n        \"\"\"执行get或post连接\r\n        :param url: 目标url\r\n        :param mode: 'get' 或 'post'\r\n        :param show_errmsg: 是否显示和抛出异常\r\n        :param retry: 重试次数\r\n        :param interval: 重试间隔（秒）\r\n        :param kwargs: 连接参数\r\n        :return: url是否可用\r\n        \"\"\"\r\n        ...\r\n\r\n    def _make_response(self,\r\n                       url: str,\r\n                       mode: str = 'get',\r\n                       retry: int = None,\r\n                       interval: float = None,\r\n                       show_errmsg: bool = False,\r\n                       **kwargs) -> Response:\r\n        \"\"\"生成Response对象\r\n        :param url: 目标url\r\n        :param mode: 'get' 或 'post'\r\n        :param show_errmsg: 是否显示和抛出异常\r\n        :param kwargs: 其它参数\r\n        :return: Response对象\r\n        \"\"\"\r\n        ...\r\n\r\n\r\ndef check_headers(kwargs: Union[dict, CaseInsensitiveDict],\r\n                  headers: Union[dict, CaseInsensitiveDict],\r\n                  arg: str) -> bool:\r\n    \"\"\"检查kwargs或headers中是否有arg所示属性\r\n    :param kwargs: 要检查的参数dict\r\n    :param headers: 要检查的headers\r\n    :param arg: 属性名称\r\n    :return: 检查结果\r\n    \"\"\"\r\n    ...\r\n\r\n\r\ndef set_charset(response: Response) -> Response:\r\n    \"\"\"设置Response对象的编码\r\n    :param response: Response对象\r\n    :return: Response对象\r\n    \"\"\"\r\n    ...\r\n"
  },
  {
    "path": "DrissionPage/_pages/web_page.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom .chromium_page import ChromiumPage\r\nfrom .session_page import SessionPage\r\nfrom .._base.base import BasePage\r\nfrom .._configs.chromium_options import ChromiumOptions\r\nfrom .._configs.session_options import SessionOptions\r\nfrom .._functions.cookies import set_session_cookies, set_tab_cookies\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._units.setter import WebPageSetter\r\n\r\n\r\nclass WebPage(SessionPage, ChromiumPage, BasePage):\r\n    def __new__(cls, mode='d', timeout=None, chromium_options=None, session_or_options=None):\r\n        # 即将废弃timeout\r\n        return super().__new__(cls, chromium_options)\r\n\r\n    def __init__(self, mode='d', timeout=None, chromium_options=None, session_or_options=None):\r\n        # 即将废弃timeout\r\n        if hasattr(self, '_created'):\r\n            return\r\n\r\n        mode = mode.lower()\r\n        if mode not in ('s', 'd'):\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'mode', ALLOW_VAL='\"s\", \"d\"', CURR_VAL=mode))\r\n        self._d_mode = mode == 'd'\r\n        self._has_driver = True\r\n        self._has_session = True\r\n\r\n        super().__init__(session_or_options=session_or_options)\r\n        if not chromium_options:\r\n            chromium_options = ChromiumOptions(read_file=chromium_options)\r\n            chromium_options.set_timeouts(base=self._timeout).set_paths(download_path=self.download_path)\r\n        super(SessionPage, self).__init__(addr_or_opts=chromium_options, timeout=timeout)  # 即将废弃timeout\r\n        self._type = 'WebPage'\r\n        self.change_mode(mode, go=False, copy_cookies=False)\r\n\r\n    def __call__(self, locator, index=1, timeout=None):\r\n        if self._d_mode:\r\n            return super(SessionPage, self).__call__(locator, index=index, timeout=timeout)\r\n        return super().__call__(locator, index=index)\r\n\r\n    def __repr__(self):\r\n        return f'<WebPage browser_id={self.browser.id} tab_id={self.tab_id}>'\r\n\r\n    @property\r\n    def latest_tab(self):\r\n        return self.browser._get_tab(id_or_num=self.tab_ids[0], mix=True, as_id=not _S.singleton_tab_obj)\r\n\r\n    @property\r\n    def set(self):\r\n        if self._set is None:\r\n            self._set = WebPageSetter(self)\r\n        return self._set\r\n\r\n    @property\r\n    def url(self):\r\n        return self._browser_url if self._d_mode else self._session_url\r\n\r\n    @property\r\n    def _browser_url(self):\r\n        return super(SessionPage, self).url if self._driver else None\r\n\r\n    @property\r\n    def title(self):\r\n        return super(SessionPage, self).title if self._d_mode else super().title\r\n\r\n    @property\r\n    def raw_data(self):\r\n        if self._d_mode:\r\n            return super(SessionPage, self).html if self._has_driver else ''\r\n        return super().raw_data\r\n\r\n    @property\r\n    def html(self):\r\n        if self._d_mode:\r\n            return super(SessionPage, self).html if self._has_driver else ''\r\n        return super().html\r\n\r\n    @property\r\n    def json(self):\r\n        return super(SessionPage, self).json if self._d_mode else super().json\r\n\r\n    @property\r\n    def response(self):\r\n        return self._response\r\n\r\n    @property\r\n    def mode(self):\r\n        return 'd' if self._d_mode else 's'\r\n\r\n    @property\r\n    def user_agent(self):\r\n        return super(SessionPage, self).user_agent if self._d_mode else super().user_agent\r\n\r\n    @property\r\n    def session(self):\r\n        if self._session is None:\r\n            self._create_session()\r\n        return self._session\r\n\r\n    @property\r\n    def _session_url(self):\r\n        return self._response.url if self._response else None\r\n\r\n    @property\r\n    def timeout(self):\r\n        return self.timeouts.base if self._d_mode else self._timeout\r\n\r\n    @property\r\n    def download_path(self):\r\n        return self.browser.download_path\r\n\r\n    def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):\r\n        if self._d_mode:\r\n            return super(SessionPage, self).get(url, show_errmsg, retry, interval, timeout)\r\n\r\n        if timeout is None:\r\n            timeout = self.timeouts.page_load if self._has_driver else self.timeout\r\n        return super().get(url, show_errmsg, retry, interval, timeout, **kwargs)\r\n\r\n    def post(self, url, show_errmsg=False, retry=None, interval=None, **kwargs):\r\n        if self.mode == 'd':\r\n            self.cookies_to_session()\r\n        super().post(url, show_errmsg, retry, interval, **kwargs)\r\n        return self.response\r\n\r\n    def ele(self, locator, index=1, timeout=None):\r\n        return (super(SessionPage, self).ele(locator, index=index, timeout=timeout)\r\n                if self._d_mode else super().ele(locator, index=index))\r\n\r\n    def eles(self, locator, timeout=None):\r\n        return super(SessionPage, self).eles(locator, timeout=timeout) if self._d_mode else super().eles(locator)\r\n\r\n    def s_ele(self, locator=None, index=1, timeout=None):\r\n        return (super(SessionPage, self).s_ele(locator, index=index, timeout=timeout)\r\n                if self._d_mode else super().s_ele(locator, index=index, timeout=timeout))\r\n\r\n    def s_eles(self, locator, timeout=None):\r\n        return (super(SessionPage, self).s_eles(locator, timeout=timeout)\r\n                if self._d_mode else super().s_eles(locator, timeout=timeout))\r\n\r\n    def change_mode(self, mode=None, go=True, copy_cookies=True):\r\n        if mode:\r\n            mode = mode.lower()\r\n        if mode is not None and ((mode == 'd' and self._d_mode) or (mode == 's' and not self._d_mode)):\r\n            return\r\n\r\n        self._d_mode = not self._d_mode\r\n\r\n        # s模式转d模式\r\n        if self._d_mode:\r\n            if self._driver is None or not self._driver.is_running:\r\n                self._driver_init(self.tab_id)\r\n                self._get_document()\r\n\r\n            self._url = None if not self._has_driver else super(SessionPage, self).url\r\n            self._has_driver = True\r\n            if self._session_url:\r\n                if copy_cookies:\r\n                    self.cookies_to_browser()\r\n                if go:\r\n                    self.get(self._session_url)\r\n\r\n            return\r\n\r\n        # d模式转s模式\r\n        self._has_session = True\r\n        self._url = self._session_url\r\n\r\n        if self._has_driver and self._driver.is_running:\r\n            if copy_cookies:\r\n                self.cookies_to_session()\r\n            if go and not self.get(super(SessionPage, self).url):\r\n                raise ConnectionError(_S._lang.join(_S._lang.S_MODE_GET_FAILED))\r\n\r\n    def cookies_to_session(self, copy_user_agent=True):\r\n        if not self._has_session:\r\n            return\r\n\r\n        if copy_user_agent:\r\n            user_agent = self._run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']\r\n            self._headers.update({\"User-Agent\": user_agent})\r\n\r\n        set_session_cookies(self.session, super(SessionPage, self).cookies())\r\n\r\n    def cookies_to_browser(self):\r\n        if not self._has_driver:\r\n            return\r\n        set_tab_cookies(self, super().cookies())\r\n\r\n    def cookies(self, all_domains=False, all_info=False):\r\n        return super(SessionPage, self).cookies(all_domains, all_info) if self._d_mode \\\r\n            else super().cookies(all_domains, all_info)\r\n\r\n    def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', as_id=False):\r\n        return self.browser._get_tab(id_or_num=id_or_num, title=title, url=url,\r\n                                     tab_type=tab_type, mix=True, as_id=as_id)\r\n\r\n    def get_tabs(self, title=None, url=None, tab_type='page', as_id=False):\r\n        return self.browser._get_tabs(title=title, url=url, tab_type=tab_type, mix=True, as_id=as_id)\r\n\r\n    def new_tab(self, url=None, new_window=False, background=False, new_context=False):\r\n        return self.browser._new_tab(url=url, new_window=new_window, background=background, new_context=new_context)\r\n\r\n    def close_driver(self):\r\n        if self._has_driver:\r\n            self.change_mode('s')\r\n            try:\r\n                self.driver.run('Browser.close')\r\n            except Exception:\r\n                pass\r\n            self._driver.stop()\r\n            self._driver = None\r\n            self._has_driver = None\r\n\r\n    def close_session(self):\r\n        if self._has_session:\r\n            self.change_mode('d')\r\n            self._session.close()\r\n            if self._response is not None:\r\n                self._response.close()\r\n            self._session = None\r\n            self._response = None\r\n            self._has_session = None\r\n\r\n    def close(self):\r\n        if self._has_driver:\r\n            self.browser._close_tab(self)\r\n        if self._session:\r\n            self._session.close()\r\n            if self._response is not None:\r\n                self._response.close()\r\n\r\n    def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):\r\n        if self._d_mode:\r\n            return super(SessionPage, self)._find_elements(locator, timeout=timeout, index=index, relative=relative)\r\n        return super()._find_elements(locator, index=index, timeout=timeout)\r\n\r\n    def quit(self, timeout=5, force=True, del_data=False):\r\n        if self._has_session:\r\n            self._session.close()\r\n            self._session = None\r\n            self._response = None\r\n            self._has_session = None\r\n        if self._has_driver:\r\n            super(SessionPage, self).quit(timeout, force, del_data=del_data)\r\n            self._driver = None\r\n            self._has_driver = None\r\n\r\n    def _set_session_options(self, session_or_options=None):\r\n        if session_or_options is None:\r\n            session_or_options = self.browser._session_options or SessionOptions(\r\n                read_file=self.browser._session_options is None)\r\n        super()._set_session_options(session_or_options)\r\n"
  },
  {
    "path": "DrissionPage/_pages/web_page.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom http.cookiejar import CookieJar\r\nfrom typing import Union, Tuple, List, Any, Optional, Literal\r\n\r\nfrom requests import Session, Response\r\n\r\nfrom .chromium_frame import ChromiumFrame\r\nfrom .chromium_page import ChromiumPage\r\nfrom .mix_tab import MixTab\r\nfrom .session_page import SessionPage\r\nfrom .._base.base import BasePage\r\nfrom .._configs.chromium_options import ChromiumOptions\r\nfrom .._configs.session_options import SessionOptions\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._elements.session_element import SessionElement\r\nfrom .._functions.cookies import CookiesList\r\nfrom .._functions.elements import SessionElementsList, ChromiumElementsList\r\nfrom .._units.setter import WebPageSetter\r\nfrom .._units.waiter import WebPageWaiter\r\n\r\n\r\nclass WebPage(SessionPage, ChromiumPage, BasePage):\r\n    \"\"\"整合浏览器和request的页面类\"\"\"\r\n    _d_mode: bool = ...\r\n    _set: WebPageSetter = ...\r\n    _has_driver: Optional[bool] = ...\r\n    _has_session: Optional[bool] = ...\r\n    _session_options: Union[SessionOptions, None] = ...\r\n    _chromium_options: Union[ChromiumOptions, None] = ...\r\n\r\n    def __init__(self,\r\n                 mode: str = 'd',\r\n                 chromium_options: Union[ChromiumOptions, bool] = None,\r\n                 session_or_options: Union[Session, SessionOptions, bool] = None) -> None:\r\n        \"\"\"初始化函数\r\n        :param mode: 'd' 或 's'，即driver模式和session模式\r\n        :param chromium_options: ChromiumOptions对象，传入None时从默认ini文件读取，传入False时不读取ini文件，使用默认配置\r\n        :param session_or_options: Session对象或SessionOptions对象，传入None时从默认ini文件读取，传入False时不读取ini文件，使用默认配置\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self,\r\n                 locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement],\r\n                 index: int = 1,\r\n                 timeout: float = None) -> Union[ChromiumElement, SessionElement]:\r\n        \"\"\"在内部查找元素\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 超时时间（秒）\r\n        :return: 子元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def latest_tab(self) -> Union[MixTab, WebPage, str]:\r\n        \"\"\"返回最新的标签页，最新标签页指最后创建或最后被激活的\r\n        当Settings.singleton_tab_obj==True时返回Tab对象，否则返回tab id\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def set(self) -> WebPageSetter:\r\n        \"\"\"返回用于设置的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def wait(self) -> WebPageWaiter:\r\n        \"\"\"返回用于等待的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def url(self) -> Union[str, None]:\r\n        \"\"\"返回浏览器当前url\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def _browser_url(self) -> Union[str, None]:\r\n        \"\"\"返回浏览器当前url\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def title(self) -> str:\r\n        \"\"\"返回当前页面title\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def raw_data(self) -> Union[str, bytes]:\r\n        \"\"\"返回页码原始数据数据\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def html(self) -> str:\r\n        \"\"\"返回页面html文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def json(self) -> dict:\r\n        \"\"\"当返回内容是json格式时，返回对应的字典\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def response(self) -> Response:\r\n        \"\"\"返回 s 模式获取到的 Response 对象，切换到 s 模式\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def mode(self) -> Literal['s', 'd']:\r\n        \"\"\"返回当前模式，'s'或'd' \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def user_agent(self) -> str:\r\n        \"\"\"返回user agent\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def session(self) -> Session:\r\n        \"\"\"返回Session对象，如未初始化则按配置信息创建\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def _session_url(self) -> str:\r\n        \"\"\"返回 session 保存的url\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def timeout(self) -> float:\r\n        \"\"\"返回通用timeout设置\"\"\"\r\n        ...\r\n\r\n    def get(self,\r\n            url: str,\r\n            show_errmsg: bool = False,\r\n            retry: Optional[int] = None,\r\n            interval: Optional[float] = None,\r\n            timeout: Optional[float] = None,\r\n            params: Optional[dict] = None,\r\n            data: Any = None,\r\n            json: Any = None,\r\n            headers: Optional[dict] = None,\r\n            cookies: Union[CookieJar, dict] = None,\r\n            files: Optional[Any] = None,\r\n            auth: Optional[Any] = None,\r\n            allow_redirects: bool = True,\r\n            proxies: Optional[dict] = None,\r\n            hooks: Optional[Any] = None,\r\n            stream: bool = None,\r\n            verify: Union[bool, str] = None,\r\n            cert: [str, Tuple[str, str]] = None) -> Union[bool, None]:\r\n        \"\"\"跳转到一个url\r\n        :param url: 目标url\r\n        :param show_errmsg: 是否显示和抛出异常\r\n        :param retry: 重试次数，为None时使用页面对象retry_times属性值\r\n        :param interval: 重试间隔（秒），为None时使用页面对象retry_interval属性值\r\n        :param timeout: 连接超时时间\r\n        :param params: url中的参数\r\n        :param data: 携带的数据\r\n        :param json: 要发送的 JSON 数据，会自动设置 Content-Type 为 application/json\r\n        :param headers: 请求头\r\n        :param cookies: cookies信息\r\n        :param files: 要上传的文件，可以是一个字典，其中键是文件名，值是文件对象或文件路径\r\n        :param auth: 身份认证信息\r\n        :param allow_redirects: 是否允许重定向\r\n        :param proxies: 代理信息\r\n        :param hooks: 回调方法\r\n        :param stream: 是否使用流式传输\r\n        :param verify: 是否验证 SSL 证书\r\n        :param cert: SSL客户端证书文件的路径(.pem格式)，或('cert', 'key')元组\r\n        :return: s模式时返回url是否可用，d模式时返回获取到的Response对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def post(self,\r\n             url: str,\r\n             show_errmsg: bool = False,\r\n             retry: Optional[int] = None,\r\n             interval: Optional[float] = None,\r\n             timeout: Optional[float] = None,\r\n             params: Optional[dict] = None,\r\n             data: Any = None,\r\n             json: Any = None,\r\n             headers: Optional[dict] = None,\r\n             cookies: Union[CookieJar, dict] = None,\r\n             files: Optional[Any] = None,\r\n             auth: Optional[Any] = None,\r\n             allow_redirects: bool = True,\r\n             proxies: Optional[dict] = None,\r\n             hooks: Optional[Any] = None,\r\n             stream: bool = None,\r\n             verify: Union[bool, str] = None,\r\n             cert: [str, Tuple[str, str]] = None) -> Response:\r\n        \"\"\"用post方式跳转到url\r\n        :param url: 目标url\r\n        :param show_errmsg: 是否显示和抛出异常\r\n        :param retry: 重试次数，为None时使用页面对象retry_times属性值\r\n        :param interval: 重试间隔（秒），为None时使用页面对象retry_interval属性值\r\n        :param timeout: 连接超时时间\r\n        :param params: url中的参数\r\n        :param data: 携带的数据\r\n        :param json: 要发送的 JSON 数据，会自动设置 Content-Type 为 application/json\r\n        :param headers: 请求头\r\n        :param cookies: cookies信息\r\n        :param files: 要上传的文件，可以是一个字典，其中键是文件名，值是文件对象或文件路径\r\n        :param auth: 身份认证信息\r\n        :param allow_redirects: 是否允许重定向\r\n        :param proxies: 代理信息\r\n        :param hooks: 回调方法\r\n        :param stream: 是否使用流式传输\r\n        :param verify: 是否验证 SSL 证书\r\n        :param cert: SSL客户端证书文件的路径(.pem格式)，或('cert', 'key')元组\r\n        :return: 获取到的Response对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def ele(self,\r\n            locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement],\r\n            index: int = 1,\r\n            timeout: float = None) -> Union[ChromiumElement, SessionElement]:\r\n        \"\"\"返回第一个符合条件的元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是元素对象，loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\r\n        :return: 元素对象或属性、文本节点文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def eles(self,\r\n             locator: Union[Tuple[str, str], str],\r\n             timeout: float = None) -> Union[ChromiumElementsList, SessionElementsList]:\r\n        \"\"\"返回页面中所有符合条件的元素、属性或节点文本\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\r\n        :return: 元素对象或属性、文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_ele(self,\r\n              locator: Union[Tuple[str, str], str] = None,\r\n              index: int = 1,\r\n              timeout: float = None) -> SessionElement:\r\n        \"\"\"查找第一个符合条件的元素以SessionElement形式返回，d模式处理复杂页面时效率很高\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param index: 获取第几个，从1开始，可传入负数获取倒数第几个\r\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\r\n        :return: SessionElement对象或属性、文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def s_eles(self, locator: Union[Tuple[str, str], str], timeout: float = None) -> SessionElementsList:\r\n        \"\"\"查找所有符合条件的元素以SessionElement形式返回，d模式处理复杂页面时效率很高\r\n        :param locator: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param timeout: 查找元素超时时间（秒），默认与页面等待时间一致\r\n        :return: SessionElement对象或属性、文本组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def change_mode(self,\r\n                    mode: str = None,\r\n                    go: bool = True,\r\n                    copy_cookies: bool = True) -> None:\r\n        \"\"\"切换模式，接收's'或'd'，除此以外的字符串会切换为 d 模式\r\n        如copy_cookies为True，切换时会把当前模式的cookies复制到目标模式\r\n        切换后，如果go是True，调用相应的get函数使访问的页面同步\r\n        :param mode: 模式字符串\r\n        :param go: 是否跳转到原模式的url\r\n        :param copy_cookies: 是否复制cookies到目标模式\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def cookies_to_session(self, copy_user_agent: bool = True) -> None:\r\n        \"\"\"把driver对象的cookies复制到session对象\r\n        :param copy_user_agent: 是否复制ua信息\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def cookies_to_browser(self) -> None:\r\n        \"\"\"把session对象的cookies复制到浏览器\"\"\"\r\n        ...\r\n\r\n    def cookies(self,\r\n                all_domains: bool = False,\r\n                all_info: bool = False) -> CookiesList:\r\n        \"\"\"返回cookies\r\n        :param all_domains: 是否返回所有域的cookies\r\n        :param all_info: 是否返回所有信息，False则只返回name、value、domain\r\n        :return: cookies信息\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_tab(self,\r\n                id_or_num: Union[str, MixTab, int] = None,\r\n                title: str = None,\r\n                url: str = None,\r\n                tab_type: Union[str, list, tuple] = 'page',\r\n                as_id: bool = False) -> Union[MixTab, str, None]:\r\n        \"\"\"获取一个标签页对象，id_or_num不为None时，后面几个参数无效\r\n        :param id_or_num: 要获取的标签页id或序号，序号从1开始，可传入负数获取倒数第几个，不是视觉排列顺序，而是激活顺序\r\n        :param title: 要匹配title的文本，模糊匹配，为None则匹配所有\r\n        :param url: 要匹配url的文本，模糊匹配，为None则匹配所有\r\n        :param tab_type: tab类型，可用列表输入多个，如 'page', 'iframe' 等，为None则匹配所有\r\n        :param as_id: 是否返回标签页id而不是标签页对象\r\n        :return: MixTab对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_tabs(self,\r\n                 title: str = None,\r\n                 url: str = None,\r\n                 tab_type: Union[str, list, tuple] = 'page',\r\n                 as_id: bool = False) -> Union[List[MixTab], List[str]]:\r\n        \"\"\"查找符合条件的tab，返回它们组成的列表\r\n        :param title: 要匹配title的文本，模糊匹配，为None则匹配所有\r\n        :param url: 要匹配url的文本，模糊匹配，为None则匹配所有\r\n        :param tab_type: tab类型，可用列表输入多个，如 'page', 'iframe' 等，为None则匹配所有\r\n        :param as_id: 是否返回标签页id而不是标签页对象\r\n        :return: ChromiumTab对象组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def new_tab(self,\r\n                url: str = None,\r\n                new_window: bool = False,\r\n                background: bool = False,\r\n                new_context: bool = False) -> MixTab:\r\n        \"\"\"新建一个标签页\r\n        :param url: 新标签页跳转到的网址\r\n        :param new_window: 是否在新窗口打开标签页\r\n        :param background: 是否不激活新标签页，如new_window为True则无效\r\n        :param new_context: 是否创建新的上下文\r\n        :return: 新标签页对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def close_driver(self) -> None:\r\n        \"\"\"关闭driver及浏览器\"\"\"\r\n        ...\r\n\r\n    def close_session(self) -> None:\r\n        \"\"\"关闭session\"\"\"\r\n        ...\r\n\r\n    def close(self) -> None:\r\n        \"\"\"关闭标签页和Session\"\"\"\r\n        ...\r\n\r\n    def _find_elements(self,\r\n                       locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement, ChromiumFrame],\r\n                       timeout: float,\r\n                       index: Optional[int] = 1,\r\n                       relative: bool = False,\r\n                       raise_err: bool = None) -> Union[\r\n        ChromiumElement, SessionElement, ChromiumFrame, SessionElementsList, ChromiumElementsList]:\r\n        \"\"\"返回页面中符合条件的元素、属性或节点文本，默认返回第一个\r\n        :param locator: 元素的定位信息，可以是元素对象，loc元组，或查询字符串\r\n        :param timeout: 查找元素超时时间（秒），d模式专用\r\n        :param index: 第几个结果，从1开始，可传入负数获取倒数第几个，为None返回所有\r\n        :param relative: MixTab用的表示是否相对定位的参数\r\n        :param raise_err: 找不到元素是是否抛出异常，为None时根据全局设置\r\n        :return: 元素对象或属性、文本节点文本\r\n        \"\"\"\r\n        ...\r\n\r\n    def quit(self,\r\n             timeout: float = 5,\r\n             force: bool = True,\r\n             del_data: bool = False) -> None:\r\n        \"\"\"关闭浏览器和Session\r\n        :param timeout: 等待浏览器关闭超时时间（秒）\r\n        :param force: 关闭超时是否强制终止进程\r\n        :param del_data: 是否删除用户文件夹\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/actions.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom time import sleep, perf_counter\r\n\r\nfrom .._functions.keys import modifierBit, make_input_data, input_text_or_keys, Keys\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._functions.web import location_in_viewport\r\n\r\n\r\nclass Actions:\r\n\r\n    def __init__(self, owner):\r\n        self.owner = owner\r\n        self._dr = owner.driver\r\n        self.modifier = 0  # 修饰符，Alt=1, Ctrl=2, Meta/Command=4, Shift=8\r\n        self.curr_x = 0  # 视口坐标\r\n        self.curr_y = 0\r\n        self._holding = 'left'\r\n\r\n    def move_to(self, ele_or_loc, offset_x=None, offset_y=None, duration=.5):\r\n        is_loc = False\r\n        mid_point = offset_x == offset_y is None\r\n        if offset_x is None:\r\n            offset_x = 0\r\n        if offset_y is None:\r\n            offset_y = 0\r\n        if isinstance(ele_or_loc, (tuple, list)):\r\n            is_loc = True\r\n            lx = ele_or_loc[0] + offset_x\r\n            ly = ele_or_loc[1] + offset_y\r\n        elif isinstance(ele_or_loc, str) or ele_or_loc._type == 'ChromiumElement':\r\n            ele_or_loc = self.owner(ele_or_loc)\r\n            self.owner.scroll.to_see(ele_or_loc)\r\n            x, y = ele_or_loc.rect.midpoint if mid_point else ele_or_loc.rect.location\r\n            lx = x + offset_x\r\n            ly = y + offset_y\r\n        else:\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'ele_or_loc',\r\n                                           ALLOW_TYPE=_S._lang.ELE_LOC_FORMAT, CURR_VAL=ele_or_loc))\r\n\r\n        if not location_in_viewport(self.owner, lx, ly):\r\n            # 把坐标滚动到页面中间\r\n            clientWidth = self.owner._run_js('return document.body.clientWidth;')\r\n            clientHeight = self.owner._run_js('return document.body.clientHeight;')\r\n            self.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2)\r\n\r\n        # 这样设计为了应付那些不随滚动条滚动的元素\r\n        if is_loc:\r\n            cx, cy = location_to_client(self.owner, lx, ly)\r\n        else:\r\n            x, y = ele_or_loc.rect.viewport_midpoint if mid_point else ele_or_loc.rect.viewport_location\r\n            cx = x + offset_x\r\n            cy = y + offset_y\r\n\r\n        ox = cx - self.curr_x\r\n        oy = cy - self.curr_y\r\n        self.move(ox, oy, duration)\r\n        return self\r\n\r\n    def move(self, offset_x=0, offset_y=0, duration=.5):\r\n        duration = .02 if duration < .02 else duration\r\n        num = int(duration * 50)\r\n\r\n        points = [(self.curr_x + i * (offset_x / num),\r\n                   self.curr_y + i * (offset_y / num)) for i in range(1, num)]\r\n        points.append((self.curr_x + offset_x, self.curr_y + offset_y))\r\n\r\n        for x, y in points:\r\n            t = perf_counter()\r\n            self.curr_x = x\r\n            self.curr_y = y\r\n            self._dr.run('Input.dispatchMouseEvent', type='mouseMoved', button=self._holding,\r\n                         x=self.curr_x, y=self.curr_y, modifiers=self.modifier)\r\n            ss = .02 - perf_counter() + t\r\n            if ss > 0:\r\n                sleep(ss)\r\n\r\n        return self\r\n\r\n    def click(self, on_ele=None, times=1):\r\n        self._hold(on_ele, 'left', times).wait(.05)._release('left')\r\n        return self\r\n\r\n    def r_click(self, on_ele=None, times=1):\r\n        self._hold(on_ele, 'right', times).wait(.05)._release('right')\r\n        return self\r\n\r\n    def m_click(self, on_ele=None, times=1):\r\n        self._hold(on_ele, 'middle', times).wait(.05)._release('middle')\r\n        return self\r\n\r\n    def hold(self, on_ele=None):\r\n        self._hold(on_ele, 'left')\r\n        return self\r\n\r\n    def release(self, on_ele=None):\r\n        if on_ele:\r\n            self.move_to(on_ele, duration=.2)\r\n        self._release('left')\r\n        return self\r\n\r\n    def r_hold(self, on_ele=None):\r\n        self._hold(on_ele, 'right')\r\n        return self\r\n\r\n    def r_release(self, on_ele=None):\r\n        if on_ele:\r\n            self.move_to(on_ele, duration=.2)\r\n        self._release('right')\r\n        return self\r\n\r\n    def m_hold(self, on_ele=None):\r\n        self._hold(on_ele, 'middle')\r\n        return self\r\n\r\n    def m_release(self, on_ele=None):\r\n        if on_ele:\r\n            self.move_to(on_ele, duration=.2)\r\n        self._release('middle')\r\n        return self\r\n\r\n    def _hold(self, on_ele=None, button='left', count=1):\r\n        if on_ele:\r\n            self.move_to(on_ele, duration=.2)\r\n        self._dr.run('Input.dispatchMouseEvent', type='mousePressed', button=button, clickCount=count,\r\n                     x=self.curr_x, y=self.curr_y, modifiers=self.modifier)\r\n        self._holding = button\r\n        return self\r\n\r\n    def _release(self, button):\r\n        self._dr.run('Input.dispatchMouseEvent', type='mouseReleased', button=button, clickCount=1,\r\n                     x=self.curr_x, y=self.curr_y, modifiers=self.modifier)\r\n        self._holding = 'left'\r\n        return self\r\n\r\n    def scroll(self, delta_y=0, delta_x=0, on_ele=None):\r\n        if on_ele:\r\n            self.move_to(on_ele, duration=.2)\r\n        self._dr.run('Input.dispatchMouseEvent', type='mouseWheel', x=self.curr_x, y=self.curr_y,\r\n                     deltaX=delta_x, deltaY=delta_y, modifiers=self.modifier)\r\n        return self\r\n\r\n    def up(self, pixel):\r\n        return self.move(0, -pixel)\r\n\r\n    def down(self, pixel):\r\n        return self.move(0, pixel)\r\n\r\n    def left(self, pixel):\r\n        return self.move(-pixel, 0)\r\n\r\n    def right(self, pixel):\r\n        return self.move(pixel, 0)\r\n\r\n    def key_down(self, key):\r\n        key = getattr(Keys, key.upper(), key)\r\n        if key in ('\\ue009', '\\ue008', '\\ue00a', '\\ue03d'):  # 如果上修饰符，添加到变量\r\n            self.modifier |= modifierBit.get(key, 0)\r\n            return self\r\n\r\n        data = make_input_data(self.modifier, key, False)\r\n        if not data:\r\n            raise ValueError(_S._lang.join(_S._lang.NO_SUCH_KEY_, key))\r\n        self.owner._run_cdp('Input.dispatchKeyEvent', **data)\r\n        return self\r\n\r\n    def key_up(self, key):\r\n        key = getattr(Keys, key.upper(), key)\r\n        if key in ('\\ue009', '\\ue008', '\\ue00a', '\\ue03d'):  # 如果上修饰符，添加到变量\r\n            self.modifier ^= modifierBit.get(key, 0)\r\n            return self\r\n\r\n        data = make_input_data(self.modifier, key, True)\r\n        if not data:\r\n            raise ValueError(_S._lang.join(_S._lang.NO_SUCH_KEY_, key))\r\n        self.owner._run_cdp('Input.dispatchKeyEvent', **data)\r\n        return self\r\n\r\n    def type(self, keys, interval=0):\r\n        modifiers = []\r\n        if not isinstance(keys, (str, tuple, list)):\r\n            keys = str(keys)\r\n        for i in keys:\r\n            for character in i:\r\n                if character in ('\\ue009', '\\ue008', '\\ue00a', '\\ue03d'):\r\n                    self.modifier |= modifierBit.get(character, 0)\r\n                    modifiers.append(character)\r\n                data = make_input_data(self.modifier, character, False)\r\n                if data:\r\n                    self.owner._run_cdp('Input.dispatchKeyEvent', **data)\r\n                    if character not in ('\\ue009', '\\ue008', '\\ue00a', '\\ue03d'):\r\n                        data['type'] = 'keyUp'\r\n                        self.owner._run_cdp('Input.dispatchKeyEvent', **data)\r\n\r\n                else:\r\n                    self.owner._run_cdp('Input.dispatchKeyEvent', type='char', text=character)\r\n                sleep(interval)\r\n\r\n        for m in modifiers:\r\n            self.key_up(m)\r\n        return self\r\n\r\n    def input(self, text):\r\n        input_text_or_keys(self.owner, text)\r\n        return self\r\n\r\n    def drag_in(self, ele_or_loc, files=None, text=None, title=None, baseURL=None):\r\n        ele_or_loc = self.owner(ele_or_loc)\r\n        x, y = ele_or_loc.rect.viewport_midpoint\r\n        if files:\r\n            items = []\r\n            paths = []\r\n            if isinstance(files, str):\r\n                files = [files]\r\n            for file in files:\r\n                path = str(Path(file).absolute())\r\n                item = {'mimeType': 'text/plain', 'data': path}\r\n                items.append(item)\r\n                paths.append(path)\r\n            data = {'items': items, 'files': paths, 'dragOperationsMask': 16}\r\n\r\n        elif text:\r\n            item = {'data': text}\r\n            if title is not None:\r\n                item['title'] = title\r\n                item['mimeType'] = 'text/uri-list'\r\n            elif baseURL is not None:\r\n                item['baseURL'] = baseURL\r\n                item['mimeType'] = 'text/uri-list'\r\n            else:\r\n                item['mimeType'] = 'text/plain'\r\n            data = {'items': [item], 'dragOperationsMask': 1}\r\n\r\n        else:\r\n            raise ValueError(_S._lang.NEED_FILES_OR_TEXT_ARG)\r\n\r\n        self._dr.run('Input.dispatchDragEvent', type='dragEnter', x=x, y=y, data=data, modifiers=self.modifier)\r\n        self._dr.run('Input.dispatchDragEvent', type='drop', x=x, y=y, data=data, modifiers=self.modifier)\r\n        return self\r\n\r\n    def wait(self, second, scope=None):\r\n        self.owner.wait(second=second, scope=scope)\r\n        return self\r\n\r\n\r\ndef location_to_client(page, lx, ly):\r\n    scroll_x = page._run_js('return document.documentElement.scrollLeft;')\r\n    scroll_y = page._run_js('return document.documentElement.scrollTop;')\r\n    return lx - scroll_x, ly - scroll_y\r\n"
  },
  {
    "path": "DrissionPage/_units/actions.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Union, Tuple, Any, Literal\r\n\r\nfrom .._base.driver import Driver\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._pages.chromium_base import ChromiumBase\r\n\r\nKEYS = Literal['NULL', 'CANCEL', 'HELP', 'BACKSPACE', 'meta',\r\n'TAB', 'CLEAR', 'RETURN', 'ENTER', 'SHIFT', 'CONTROL', 'command ',\r\n'CTRL', 'ALT', 'PAUSE', 'ESCAPE', 'SPACE',\r\n'PAGE_UP', 'PAGE_DOWN', 'END', 'HOME', 'LEFT', 'UP',\r\n'RIGHT', 'DOWN', 'INSERT',\r\n'DELETE', 'DEL', 'SEMICOLON', 'EQUALS', 'NUMPAD0', 'NUMPAD1', 'NUMPAD2',\r\n'NUMPAD3', 'NUMPAD4', 'NUMPAD5', 'NUMPAD6', 'NUMPAD7', 'NUMPAD8', 'NUMPAD9',\r\n'MULTIPLY', 'ADD', 'SUBTRACT', 'DECIMAL', 'DIVIDE', 'F1', 'F2',\r\n'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'META', 'COMMAND ',\r\n'null', 'cancel', 'help', 'backspace', 'tab', 'clear', 'return', 'enter',\r\n'shift', 'control', 'ctrl', 'alt', 'pause',\r\n'escape', 'space', 'page_up', 'page_down', 'end', 'home', 'left', 'up',\r\n'right', 'down', 'insert', 'delete', 'del',\r\n'semicolon', 'equals', 'numpad0', 'numpad1', 'numpad2', 'numpad3', 'numpad4', 'numpad5',\r\n'numpad6', 'numpad7', 'numpad8', 'numpad9', 'multiply', 'add', 'subtract', 'decimal',\r\n'divide', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12',\r\n'\\ue000', '\\ue002', '\\ue003', '\\ue004', '\\ue005', '\\ue006', '\\ue007', '\\ue008', '\\ue009',\r\n'\\ue009', '\\ue00a', '\\ue00b', '\\ue00c', '\\ue00d', '\\ue00e', '\\ue00f', '\\ue010', '\\ue011',\r\n'\\ue012', '\\ue013', '\\ue014', '\\ue015', '\\ue016', '\\ue017', '\\ue017', '\\ue018', '\\ue019',\r\n'\\ue01a', '\\ue01b', '\\ue01c', '\\ue01d', '\\ue01e', '\\ue01f', '\\ue020', '\\ue021', '\\ue022',\r\n'\\ue023', '\\ue024', '\\ue025', '\\ue027', '\\ue028', '\\ue029', '\\ue031', '\\ue032', '\\ue033', '\\ue034',\r\n'\\ue035', '\\ue036', '\\ue037', '\\ue038', '\\ue039', '\\ue03a', '\\ue03b', '\\ue03c', '\\ue03d', '\\ue03d',\r\n'`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'q', 'w',\r\n'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\\\', 'a', 's', 'd', 'f',\r\n'g', 'h', 'j', 'k', 'l', ';', '\\'', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',',\r\n'.', '/', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+',\r\n'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', 'A', 'S', 'D',\r\n'F', 'G', 'H', 'J', 'K', 'L', ':', '\"', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?'\r\n]\r\n\r\n\r\nclass Actions:\r\n    \"\"\"用于实现动作链的类\"\"\"\r\n\r\n    owner: ChromiumBase = ...\r\n    _dr: Driver = ...\r\n    modifier: int = ...\r\n    curr_x: float = ...\r\n    curr_y: float = ...\r\n    _holding: str = ...\r\n\r\n    def __init__(self, owner: ChromiumBase):\r\n        \"\"\"\r\n        :param owner: ChromiumBase对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def move_to(self, ele_or_loc: Union[ChromiumElement, Tuple[float, float], str],\r\n                offset_x: float = 0, offset_y: float = 0, duration: float = .5) -> Actions:\r\n        \"\"\"鼠标移动到元素中点，或页面上的某个绝对坐标。可设置偏移量\r\n        当带偏移量时，偏移量相对于元素左上角坐标\r\n        :param ele_or_loc: 元素对象、绝对坐标或文本定位符，坐标为tuple(int, int)形式\r\n        :param offset_x: 偏移量x\r\n        :param offset_y: 偏移量y\r\n        :param duration: 拖动用时，传入0即瞬间到达\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def move(self, offset_x: float = 0, offset_y: float = 0, duration: float = .5) -> Actions:\r\n        \"\"\"鼠标相对当前位置移动若干位置\r\n        :param offset_x: 偏移量x\r\n        :param offset_y: 偏移量y\r\n        :param duration: 拖动用时，传入0即瞬间到达\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def click(self, on_ele: Union[ChromiumElement, str] = None, times: int = 1) -> Actions:\r\n        \"\"\"点击鼠标左键，可先移动到元素上\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :param times: 点击次数\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def r_click(self, on_ele: Union[ChromiumElement, str] = None, times: int = 1) -> Actions:\r\n        \"\"\"点击鼠标右键，可先移动到元素上\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :param times: 点击次数\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def m_click(self, on_ele: Union[ChromiumElement, str] = None, times: int = 1) -> Actions:\r\n        \"\"\"点击鼠标中键，可先移动到元素上\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :param times: 点击次数\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:\r\n        \"\"\"按住鼠标左键，可先移动到元素上\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def release(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:\r\n        \"\"\"释放鼠标左键，可先移动到元素上\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def r_hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:\r\n        \"\"\"按住鼠标右键，可先移动到元素上\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def r_release(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:\r\n        \"\"\"释放鼠标右键，可先移动到元素上\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def m_hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:\r\n        \"\"\"按住鼠标中键，可先移动到元素上\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def m_release(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:\r\n        \"\"\"释放鼠标中键，可先移动到元素上\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def _hold(self,\r\n              on_ele: Union[ChromiumElement, str] = None,\r\n              button: str = 'left',\r\n              count: int = 1) -> Actions:\r\n        \"\"\"按下鼠标按键\r\n        :param on_ele: ChromiumElement元素或文本定位符\r\n        :param button: 要按下的按键\r\n        :param count: 点击次数\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def _release(self, button: str) -> Actions:\r\n        \"\"\"释放鼠标按键\r\n        :param button: 要释放的按键\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def scroll(self, delta_y: int = 0, delta_x: int = 0,\r\n               on_ele: Union[ChromiumElement, str] = None) -> Actions:\r\n        \"\"\"滚动鼠标滚轮，可先移动到元素上\r\n        :param delta_y: 滚轮变化值y\r\n        :param delta_x: 滚轮变化值x\r\n        :param on_ele: ChromiumElement元素\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def up(self, pixel: int) -> Actions:\r\n        \"\"\"鼠标向上移动若干像素\r\n        :param pixel: 鼠标移动的像素值\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def down(self, pixel: int) -> Actions:\r\n        \"\"\"鼠标向下移动若干像素\r\n        :param pixel: 鼠标移动的像素值\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def left(self, pixel: int) -> Actions:\r\n        \"\"\"鼠标向左移动若干像素\r\n        :param pixel: 鼠标移动的像素值\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def right(self, pixel: int) -> Actions:\r\n        \"\"\"鼠标向右移动若干像素\r\n        :param pixel: 鼠标移动的像素值\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def key_down(self, key: Union[KEYS, str]) -> Actions:\r\n        \"\"\"按下键盘上的按键，\r\n        :param key: 使用Keys获取的按键，或 'DEL' 形式按键名称\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def key_up(self, key: Union[KEYS, str]) -> Actions:\r\n        \"\"\"提起键盘上的按键\r\n        :param key: 按键，特殊字符见Keys\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def type(self,\r\n             keys: Union[KEYS, str, list, tuple],\r\n             interval: float = 0) -> Actions:\r\n        \"\"\"用模拟键盘按键方式输入文本，可输入字符串，也可输入组合键\r\n        :param keys: 要按下的按键，特殊字符和多个文本可用list或tuple传入\r\n        :param interval: 每个字符之间间隔时间\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def input(self, text: Any) -> Actions:\r\n        \"\"\"输入文本，也可输入组合键，组合键用tuple形式输入\r\n        :param text: 文本值或按键组合\r\n        :return: 动作链对象本身\r\n        \"\"\"\r\n        ...\r\n\r\n    def drag_in(self, ele_or_loc: Union[str, ChromiumElement], files: Union[str, list, tuple] = None,\r\n                text: str = None, title: str = None, baseURL: str = None) -> Actions:\r\n        \"\"\"触发从浏览器外拖入文件、文本等事件\r\n        :param ele_or_loc: 接收拖动动作的元素\r\n        :param files: 要拖入文件路径，可多个，不为None时下面参数无效\r\n        :param text: 要拖入的文本，files参数为None时才生效\r\n        :param title: 如果text是超链接，可在此设置title，与baseURL互斥\r\n        :param baseURL: 如果text是html，可在此设置baseUrl，与title互斥\r\n        :return:\r\n        \"\"\"\r\n        ...\r\n\r\n    def wait(self, second: float, scope: float = None) -> Actions:\r\n        \"\"\"等待若干秒，如传入两个参数，等待时间为这两个数间的一个随机数\r\n        :param second: 秒数\r\n        :param scope: 随机数范围\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\ndef location_to_client(page: ChromiumBase, lx: int, ly: int) -> tuple:\r\n    \"\"\"绝对坐标转换为视口坐标\r\n    :param page: 页面对象\r\n    :param lx: 绝对坐标x\r\n    :param ly: 绝对坐标y\r\n    :return: 视口坐标元组\r\n    \"\"\"\r\n    ...\r\n"
  },
  {
    "path": "DrissionPage/_units/clicker.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom time import perf_counter, sleep\r\n\r\nfrom .waiter import wait_mission\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._functions.web import offset_scroll\r\nfrom .._units.downloader import TabDownloadSettings\r\nfrom ..errors import CanNotClickError, CDPError, NoRectError, AlertExistsError\r\n\r\n\r\nclass Clicker(object):\r\n    def __init__(self, ele):\r\n        self._ele = ele\r\n\r\n    def __call__(self, by_js=False, timeout=1.5, wait_stop=True):\r\n        return self.left(by_js, timeout, wait_stop)\r\n\r\n    def left(self, by_js=False, timeout=1.5, wait_stop=True):\r\n        if self._ele.tag == 'option':\r\n            if not self._ele.states.is_selected:\r\n                self._ele.parent('t:select').select.by_option(self._ele)\r\n            else:\r\n                select = self._ele.parent('t:select')\r\n                if select.select.is_multi:\r\n                    self._ele.parent('t:select').select.cancel_by_option(self._ele)\r\n            return self._ele\r\n\r\n        if not by_js:  # 模拟点击\r\n            can_click = False\r\n            if timeout is None:\r\n                timeout = self._ele.timeout\r\n            rect = None\r\n            if timeout == 0:\r\n                try:\r\n                    self._ele.scroll.to_see()\r\n                    if self._ele.states.is_enabled and self._ele.states.is_displayed:\r\n                        rect = self._ele.rect.viewport_corners\r\n                        can_click = True\r\n                except NoRectError:\r\n                    if by_js is False:\r\n                        raise\r\n\r\n            else:\r\n                rect = self._ele.states.has_rect\r\n                end_time = perf_counter() + timeout\r\n                while not rect and perf_counter() < end_time:\r\n                    rect = self._ele.states.has_rect\r\n                    sleep(.001)\r\n\r\n                if wait_stop and rect:\r\n                    self._ele.wait.stop_moving(timeout=end_time - perf_counter())\r\n                if rect:\r\n                    self._ele.scroll.to_see()\r\n                    rect = self._ele.rect.corners\r\n                    while perf_counter() < end_time:\r\n                        if self._ele.states.is_enabled and self._ele.states.is_displayed:\r\n                            can_click = True\r\n                            break\r\n                        sleep(.001)\r\n\r\n                elif by_js is False:\r\n                    raise NoRectError\r\n\r\n            if can_click and not self._ele.states.is_in_viewport:\r\n                by_js = True\r\n\r\n            elif can_click and (by_js is False or not self._ele.states.is_covered):\r\n                x = rect[1][0] - (rect[1][0] - rect[0][0]) / 2\r\n                y = rect[0][0] + 3\r\n                try:\r\n                    r = self._ele.owner._run_cdp('DOM.getNodeForLocation', x=int(x), y=int(y),\r\n                                                 includeUserAgentShadowDOM=True, ignorePointerEventsNone=True)\r\n                    if r['backendNodeId'] != self._ele._backend_id:\r\n                        vx, vy = self._ele.rect.viewport_midpoint\r\n                    else:\r\n                        vx, vy = self._ele.rect.viewport_click_point\r\n\r\n                except CDPError:\r\n                    vx, vy = self._ele.rect.viewport_midpoint\r\n\r\n                self._click(vx, vy)\r\n                return self._ele\r\n\r\n        if by_js is not False:\r\n            self._ele._run_js('this.click();')\r\n            return self._ele\r\n        if _S.raise_when_click_failed:\r\n            raise CanNotClickError\r\n        return False\r\n\r\n    def right(self):\r\n        self._ele.owner.scroll.to_see(self._ele)\r\n        return self._click(*self._ele.rect.viewport_click_point, button='right')\r\n\r\n    def middle(self, get_tab=True):\r\n        self._ele.owner.scroll.to_see(self._ele)\r\n        curr_tid = self._ele.tab.browser._newest_tab_id\r\n        self._click(*self._ele.rect.viewport_click_point, button='middle')\r\n        if get_tab:\r\n            tid = self._ele.tab.browser.wait.new_tab(curr_tab=curr_tid)\r\n            if not tid:\r\n                raise RuntimeError(_S._lang.join(_S._lang.NO_NEW_TAB))\r\n            return self._ele.tab.browser._get_tab(tid, mix=self._ele.tab._type == 'MixTab')\r\n\r\n    def at(self, offset_x=None, offset_y=None, button='left', count=1):\r\n        self._ele.owner.scroll.to_see(self._ele)\r\n        if offset_x is None and offset_y is None:\r\n            w, h = self._ele.rect.size\r\n            offset_x = w // 2\r\n            offset_y = h // 2\r\n        return self._click(*offset_scroll(self._ele, offset_x, offset_y), button=button, count=count)\r\n\r\n    def multi(self, times=2):\r\n        return self.at(count=times)\r\n\r\n    def to_download(self, save_path=None, rename=None, suffix=None, new_tab=None, by_js=False, timeout=None):\r\n        if not self._ele.tab._browser._dl_mgr._running:\r\n            self._ele.tab._browser.set.download_path('.')\r\n\r\n        when_file_exists = None\r\n        tmp_path = None\r\n        if self._ele.tab._type.endswith('Page'):\r\n            obj = browser = self._ele.owner._browser\r\n            tid = 'browser'\r\n\r\n        elif new_tab:\r\n            obj = browser = self._ele.owner._browser\r\n            tid = 'browser'\r\n            t_settings = TabDownloadSettings(self._ele.owner.tab_id)\r\n            b_settings = TabDownloadSettings('browser')\r\n\r\n            when_file_exists = b_settings.when_file_exists\r\n            b_settings.when_file_exists = t_settings.when_file_exists\r\n            b_settings.rename = t_settings.rename\r\n            b_settings.suffix = t_settings.suffix\r\n            t_settings.rename = None\r\n            t_settings.suffix = None\r\n            if not save_path and b_settings.path != t_settings.path:\r\n                tmp_path = b_settings.path\r\n                b_settings.path = t_settings.path\r\n\r\n        else:\r\n            obj = self._ele.owner._tab\r\n            browser = obj.browser\r\n            browser._dl_mgr._waiting_tab.add(self._ele.owner.tab_id)\r\n            tid = obj.tab_id\r\n\r\n        if save_path:\r\n            tmp_path = obj.download_path\r\n            TabDownloadSettings(tid).path = str(Path(save_path).absolute())\r\n        if rename or suffix:\r\n            obj.set.download_file_name(rename, suffix)\r\n        if timeout is None:\r\n            timeout = obj.timeout\r\n\r\n        browser._dl_mgr.set_flag(tid, True)\r\n        self.left(by_js=by_js)\r\n        m = wait_mission(browser, tid, timeout)\r\n\r\n        if tmp_path:\r\n            TabDownloadSettings(tid).path = tmp_path\r\n        if when_file_exists:\r\n            browser.set.when_download_file_exists(when_file_exists)\r\n        if m and new_tab:\r\n            self._ele.owner.browser._dl_mgr._tab_missions.setdefault(self._ele.owner.tab_id, set()).add(m)\r\n            m.from_tab = self._ele.owner.tab_id\r\n        browser._dl_mgr._waiting_tab.discard(self._ele.owner.tab_id)\r\n        return m\r\n\r\n    def to_upload(self, file_paths, by_js=False):\r\n        self._ele.owner.set.upload_files(file_paths)\r\n        self.left(by_js=by_js)\r\n        self._ele.owner.wait.upload_paths_inputted()\r\n\r\n    def for_new_tab(self, by_js=False, timeout=3):\r\n        curr_tid = self._ele.tab.browser._newest_tab_id\r\n        self.left(by_js=by_js)\r\n        tid = self._ele.tab.browser.wait.new_tab(timeout=timeout, curr_tab=curr_tid)\r\n        if not tid:\r\n            raise RuntimeError(_S._lang.join(_S._lang.NO_NEW_TAB))\r\n        return self._ele.tab.browser._get_tab(tid, mix=self._ele.tab._type == 'MixTab')\r\n\r\n    def for_url_change(self, text=None, exclude=False, by_js=False, timeout=None):\r\n        if text is None:\r\n            exclude = True\r\n            text = self._ele.tab.url\r\n        self.left(by_js=by_js)\r\n        return True if self._ele.tab.wait.url_change(text=text, exclude=exclude, timeout=timeout) else False\r\n\r\n    def for_title_change(self, text=None, exclude=False, by_js=False, timeout=None):\r\n        if text is None:\r\n            exclude = True\r\n            text = self._ele.tab.title\r\n        self.left(by_js=by_js)\r\n        return True if self._ele.tab.wait.title_change(text=text, exclude=exclude, timeout=timeout) else False\r\n\r\n    def _click(self, view_x, view_y, button='left', count=1):\r\n        self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=view_x,\r\n                                 y=view_y, button=button, clickCount=count, _ignore=AlertExistsError)\r\n        self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=view_x,\r\n                                 y=view_y, button=button, _ignore=AlertExistsError)\r\n        return self._ele\r\n"
  },
  {
    "path": "DrissionPage/_units/clicker.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Union\r\n\r\nfrom .downloader import DownloadMission\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._pages.chromium_tab import ChromiumTab\r\nfrom .._pages.mix_tab import MixTab\r\n\r\n\r\nclass Clicker(object):\r\n    _ele: ChromiumElement = ...\r\n\r\n    def __init__(self, ele: ChromiumElement):\r\n        \"\"\"\r\n        :param ele: ChromiumElement\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self, by_js: Union[bool, str, None] = False,\r\n                 timeout: float = 1.5, wait_stop: bool = True) -> Union[ChromiumElement, False]:\r\n        \"\"\"点击元素\r\n        如果遇到遮挡，可选择是否用js点击\r\n        :param by_js: 是否用js点击，为None时先用模拟点击，遇到遮挡改用js，为True时直接用js点击，为False时只用模拟点击\r\n        :param timeout: 模拟点击的超时时间（秒），等待元素可见、可用、进入视口\r\n        :param wait_stop: 是否等待元素运动结束再执行点击\r\n        :return: 是否点击成功\r\n        \"\"\"\r\n        ...\r\n\r\n    def left(self, by_js: Union[bool, str, None] = False,\r\n             timeout: float = 1.5, wait_stop: bool = True) -> Union[ChromiumElement, False]:\r\n        \"\"\"点击元素，可选择是否用js点击\r\n        :param by_js: 是否用js点击，为None时先用模拟点击，遇到遮挡改用js，为True时直接用js点击，为False时只用模拟点击\r\n        :param timeout: 模拟点击的超时时间（秒），等待元素可见、可用、进入视口\r\n        :param wait_stop: 是否等待元素运动结束再执行点击\r\n        :return: 是否点击成功\r\n        \"\"\"\r\n        ...\r\n\r\n    def right(self) -> ChromiumElement:\r\n        \"\"\"右键单击\"\"\"\r\n        ...\r\n\r\n    def middle(self, get_tab: bool = True) -> Union[ChromiumTab, MixTab, None]:\r\n        \"\"\"中键单击，默认返回新出现的tab对象\r\n        :param get_tab: 是否返回新tab对象，为False则返回None\r\n        :return: Tab对象或None\r\n        \"\"\"\r\n        ...\r\n\r\n    def at(self,\r\n           offset_x: float = None,\r\n           offset_y: float = None,\r\n           button: str = 'left',\r\n           count: int = 1) -> ChromiumElement:\r\n        \"\"\"带偏移量点击本元素，相对于左上角坐标。不传入x或y值时点击元素中间点\r\n        :param offset_x: 相对元素左上角坐标的x轴偏移量\r\n        :param offset_y: 相对元素左上角坐标的y轴偏移量\r\n        :param button: 点击哪个键，可选 left, middle, right, back, forward\r\n        :param count: 点击次数\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def multi(self, times: int = 2) -> ChromiumElement:\r\n        \"\"\"多次点击\r\n        :param times: 默认双击\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def to_download(self,\r\n                    save_path: Union[str, Path] = None,\r\n                    rename: str = None,\r\n                    suffix: str = None,\r\n                    new_tab: bool = None,\r\n                    by_js: bool = False,\r\n                    timeout: float = None) -> DownloadMission:\r\n        \"\"\"点击触发下载\r\n        :param save_path: 保存路径，为None保存在原来设置的，如未设置保存到当前路径\r\n        :param rename: 重命名文件名\r\n        :param suffix: 指定文件后缀\r\n        :param new_tab: 下载任务是否从新标签页触发，为None会自动获取，如获取不到，设为True\r\n        :param by_js: 是否用js方式点击，逻辑与click()一致\r\n        :param timeout: 等待下载触发的超时时间，为None则使用页面对象设置\r\n        :return: DownloadMission对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def to_upload(self, file_paths: Union[str, Path, list, tuple], by_js: bool = False) -> None:\r\n        \"\"\"触发上传文件选择框并自动填入指定路径\r\n        :param file_paths: 文件路径，如果上传框支持多文件，可传入列表或字符串，字符串时多个文件用回车分隔\r\n        :param by_js: 是否用js方式点击，逻辑与click()一致\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def for_new_tab(self, by_js: bool = False, timeout: float = 3) -> Union[ChromiumTab, MixTab]:\r\n        \"\"\"点击后等待新tab出现并返回其对象\r\n        :param by_js: 是否使用js点击，逻辑与click()一致\r\n        :param timeout: 等待超时时间\r\n        :return: 新标签页对象，如果没有等到新标签页出现则抛出异常\r\n        \"\"\"\r\n        ...\r\n\r\n    def for_url_change(self, text: str = None, exclude: bool = False,\r\n                       by_js: bool = False, timeout: float = None) -> bool:\r\n        \"\"\"点击并等待tab的url变成包含或不包含指定文本\r\n        :param text: 用于识别的文本，为None等待当前url变化\r\n        :param exclude: 是否排除，为True时当url不包含text指定文本时返回True，text为None时自动设为True\r\n        :param by_js: 是否用js点击\r\n        :param timeout: 超时时间（秒），为None使用页面设置\r\n        :return: 是否等待成功\r\n        \"\"\"\r\n        ...\r\n\r\n    def for_title_change(self, text: str = None, exclude: bool = False,\r\n                         by_js: bool = False, timeout: float = None) -> bool:\r\n        \"\"\"点击并等待tab的title变成包含或不包含指定文本\r\n        :param text: 用于识别的文本，为None等待当前title变化\r\n        :param exclude: 是否排除，为True时当title不包含text指定文本时返回True，text为None时自动设为True\r\n        :param by_js: 是否用js点击\r\n        :param timeout: 超时时间（秒），为None使用页面设置\r\n        :return: 是否等待成功\r\n        \"\"\"\r\n        ...\r\n\r\n    def _click(self,\r\n               view_x: float,\r\n               view_y: float,\r\n               button: str = 'left',\r\n               count: int = 1) -> ChromiumElement:\r\n        \"\"\"实施点击\r\n        :param view_x: 视口x坐标\r\n        :param view_y: 视口y坐标\r\n        :param button: 'left' 'right' 'middle'  'back' 'forward'\r\n        :param count: 点击次数\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/console.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom queue import Queue\nfrom time import perf_counter, sleep\n\nfrom .._functions.settings import Settings as _S\n\n\nclass Console(object):\n    def __init__(self, owner):\n        self._owner = owner\n        self._caught = None\n        self._not_enabled = True\n        self.listening = False\n\n    @property\n    def messages(self):\n        if self._caught is None:\n            return []\n        lst = []\n        while not self._caught.empty():\n            lst.append(self._caught.get_nowait())\n        return lst\n\n    def start(self):\n        self._caught = Queue(maxsize=0)\n        self._owner._driver.set_callback(\"Console.messageAdded\", self._console)\n        if self._not_enabled:\n            self._owner._run_cdp(\"Console.enable\")\n            self._not_enabled = False\n        self.listening = True\n\n    def stop(self):\n        if self.listening:\n            self._owner._driver.set_callback('Console.messageAdded', None)\n            self.listening = False\n\n    def clear(self):\n        self._caught = Queue(maxsize=0)\n\n    def wait(self, timeout=None):\n        if not self.listening:\n            raise RuntimeError(_S._lang.join(_S._lang.NOT_LISTENING))\n        if timeout is None:\n            while self._owner._driver.is_running and self.listening and not self._caught.qsize():\n                sleep(.03)\n            return self._caught.get_nowait() if self._caught.qsize() else None\n\n        else:\n            end = perf_counter() + timeout\n            while self._owner._driver.is_running and self.listening and perf_counter() < end:\n                if self._caught.qsize():\n                    return self._caught.get_nowait()\n                sleep(0.05)\n            return False\n\n    def steps(self, timeout=None):\n        if timeout is None:\n            while self._owner._driver.is_running and self.listening:\n                if self._caught.qsize():\n                    yield self._caught.get_nowait()\n                sleep(0.05)\n\n        else:\n            end = perf_counter() + timeout\n            while self._owner._driver.is_running and self.listening and perf_counter() < end:\n                if self._caught.qsize():\n                    yield self._caught.get_nowait()\n                    end = perf_counter() + timeout\n                sleep(0.05)\n            return False\n\n    def _console(self, **kwargs):\n        self._caught.put(ConsoleData(kwargs['message']))\n\n\nclass ConsoleData(object):\n    __slots__ = ('_data', 'source', 'level', 'text', 'url', 'line', 'column')\n\n    def __init__(self, data):\n        self._data = data\n\n    def __getattr__(self, item):\n        return self._data.get(item, None)\n\n    def __repr__(self):\n        return (f'<ConsoleData source={self.source} level={self.level} text={self.text} url={self.url} '\n                f'line={self.line} column={self.column} >')\n\n    @property\n    def body(self):\n        from json import loads\n        try:\n            return loads(self.text)\n        except:\n            return self._raw_body\n"
  },
  {
    "path": "DrissionPage/_units/console.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom queue import Queue\nfrom typing import Optional, Iterable, List, Union, Any\n\nfrom .._pages.chromium_base import ChromiumBase\n\n\nclass Console(object):\n    listening: bool = ...\n    _owner: ChromiumBase = ...\n    _caught: Optional[Queue] = ...\n    _not_enabled: bool = ...\n\n    def __init__(self, owner: ChromiumBase) -> None:\n        \"\"\"\n        :param owner: 页面对象\n        \"\"\"\n        ...\n\n    @property\n    def messages(self) -> List[ConsoleData]:\n        \"\"\"以list方式返回获取到的信息，返回后会清空列表\"\"\"\n        ...\n\n    def start(self) -> None:\n        \"\"\"开启console监听\"\"\"\n        ...\n\n    def stop(self) -> None:\n        \"\"\"停止监听，清空已监听到的列表\"\"\"\n        ...\n\n    def clear(self) -> None:\n        \"\"\"清空已获取但未返回的信息\"\"\"\n        ...\n\n    def wait(self, timeout: float = None) -> Union[ConsoleData, False]:\n        \"\"\"等待一条信息\n        :param timeout: 超时时间（秒）\n        :return: ConsoleData对象\n        \"\"\"\n        ...\n\n    def steps(self, timeout: Optional[float] = None) -> Iterable[ConsoleData]:\n        \"\"\"每监听到一个信息就返回，用于for循环\n        :param timeout: 等待一个信息的超时时间，为None无限等待\n        :return: None\n        \"\"\"\n        ...\n\n    def _console(self, **kwargs) -> None: ...\n\n\nclass ConsoleData(object):\n    __slots__ = ('_data', 'source', 'level', 'text', 'url', 'line', 'column')\n\n    def __init__(self, data: dict) -> None: ...\n\n    def __getattr__(self, item: str) -> str: ...\n\n    @property\n    def body(self) -> Any: ...\n"
  },
  {
    "path": "DrissionPage/_units/cookies_setter.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom .._functions.cookies import set_tab_cookies, set_session_cookies, set_browser_cookies\r\nfrom .._functions.settings import Settings as _S\r\n\r\n\r\nclass BrowserCookiesSetter(object):\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n\r\n    def __call__(self, cookies):\r\n        set_browser_cookies(self._owner, cookies)\r\n\r\n    def clear(self):\r\n        self._owner._run_cdp('Storage.clearCookies')\r\n\r\n\r\nclass CookiesSetter(BrowserCookiesSetter):\r\n    def __call__(self, cookies):\r\n        set_tab_cookies(self._owner, cookies)\r\n\r\n    def remove(self, name, url=None, domain=None, path=None):\r\n        d = {'name': name}\r\n        if url is not None:\r\n            d['url'] = url\r\n        if domain is not None:\r\n            d['domain'] = domain\r\n        if not url and not domain:\r\n            d['url'] = self._owner.url\r\n            if not d['url'].startswith('http'):\r\n                raise ValueError(_S._lang.join(_S._lang.NEED_DOMAIN))\r\n        if path is not None:\r\n            d['path'] = path\r\n        self._owner._run_cdp('Network.deleteCookies', **d)\r\n\r\n    def clear(self):\r\n        self._owner._run_cdp('Network.clearBrowserCookies')\r\n\r\n\r\nclass SessionCookiesSetter(object):\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n\r\n    def __call__(self, cookies):\r\n        set_session_cookies(self._owner.session, cookies)\r\n\r\n    def remove(self, name):\r\n        self._owner.session.cookies.set(name, None)\r\n\r\n    def clear(self):\r\n        self._owner.session.cookies.clear()\r\n\r\n\r\nclass WebPageCookiesSetter(CookiesSetter):\r\n    def __call__(self, cookies):\r\n        if self._owner.mode == 'd' and self._owner._has_driver:\r\n            super().__call__(cookies)\r\n        elif self._owner.mode == 's' and self._owner._has_session:\r\n            set_session_cookies(self._owner.session, cookies)\r\n\r\n    def remove(self, name, url=None, domain=None, path=None):\r\n        if self._owner.mode == 'd' and self._owner._has_driver:\r\n            super().remove(name, url, domain, path)\r\n        elif self._owner.mode == 's' and self._owner._has_session:\r\n            if url or domain or path:\r\n                raise ValueError(_S._lang.join(_S._lang.D_MODE_ONLY))\r\n            self._owner.session.cookies.set(name, None)\r\n\r\n    def clear(self):\r\n        if self._owner.mode == 'd' and self._owner._has_driver:\r\n            super().clear()\r\n        elif self._owner.mode == 's' and self._owner._has_session:\r\n            self._owner.session.cookies.clear()\r\n\r\n\r\nclass MixTabCookiesSetter(CookiesSetter):\r\n    def __call__(self, cookies):\r\n        if self._owner._d_mode and self._owner._driver.is_running:\r\n            super().__call__(cookies)\r\n        elif not self._owner._d_mode and self._owner._session:\r\n            set_session_cookies(self._owner.session, cookies)\r\n\r\n    def remove(self, name, url=None, domain=None, path=None):\r\n        if self._owner._d_mode and self._owner._driver.is_running:\r\n            super().remove(name, url, domain, path)\r\n        elif not self._owner._d_mode and self._owner._session:\r\n            if url or domain or path:\r\n                raise ValueError(_S._lang.join(_S._lang.D_MODE_ONLY))\r\n            self._owner.session.cookies.set(name, None)\r\n\r\n    def clear(self):\r\n        if self._owner._d_mode and self._owner._driver.is_running:\r\n            super().clear()\r\n        elif not self._owner._d_mode and self._owner._session:\r\n            self._owner.session.cookies.clear()\r\n"
  },
  {
    "path": "DrissionPage/_units/cookies_setter.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom http.cookiejar import Cookie, CookieJar\r\nfrom typing import Union\r\n\r\nfrom .._base.chromium import Chromium\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.mix_tab import MixTab\r\nfrom .._pages.session_page import SessionPage\r\nfrom .._pages.web_page import WebPage\r\n\r\n\r\nclass BrowserCookiesSetter(object):\r\n    _owner: Chromium = ...\r\n\r\n    def __init__(self, owner: Chromium):\r\n        \"\"\"\r\n        :param owner: Chromium对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self, cookies: Union[CookieJar, Cookie, list, tuple, str, dict]) -> None:\r\n        \"\"\"设置一个或多个cookie\r\n        :param cookies: cookies信息\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def clear(self) -> None:\r\n        \"\"\"清除cookies\"\"\"\r\n        ...\r\n\r\n\r\nclass CookiesSetter(BrowserCookiesSetter):\r\n    _owner: ChromiumBase = ...\r\n\r\n    def __init__(self, owner: ChromiumBase):\r\n        \"\"\"\r\n        :param owner: 页面对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self, cookies: Union[CookieJar, Cookie, list, tuple, str, dict]) -> None:\r\n        \"\"\"设置一个或多个cookie\r\n        :param cookies: cookies信息\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove(self,\r\n               name: str,\r\n               url: str = None,\r\n               domain: str = None,\r\n               path: str = None) -> None:\r\n        \"\"\"删除一个cookie\r\n        :param name: cookie的name字段\r\n        :param url: cookie的url字段，可选\r\n        :param domain: cookie的domain字段，可选\r\n        :param path: cookie的path字段，可选\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def clear(self) -> None:\r\n        \"\"\"清除cookies\"\"\"\r\n        ...\r\n\r\n\r\nclass SessionCookiesSetter(object):\r\n    _owner: SessionPage = ...\r\n\r\n    def __init__(self, owner: SessionPage):\r\n        \"\"\"\r\n        :param owner: SessionPage对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self, cookies: Union[CookieJar, Cookie, list, tuple, str, dict]) -> None:\r\n        \"\"\"设置一个或多个cookie\r\n        :param cookies: cookies信息\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove(self, name: str) -> None:\r\n        \"\"\"删除一个cookie\r\n        :param name: cookie的name字段\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def clear(self) -> None:\r\n        \"\"\"清除cookies\"\"\"\r\n        ...\r\n\r\n\r\nclass WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter):\r\n    _owner: WebPage = ...\r\n\r\n    def __init__(self, owner: WebPage):\r\n        \"\"\"\r\n        :param owner: WebPage对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self, cookies: Union[CookieJar, Cookie, list, tuple, str, dict]) -> None:\r\n        \"\"\"设置一个或多个cookie\r\n        :param cookies: cookies信息\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove(self,\r\n               name: str,\r\n               url: str = None,\r\n               domain: str = None,\r\n               path: str = None) -> None:\r\n        \"\"\"删除一个cookie\r\n        :param name: cookie的name字段\r\n        :param url: cookie的url字段，可选，d模式时才有效\r\n        :param domain: cookie的domain字段，可选，d模式时才有效\r\n        :param path: cookie的path字段，可选，d模式时才有效\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def clear(self) -> None:\r\n        \"\"\"清除cookies\"\"\"\r\n        ...\r\n\r\n\r\nclass MixTabCookiesSetter(CookiesSetter, SessionCookiesSetter):\r\n    _owner: MixTab = ...\r\n\r\n    def __init__(self, owner: MixTab):\r\n        \"\"\"\r\n        :param owner: MixTab对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self, cookies: Union[CookieJar, Cookie, list, tuple, str, dict]) -> None:\r\n        \"\"\"设置一个或多个cookie\r\n        :param cookies: cookies信息\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def remove(self,\r\n               name: str,\r\n               url: str = None,\r\n               domain: str = None,\r\n               path: str = None) -> None:\r\n        \"\"\"删除一个cookie\r\n        :param name: cookie的name字段\r\n        :param url: cookie的url字段，可选，d模式时才有效\r\n        :param domain: cookie的domain字段，可选，d模式时才有效\r\n        :param path: cookie的path字段，可选，d模式时才有效\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def clear(self) -> None:\r\n        \"\"\"清除cookies\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/downloader.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom os.path import sep\r\nfrom pathlib import Path\r\nfrom shutil import move\r\nfrom time import sleep, perf_counter\r\n\r\nfrom DataRecorder.tools import get_usable_path\r\n\r\nfrom .._functions.settings import Settings as _S\r\n\r\n\r\nclass DownloadManager(object):\r\n\r\n    def __init__(self, browser):\r\n        self._browser = browser\r\n\r\n        t = TabDownloadSettings('browser')\r\n        t.path = self._browser.download_path\r\n        t.rename = None\r\n        t.suffix = None\r\n        t.when_file_exists = 'rename'\r\n\r\n        self._missions = {}  # {guid: DownloadMission}\r\n        self._tab_missions = {}  # {tab_id: [DownloadMission, ...]}\r\n        self._flags = {}  # {tab_id: [bool, DownloadMission]}\r\n        self._waiting_tab = set()  # click.to_download()专用\r\n        self._tmp_path = '.'\r\n        self._page_id = None\r\n\r\n        self._running = False\r\n\r\n    @property\r\n    def missions(self):\r\n        return self._missions\r\n\r\n    def set_path(self, tab, path):\r\n        tid = tab if isinstance(tab, str) else tab.tab_id\r\n        TabDownloadSettings(tid).path = path\r\n        if not self._running or tid == 'browser':\r\n            self._browser._driver.set_callback('Browser.downloadProgress', self._onDownloadProgress)\r\n            self._browser._driver.set_callback('Browser.downloadWillBegin', self._onDownloadWillBegin)\r\n            r = self._browser._run_cdp('Browser.setDownloadBehavior', downloadPath=self._browser._download_path,\r\n                                       behavior='allowAndName', eventsEnabled=True)\r\n            self._tmp_path = self._browser._download_path\r\n            if 'error' in r:\r\n                print(_S._lang.NOT_SUPPORT_DOWNLOAD)\r\n        self._running = True\r\n\r\n    @staticmethod\r\n    def set_rename(tab_id, rename=None, suffix=None):\r\n        ts = TabDownloadSettings(tab_id)\r\n        ts.rename = rename\r\n        ts.suffix = suffix\r\n\r\n    @staticmethod\r\n    def set_file_exists(tab_id, mode):\r\n        TabDownloadSettings(tab_id).when_file_exists = mode\r\n\r\n    def set_flag(self, tab_id, flag):\r\n        self._flags[tab_id] = flag\r\n\r\n    def get_flag(self, tab_id):\r\n        return self._flags.get(tab_id, None)\r\n\r\n    def get_tab_missions(self, tab_id):\r\n        return self._tab_missions.get(tab_id, set())\r\n\r\n    def set_done(self, mission, state, final_path=None):\r\n        if mission.state not in ('canceled', 'skipped'):\r\n            mission.state = state\r\n        mission.final_path = final_path\r\n        if mission.tab_id in self._tab_missions and mission in self._tab_missions[mission.tab_id]:\r\n            self._tab_missions[mission.tab_id].discard(mission)\r\n        if (mission.from_tab and mission.from_tab in self._tab_missions\r\n                and mission in self._tab_missions[mission.from_tab]):\r\n            self._tab_missions[mission.from_tab].discard(mission)\r\n        self._missions.pop(mission.id, None)\r\n        mission._is_done = True\r\n\r\n    def cancel(self, mission):\r\n        mission.state = 'canceled'\r\n        try:\r\n            self._browser._run_cdp('Browser.cancelDownload', guid=mission.id)\r\n        except:\r\n            pass\r\n        if mission.final_path:\r\n            Path(mission.final_path).unlink(True)\r\n\r\n    def skip(self, mission):\r\n        mission.state = 'skipped'\r\n        try:\r\n            self._browser._run_cdp('Browser.cancelDownload', guid=mission.id)\r\n        except:\r\n            pass\r\n\r\n    def clear_tab_info(self, tab_id):\r\n        self._tab_missions.pop(tab_id, None)\r\n        self._flags.pop(tab_id, None)\r\n        TabDownloadSettings.TABS.pop(tab_id, None)\r\n        self._waiting_tab.discard(tab_id)\r\n\r\n    def _onDownloadWillBegin(self, **kwargs):\r\n        guid = kwargs['guid']\r\n        tab_id = self._browser._frames.get(kwargs['frameId'], 'browser')\r\n        tab = 'browser' if tab_id in ('browser', self._page_id) or self.get_flag('browser') is not None else tab_id\r\n        opener = self._browser._relation.get(tab_id, None)\r\n        from_tab = None\r\n        if opener and opener in self._waiting_tab:\r\n            tab = from_tab = opener\r\n\r\n        settings = TabDownloadSettings(tab)\r\n        if settings.rename:\r\n            if settings.suffix is not None:\r\n                name = f'{settings.rename}.{settings.suffix}' if settings.suffix else settings.rename\r\n\r\n            else:\r\n                tmp = kwargs['suggestedFilename'].rsplit('.', 1)\r\n                ext_name = tmp[-1] if len(tmp) > 1 else ''\r\n                tmp = settings.rename.rsplit('.', 1)\r\n                ext_rename = tmp[-1] if len(tmp) > 1 else ''\r\n                name = settings.rename if ext_rename == ext_name else f'{settings.rename}.{ext_name}'\r\n\r\n            settings.rename = None\r\n            settings.suffix = None\r\n\r\n        elif settings.suffix is not None:\r\n            name = kwargs[\"suggestedFilename\"].rsplit(\".\", 1)[0]\r\n            if settings.suffix:\r\n                name = f'{name}.{settings.suffix}'\r\n            settings.suffix = None\r\n\r\n        else:\r\n            name = kwargs['suggestedFilename']\r\n\r\n        skip = False\r\n        overwrite = None  # 存在且重命名\r\n        goal_path = Path(settings.path) / name\r\n        if goal_path.exists():\r\n            if settings.when_file_exists == 'skip':\r\n                skip = True\r\n            elif settings.when_file_exists == 'overwrite':\r\n                overwrite = True  # 存在且覆盖\r\n        else:  # 不存在\r\n            overwrite = False\r\n\r\n        m = DownloadMission(self, tab_id, guid, settings.path, name, kwargs['url'], self._tmp_path, overwrite)\r\n        if from_tab:\r\n            m.from_tab = from_tab\r\n            self._tab_missions.setdefault(from_tab, set()).add(m)\r\n        self._missions[guid] = m\r\n\r\n        if self.get_flag('browser') is False or self.get_flag(tab) is False:  # 取消该任务\r\n            self.cancel(m)\r\n        elif skip:\r\n            self.skip(m)\r\n        else:\r\n            self._tab_missions.setdefault(tab_id, set()).add(m)\r\n\r\n        if self.get_flag('browser') is not None:\r\n            self._flags['browser'] = m\r\n        elif self.get_flag(tab) is not None:\r\n            self._flags[tab] = m\r\n\r\n    def _onDownloadProgress(self, **kwargs):\r\n        if kwargs['guid'] in self._missions:\r\n            mission = self._missions[kwargs['guid']]\r\n            if kwargs['state'] == 'inProgress':\r\n                mission.received_bytes = kwargs['receivedBytes']\r\n                mission.total_bytes = kwargs['totalBytes']\r\n\r\n            elif kwargs['state'] == 'completed':\r\n                if mission.state == 'skipped':\r\n                    Path(f'{mission.tmp_path}{sep}{mission.id}').unlink(True)\r\n                    self.set_done(mission, 'skipped')\r\n                    return\r\n                mission.received_bytes = kwargs['receivedBytes']\r\n                mission.total_bytes = kwargs['totalBytes']\r\n                form_path = f'{mission.tmp_path}{sep}{mission.id}'\r\n                if mission._overwrite is None:\r\n                    to_path = str(get_usable_path(f'{mission.folder}{sep}{mission.name}'))\r\n                else:\r\n                    to_path = f'{mission.folder}{sep}{mission.name}'\r\n                Path(mission.folder).mkdir(parents=True, exist_ok=True)\r\n                not_moved = True\r\n                for _ in range(10):\r\n                    try:\r\n                        move(form_path, to_path)\r\n                        not_moved = False\r\n                        break\r\n                    except PermissionError:\r\n                        sleep(.5)\r\n                if not_moved:\r\n                    from shutil import copy\r\n                    copy(form_path, to_path)\r\n                self.set_done(mission, 'completed', final_path=to_path)\r\n\r\n            else:  # 'canceled'\r\n                self.set_done(mission, 'canceled')\r\n\r\n\r\nclass TabDownloadSettings(object):\r\n    TABS = {}\r\n\r\n    def __new__(cls, tab_id):\r\n        \"\"\"\r\n        :param tab_id: tab id\r\n        \"\"\"\r\n        if tab_id in cls.TABS:\r\n            return cls.TABS[tab_id]\r\n        return object.__new__(cls)\r\n\r\n    def __init__(self, tab_id):\r\n        if hasattr(self, '_created'):\r\n            return\r\n        self._created = True\r\n        self.tab_id = tab_id\r\n        self.rename = None\r\n        self.suffix = None\r\n        self.path = '' if tab_id == 'browser' else self.TABS['browser'].path\r\n        self.when_file_exists = 'rename' if tab_id == 'browser' else self.TABS['browser'].when_file_exists\r\n\r\n        TabDownloadSettings.TABS[tab_id] = self\r\n\r\n\r\nclass DownloadMission(object):\r\n    def __init__(self, mgr, tab_id, _id, folder, name, url, tmp_path, overwrite):\r\n        self._mgr = mgr\r\n        self.url = url\r\n        self.tab_id = tab_id\r\n        self.from_tab = None\r\n        self.id = _id\r\n        self.folder = folder\r\n        self.name = name\r\n        self.state = 'running'\r\n        self.total_bytes = None\r\n        self.received_bytes = 0\r\n        self.final_path = None\r\n        self.tmp_path = tmp_path\r\n        self._overwrite = overwrite\r\n        self._is_done = False\r\n\r\n    def __repr__(self):\r\n        return f'<DownloadMission {id(self)} {self.rate}>'\r\n\r\n    @property\r\n    def rate(self):\r\n        return round((self.received_bytes / self.total_bytes) * 100, 2) if self.total_bytes else None\r\n\r\n    @property\r\n    def is_done(self):\r\n        return self._is_done\r\n\r\n    def cancel(self):\r\n        self._mgr.cancel(self)\r\n\r\n    def wait(self, show=True, timeout=None, cancel_if_timeout=True):\r\n        if show:\r\n            print(f'url: {self.url}')\r\n            end_time = perf_counter()\r\n            while self.name is None and perf_counter() < end_time:\r\n                sleep(0.01)\r\n            print(f'{_S._lang.FILE_NAME}: {self.name or _S._lang.UNKNOWN}')\r\n            print(f'{_S._lang.FOLDER_PATH}: {self.folder}')\r\n\r\n        if timeout is None:\r\n            while not self.is_done:\r\n                if show:\r\n                    print(f'\\r{self.rate}% ', end='')\r\n                sleep(.2)\r\n\r\n        else:\r\n            end_time = perf_counter() + timeout\r\n            while perf_counter() < end_time:\r\n                if show:\r\n                    print(f'\\r{self.rate}% ', end='')\r\n                sleep(.2)\r\n\r\n            if not self.is_done and cancel_if_timeout:\r\n                self.cancel()\r\n\r\n        if show:\r\n            if self.state == 'completed':\r\n                print('\\r100% ', end='')\r\n                if self._overwrite is None:\r\n                    print(f'{_S._lang.COMPLETED_AND_RENAME} {self.final_path}')\r\n                elif self._overwrite is False:\r\n                    print(f'{_S._lang.DOWNLOAD_COMPLETED} {self.final_path}')\r\n                else:\r\n                    print(f'{_S._lang.OVERWROTE} {self.final_path}')\r\n            elif self.state == 'canceled':\r\n                print(_S._lang.DOWNLOAD_CANCELED)\r\n            elif self.state == 'skipped':\r\n                print(f'{_S._lang.SKIPPED} {self.folder}{sep}{self.name}')\r\n            print()\r\n\r\n        return self.final_path if self.final_path else False\r\n"
  },
  {
    "path": "DrissionPage/_units/downloader.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Dict, Optional, Union, Literal, Set\r\n\r\nfrom .._base.chromium import Chromium\r\nfrom .._pages.chromium_base import ChromiumBase\r\n\r\nFILE_EXISTS = Literal['skip', 'rename', 'overwrite', 's', 'r', 'o']\r\n\r\n\r\nclass DownloadManager(object):\r\n    _browser: Chromium = ...\r\n    _missions: Dict[str, DownloadMission] = ...\r\n    _tab_missions: Dict[str, Set[DownloadMission]] = ...\r\n    _flags: dict = ...\r\n    _waiting_tab: set = ...\r\n    _running: bool = ...\r\n    _tmp_path: str = ...\r\n    _page_id: Optional[str] = ...\r\n\r\n    def __init__(self, browser: Chromium):\r\n        \"\"\"\r\n        :param browser: Browser对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def missions(self) -> Dict[str, DownloadMission]:\r\n        \"\"\"返回所有未完成的下载任务\"\"\"\r\n        ...\r\n\r\n    def set_path(self, tab: Union[str, ChromiumBase], path: str) -> None:\r\n        \"\"\"设置某个tab的下载路径\r\n        :param tab: 页面对象\r\n        :param path: 下载路径（绝对路径str）\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    @staticmethod\r\n    def set_rename(tab_id: str,\r\n                   rename: str = None,\r\n                   suffix: str = None) -> None:\r\n        \"\"\"设置某个tab的重命名文件名\r\n        :param tab_id: tab id\r\n        :param rename: 文件名，可不含后缀，会自动使用远程文件后缀\r\n        :param suffix: 后缀名，显式设置后缀名，不使用远程文件后缀\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    @staticmethod\r\n    def set_file_exists(tab_id: str, mode: FILE_EXISTS) -> None:\r\n        \"\"\"设置某个tab下载文件重名时执行的策略\r\n        :param tab_id: tab id\r\n        :param mode: 下载路径\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_flag(self, tab_id: str, flag: Union[bool, DownloadMission, None]) -> None:\r\n        \"\"\"设置某个tab的重命名文件名\r\n        :param tab_id: tab id\r\n        :param flag: 等待标志\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_flag(self, tab_id: str) -> Union[bool, DownloadMission, None]:\r\n        \"\"\"获取tab下载等待标记\r\n        :param tab_id: tab id\r\n        :return: 任务对象或False\r\n        \"\"\"\r\n        ...\r\n\r\n    def get_tab_missions(self, tab_id: str) -> list:\r\n        \"\"\"获取某个tab正在下载的任务\r\n        :param tab_id:\r\n        :return: 下载任务组成的列表\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_done(self,\r\n                 mission: DownloadMission,\r\n                 state: str,\r\n                 final_path: str = None) -> None:\r\n        \"\"\"设置任务结束\r\n        :param mission: 任务对象\r\n        :param state: 任务状态\r\n        :param final_path: 最终路径\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def cancel(self, mission: DownloadMission) -> None:\r\n        \"\"\"取消任务\r\n        :param mission: 任务对象\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def skip(self, mission: DownloadMission) -> None:\r\n        \"\"\"跳过任务\r\n        :param mission: 任务对象\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def clear_tab_info(self, tab_id: str) -> None:\r\n        \"\"\"当tab关闭时清除有关信息\r\n        :param tab_id: 标签页id\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _onDownloadWillBegin(self, **kwargs) -> None:\r\n        \"\"\"用于获取弹出新标签页触发的下载任务\"\"\"\r\n        ...\r\n\r\n    def _onDownloadProgress(self, **kwargs) -> None:\r\n        \"\"\"下载状态变化时执行\"\"\"\r\n        ...\r\n\r\n\r\nclass TabDownloadSettings(object):\r\n    TABS: dict = ...\r\n    tab_id: str = ...\r\n    waiting_flag: Union[bool, dict, None] = ...\r\n    rename: Optional[str] = ...\r\n    suffix: Optional[str] = ...\r\n    path: Optional[str] = ...\r\n    when_file_exists: FILE_EXISTS = ...\r\n\r\n    def __init__(self, tab_id: str):\r\n        \"\"\"\r\n        :param tab_id: tab id\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass DownloadMission(object):\r\n    tab_id: str = ...\r\n    from_tab: Optional[str] = ...\r\n    _mgr: DownloadManager = ...\r\n    url: str = ...\r\n    id: str = ...\r\n    folder: str = ...\r\n    name: str = ...\r\n    state: str = ...\r\n    total_bytes: Optional[int] = ...\r\n    received_bytes: int = ...\r\n    final_path: Optional[str] = ...\r\n    tmp_path: str = ...\r\n    _overwrite: bool = ...\r\n    _is_done: bool = ...\r\n\r\n    def __init__(self,\r\n                 mgr: DownloadManager,\r\n                 tab_id: str,\r\n                 _id: str,\r\n                 folder: str,\r\n                 name: str,\r\n                 url: str,\r\n                 tmp_path: str,\r\n                 overwrite: bool):\r\n        \"\"\"\r\n        :param mgr: BrowserDownloadManager对象\r\n        :param tab_id: 标签页id\r\n        :param _id: 任务id\r\n        :param folder: 最终保存文件夹路径\r\n        :param name: 文件名\r\n        :param url: url\r\n        :param tmp_path: 下载临时路径\r\n        :param overwrite: 是否已存在同名文件，None表示重命名\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def rate(self) -> float:\r\n        \"\"\"以百分比形式返回下载进度\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_done(self) -> bool:\r\n        \"\"\"返回任务是否在运行中\"\"\"\r\n        ...\r\n\r\n    def cancel(self) -> None:\r\n        \"\"\"取消该任务，如任务已完成，删除已下载的文件\"\"\"\r\n        ...\r\n\r\n    def wait(self,\r\n             show: bool = True,\r\n             timeout=None,\r\n             cancel_if_timeout=True) -> Union[bool, str]:\r\n        \"\"\"等待任务结束\r\n        :param show: 是否显示下载信息\r\n        :param timeout: 超时时间（秒），为None则无限等待\r\n        :param cancel_if_timeout: 超时时是否取消任务\r\n        :return: 等待成功返回完整路径，否则返回False\r\n        \"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/listener.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom base64 import b64decode\r\nfrom json import JSONDecodeError, loads\r\nfrom queue import Queue\r\nfrom re import search\r\nfrom time import perf_counter, sleep\r\n\r\nfrom requests.structures import CaseInsensitiveDict\r\n\r\nfrom .._base.driver import Driver\r\nfrom .._functions.settings import Settings as _S\r\nfrom ..errors import WaitTimeoutError\r\n\r\n\r\nclass Listener(object):\r\n    \"\"\"监听器基类\"\"\"\r\n\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n        self._address = owner.browser._ws_address\r\n        self._target_id = owner._target_id\r\n        self._driver = None\r\n        self._running_requests = 0\r\n        self._running_targets = 0\r\n\r\n        self._caught = None\r\n        self._request_ids = None\r\n        self._extra_info_ids = None\r\n\r\n        self.listening = False\r\n        self.tab_id = None\r\n\r\n        self._targets = True\r\n        self._is_regex = False\r\n        self._method = {'GET', 'POST'}\r\n        self._res_type = True\r\n\r\n    @property\r\n    def targets(self):\r\n        \"\"\"返回监听目标\"\"\"\r\n        return self._targets\r\n\r\n    def set_targets(self, targets=True, is_regex=False, method=('GET', 'POST'), res_type=True):\r\n        if targets is not None:\r\n            if not isinstance(targets, (str, list, tuple, set)) and targets is not True:\r\n                raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'targets',\r\n                                               ALLOW_TYPE='str, list, tuple, set, True', CURR_TYPE=type(targets)))\r\n            if targets is True:\r\n                self._targets = True\r\n            else:\r\n                self._targets = {targets} if isinstance(targets, str) else set(targets)\r\n\r\n        if is_regex is not None:\r\n            self._is_regex = is_regex\r\n\r\n        if method is not None:\r\n            if isinstance(method, str):\r\n                self._method = {method.upper()}\r\n            elif isinstance(method, (list, tuple, set)):\r\n                self._method = set(i.upper() for i in method)\r\n            elif method is True:\r\n                self._method = True\r\n            else:\r\n                raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'method',\r\n                                               ALLOW_TYPE='str, list, tuple, set, True', CURR_TYPE=type(method)))\r\n\r\n        if res_type is not None:\r\n            if isinstance(res_type, str):\r\n                self._res_type = {res_type.upper()}\r\n            elif isinstance(res_type, (list, tuple, set)):\r\n                self._res_type = set(i.upper() for i in res_type)\r\n            elif res_type is True:\r\n                self._res_type = True\r\n            else:\r\n                raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'res_type',\r\n                                               ALLOW_TYPE='str, list, tuple, set, True', CURR_TYPE=type(res_type)))\r\n\r\n    def start(self, targets=None, is_regex=None, method=None, res_type=None):\r\n        if targets is not None:\r\n            if is_regex is None:\r\n                is_regex = False\r\n        if targets or is_regex is not None or method or res_type:\r\n            self.set_targets(targets, is_regex, method, res_type)\r\n        self.clear()\r\n\r\n        if self.listening:\r\n            return\r\n\r\n        self._driver = Driver(self._target_id, self._address)\r\n        self._driver.session_id = self._driver.run('Target.attachToTarget', targetId=self._target_id, flatten=True)['sessionId']\r\n        self._driver.run('Network.enable')\r\n\r\n        self._set_callback()\r\n        self.listening = True\r\n\r\n    def wait(self, count=1, timeout=None, fit_count=True, raise_err=None):\r\n        if not self.listening:\r\n            raise RuntimeError(_S._lang.join(_S._lang.NOT_LISTENING))\r\n        if not timeout:\r\n            while self._driver.is_running and self.listening and self._caught.qsize() < count:\r\n                sleep(.03)\r\n            fail = False\r\n\r\n        else:\r\n            end = perf_counter() + timeout\r\n            while self._driver.is_running and self.listening:\r\n                if perf_counter() > end:\r\n                    fail = True\r\n                    break\r\n                if self._caught.qsize() >= count:\r\n                    fail = False\r\n                    break\r\n                sleep(.03)\r\n\r\n        if fail:\r\n            if fit_count or not self._caught.qsize():\r\n                if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\r\n                    raise WaitTimeoutError(_S._lang.join(_S._lang.WAITING_FAILED_, _S._lang.DATA_PACKET, timeout))\r\n                else:\r\n                    return False\r\n            else:\r\n                return [self._caught.get_nowait() for _ in range(self._caught.qsize())]\r\n\r\n        if count == 1:\r\n            return self._caught.get_nowait()\r\n\r\n        return [self._caught.get_nowait() for _ in range(count)]\r\n\r\n    def steps(self, count=None, timeout=None, gap=1):\r\n        if not self.listening:\r\n            raise RuntimeError(_S._lang.join(_S._lang.NOT_LISTENING))\r\n        caught = 0\r\n        if timeout is None:\r\n            while self._driver.is_running and self.listening:\r\n                if self._caught.qsize() >= gap:\r\n                    yield self._caught.get_nowait() if gap == 1 else [self._caught.get_nowait() for _ in range(gap)]\r\n                    if count:\r\n                        caught += gap\r\n                        if caught >= count:\r\n                            return\r\n                sleep(.03)\r\n\r\n        else:\r\n            end = perf_counter() + timeout\r\n            while self._driver.is_running and self.listening and perf_counter() < end:\r\n                if self._caught.qsize() >= gap:\r\n                    yield self._caught.get_nowait() if gap == 1 else [self._caught.get_nowait() for _ in range(gap)]\r\n                    end = perf_counter() + timeout\r\n                    if count:\r\n                        caught += gap\r\n                        if caught >= count:\r\n                            return\r\n                sleep(.03)\r\n            return False\r\n\r\n    def stop(self):\r\n        if self.listening:\r\n            self.pause()\r\n            self.clear()\r\n        self._driver.stop()\r\n        self._driver = None\r\n\r\n    def pause(self, clear=True):\r\n        if self.listening:\r\n            self._driver.set_callback('Network.requestWillBeSent', None)\r\n            self._driver.set_callback('Network.responseReceived', None)\r\n            self._driver.set_callback('Network.loadingFinished', None)\r\n            self._driver.set_callback('Network.loadingFailed', None)\r\n            self.listening = False\r\n        if clear:\r\n            self.clear()\r\n\r\n    def resume(self):\r\n        if self.listening:\r\n            return\r\n        self._set_callback()\r\n        self.listening = True\r\n\r\n    def clear(self):\r\n        self._request_ids = {}\r\n        self._extra_info_ids = {}\r\n        self._caught = Queue(maxsize=0)\r\n        self._running_requests = 0\r\n        self._running_targets = 0\r\n\r\n    def wait_silent(self, timeout=None, targets_only=False, limit=0):\r\n        if not self.listening:\r\n            raise RuntimeError(_S._lang.join(_S._lang.NOT_LISTENING))\r\n        if timeout is None:\r\n            while ((not targets_only and self._running_requests > limit)\r\n                   or (targets_only and self._running_targets > limit)):\r\n                sleep(.01)\r\n            return True\r\n\r\n        end_time = perf_counter() + timeout\r\n        while perf_counter() < end_time:\r\n            if ((not targets_only and self._running_requests <= limit)\r\n                    or (targets_only and self._running_targets <= limit)):\r\n                return True\r\n            sleep(.01)\r\n        else:\r\n            return False\r\n\r\n    def _to_target(self, target_id, address, owner):\r\n        self._target_id = target_id\r\n        self._address = address\r\n        self._owner = owner\r\n        if self._driver:\r\n            self._driver.stop()\r\n        if self.listening:\r\n            self._driver = Driver(self._target_id, self._address)\r\n            self._driver.session_id = self._driver.run('Target.attachToTarget',\r\n                                                       targetId=target_id, flatten=True)['sessionId']\r\n            self._driver.run('Network.enable')\r\n            self._set_callback()\r\n\r\n    def _set_callback(self):\r\n        self._driver.set_callback('Network.requestWillBeSent', self._requestWillBeSent)\r\n        self._driver.set_callback('Network.requestWillBeSentExtraInfo', self._requestWillBeSentExtraInfo)\r\n        self._driver.set_callback('Network.responseReceived', self._response_received)\r\n        self._driver.set_callback('Network.responseReceivedExtraInfo', self._responseReceivedExtraInfo)\r\n        self._driver.set_callback('Network.loadingFinished', self._loading_finished)\r\n        self._driver.set_callback('Network.loadingFailed', self._loading_failed)\r\n\r\n    def _requestWillBeSent(self, **kwargs):\r\n        self._running_requests += 1\r\n        p = None\r\n        if self._targets is True:\r\n            if ((self._method is True or kwargs['request']['method'] in self._method)\r\n                    and (self._res_type is True or kwargs.get('type', '').upper() in self._res_type)):\r\n                self._running_targets += 1\r\n                rid = kwargs['requestId']\r\n                p = self._request_ids.setdefault(rid, DataPacket(self._owner.tab_id, True))\r\n                p._raw_request = kwargs\r\n                if kwargs['request'].get('hasPostData', None) and not kwargs['request'].get('postData', None):\r\n                    p._raw_post_data = self._driver.run('Network.getRequestPostData',\r\n                                                        requestId=rid).get('postData', None)\r\n\r\n        else:\r\n            rid = kwargs['requestId']\r\n            for target in self._targets:\r\n                if (((self._is_regex and search(target, kwargs['request']['url']))\r\n                     or (not self._is_regex and target in kwargs['request']['url']))\r\n                        and (self._method is True or kwargs['request']['method'] in self._method)\r\n                        and (self._res_type is True or kwargs.get('type', '').upper() in self._res_type)):\r\n                    self._running_targets += 1\r\n                    p = self._request_ids.setdefault(rid, DataPacket(self._owner.tab_id, target))\r\n                    p._raw_request = kwargs\r\n                    break\r\n\r\n        self._extra_info_ids.setdefault(kwargs['requestId'], {})['obj'] = p if p else False\r\n\r\n    def _requestWillBeSentExtraInfo(self, **kwargs):\r\n        self._running_requests += 1\r\n        self._extra_info_ids.setdefault(kwargs['requestId'], {})['request'] = kwargs\r\n\r\n    def _response_received(self, **kwargs):\r\n        request = self._request_ids.get(kwargs['requestId'], None)\r\n        if request:\r\n            request._raw_response = kwargs['response']\r\n            request._resource_type = kwargs['type']\r\n\r\n    def _responseReceivedExtraInfo(self, **kwargs):\r\n        self._running_requests -= 1\r\n        r = self._extra_info_ids.get(kwargs['requestId'], None)\r\n        if r:\r\n            obj = r.get('obj', None)\r\n            if obj is False:\r\n                self._extra_info_ids.pop(kwargs['requestId'], None)\r\n            elif isinstance(obj, DataPacket):\r\n                obj._requestExtraInfo = r.get('request', None)\r\n                obj._responseExtraInfo = kwargs\r\n                self._extra_info_ids.pop(kwargs['requestId'], None)\r\n            else:\r\n                r['response'] = kwargs\r\n\r\n    def _loading_finished(self, **kwargs):\r\n        self._running_requests -= 1\r\n        rid = kwargs['requestId']\r\n        packet = self._request_ids.get(rid)\r\n        if packet:\r\n            r = self._driver.run('Network.getResponseBody', requestId=rid)\r\n            if 'body' in r:\r\n                packet._raw_body = r['body']\r\n                packet._base64_body = r['base64Encoded']\r\n            else:\r\n                packet._raw_body = ''\r\n                packet._base64_body = False\r\n\r\n            if (packet._raw_request['request'].get('hasPostData', None)\r\n                    and not packet._raw_request['request'].get('postData', None)):\r\n                r = self._driver.run('Network.getRequestPostData', requestId=rid, _timeout=1)\r\n                packet._raw_post_data = r.get('postData', None)\r\n\r\n        r = self._extra_info_ids.get(kwargs['requestId'], None)\r\n        if r:\r\n            obj = r.get('obj', None)\r\n            if obj is False or (isinstance(obj, DataPacket) and not self._extra_info_ids.get('request')):\r\n                self._extra_info_ids.pop(kwargs['requestId'], None)\r\n            elif isinstance(obj, DataPacket) and self._extra_info_ids.get('response'):\r\n                response = r.get('response')\r\n                obj._requestExtraInfo = r['request']\r\n                obj._responseExtraInfo = response\r\n                self._extra_info_ids.pop(kwargs['requestId'], None)\r\n\r\n        self._request_ids.pop(rid, None)\r\n\r\n        if packet:\r\n            self._caught.put(packet)\r\n            self._running_targets -= 1\r\n\r\n    def _loading_failed(self, **kwargs):\r\n        self._running_requests -= 1\r\n        r_id = kwargs['requestId']\r\n        data_packet = self._request_ids.get(r_id, None)\r\n        if data_packet:\r\n            data_packet._raw_fail_info = kwargs\r\n            data_packet._resource_type = kwargs['type']\r\n            data_packet.is_failed = True\r\n\r\n        r = self._extra_info_ids.get(kwargs['requestId'], None)\r\n        if r:\r\n            obj = r.get('obj', None)\r\n            if obj is False and r.get('response'):\r\n                self._extra_info_ids.pop(kwargs['requestId'], None)\r\n            elif isinstance(obj, DataPacket):\r\n                response = r.get('response')\r\n                if response:\r\n                    obj._requestExtraInfo = r['request']\r\n                    obj._responseExtraInfo = response\r\n                    self._extra_info_ids.pop(kwargs['requestId'], None)\r\n\r\n        self._request_ids.pop(r_id, None)\r\n\r\n        if data_packet:\r\n            self._caught.put(data_packet)\r\n            self._running_targets -= 1\r\n\r\n\r\nclass FrameListener(Listener):\r\n    def _requestWillBeSent(self, **kwargs):\r\n        if not self._owner._is_diff_domain and kwargs.get('frameId', None) != self._owner._frame_id:\r\n            return\r\n        super()._requestWillBeSent(**kwargs)\r\n\r\n    def _response_received(self, **kwargs):\r\n        if not self._owner._is_diff_domain and kwargs.get('frameId', None) != self._owner._frame_id:\r\n            return\r\n        super()._response_received(**kwargs)\r\n\r\n\r\nclass DataPacket(object):\r\n\r\n    def __init__(self, tab_id, target):\r\n        self.tab_id = tab_id\r\n        self.target = target\r\n        self.is_failed = False\r\n\r\n        self._raw_request = None\r\n        self._raw_post_data = None\r\n        self._raw_response = None\r\n        self._raw_body = None\r\n        self._raw_fail_info = None\r\n\r\n        self._request = None\r\n        self._response = None\r\n        self._fail_info = None\r\n\r\n        self._base64_body = False\r\n        self._requestExtraInfo = None\r\n        self._responseExtraInfo = None\r\n        self._resource_type = None\r\n\r\n    def __repr__(self):\r\n        t = f'\"{self.target}\"' if self.target is not True else True\r\n        return f'<DataPacket target={t} url=\"{self.url}\">'\r\n\r\n    @property\r\n    def _request_extra_info(self):\r\n        return self._requestExtraInfo\r\n\r\n    @property\r\n    def _response_extra_info(self):\r\n        return self._responseExtraInfo\r\n\r\n    @property\r\n    def url(self):\r\n        return self.request.url\r\n\r\n    @property\r\n    def method(self):\r\n        return self.request.method\r\n\r\n    @property\r\n    def frameId(self):\r\n        return self._raw_request.get('frameId')\r\n\r\n    @property\r\n    def resourceType(self):\r\n        return self._resource_type\r\n\r\n    @property\r\n    def request(self):\r\n        if self._request is None:\r\n            self._request = Request(self, self._raw_request['request'], self._raw_post_data)\r\n        return self._request\r\n\r\n    @property\r\n    def response(self):\r\n        if self._response is None:\r\n            self._response = Response(self, self._raw_response, self._raw_body, self._base64_body)\r\n        return self._response\r\n\r\n    @property\r\n    def fail_info(self):\r\n        if self._fail_info is None:\r\n            self._fail_info = FailInfo(self, self._raw_fail_info)\r\n        return self._fail_info\r\n\r\n    def wait_extra_info(self, timeout=None):\r\n        if timeout is None:\r\n            while self._responseExtraInfo is None:\r\n                sleep(.01)\r\n            return True\r\n\r\n        else:\r\n            end_time = perf_counter() + timeout\r\n            while perf_counter() < end_time:\r\n                if self._responseExtraInfo is not None:\r\n                    return True\r\n                sleep(.01)\r\n            else:\r\n                return False\r\n\r\n\r\nclass Request(object):\r\n    def __init__(self, data_packet, raw_request, post_data):\r\n        self._data_packet = data_packet\r\n        self._request = raw_request\r\n        self._raw_post_data = post_data\r\n        self._postData = None\r\n        self._headers = None\r\n\r\n    def __getattr__(self, item):\r\n        return self._request.get(item, None)\r\n\r\n    @property\r\n    def headers(self):\r\n        if self._headers is None:\r\n            self._headers = CaseInsensitiveDict(self._request['headers'])\r\n            if self.extra_info.headers:\r\n                h = CaseInsensitiveDict(self.extra_info.headers)\r\n                for k, v in h.items():\r\n                    if k not in self._headers:\r\n                        self._headers[k] = v\r\n        return self._headers\r\n\r\n    @property\r\n    def params(self):\r\n        from urllib.parse import parse_qsl, urlparse\r\n        return dict(parse_qsl(urlparse(self.url).query, keep_blank_values=True))\r\n\r\n    @property\r\n    def postData(self):\r\n        if self._postData is None:\r\n            if self._raw_post_data:\r\n                postData = self._raw_post_data\r\n            elif self._request.get('postData', None):\r\n                postData = self._request['postData']\r\n            else:\r\n                postData = False\r\n            try:\r\n                self._postData = loads(postData)\r\n            except (JSONDecodeError, TypeError):\r\n                self._postData = postData\r\n        return self._postData\r\n\r\n    @property\r\n    def cookies(self):\r\n        return [c['cookie'] for c in self.extra_info.associatedCookies if not c['blockedReasons']]\r\n\r\n    @property\r\n    def extra_info(self):\r\n        return RequestExtraInfo(self._data_packet._request_extra_info or {})\r\n\r\n\r\nclass Response(object):\r\n    def __init__(self, data_packet, raw_response, raw_body, base64_body):\r\n        self._data_packet = data_packet\r\n        self._response = raw_response\r\n        self._raw_body = raw_body\r\n        self._is_base64_body = base64_body\r\n        self._body = None\r\n        self._headers = None\r\n\r\n    def __getattr__(self, item):\r\n        return self._response.get(item, None) if self._response else None\r\n\r\n    @property\r\n    def headers(self):\r\n        if self._headers is None:\r\n            self._headers = CaseInsensitiveDict(self._response['headers'])\r\n            if self.extra_info.headers:\r\n                h = CaseInsensitiveDict(self.extra_info.headers)\r\n                for k, v in h.items():\r\n                    if k not in self._headers:\r\n                        self._headers[k] = v\r\n        return self._headers\r\n\r\n    @property\r\n    def raw_body(self):\r\n        return self._raw_body\r\n\r\n    @property\r\n    def body(self):\r\n        if self._body is None:\r\n            if self._is_base64_body:\r\n                self._body = b64decode(self._raw_body)\r\n\r\n            else:\r\n                try:\r\n                    self._body = loads(self._raw_body)\r\n                except (JSONDecodeError, TypeError):\r\n                    self._body = self._raw_body\r\n\r\n        return self._body\r\n\r\n    @property\r\n    def extra_info(self):\r\n        return ResponseExtraInfo(self._data_packet._response_extra_info or {})\r\n\r\n\r\nclass ExtraInfo(object):\r\n    def __init__(self, extra_info):\r\n        self._extra_info = extra_info\r\n\r\n    @property\r\n    def all_info(self):\r\n        return self._extra_info\r\n\r\n    def __getattr__(self, item):\r\n        return self._extra_info.get(item, None)\r\n\r\n\r\nclass RequestExtraInfo(ExtraInfo):\r\n    pass\r\n\r\n\r\nclass ResponseExtraInfo(ExtraInfo):\r\n    pass\r\n\r\n\r\nclass FailInfo(object):\r\n    def __init__(self, data_packet, fail_info):\r\n        self._data_packet = data_packet\r\n        self._fail_info = fail_info\r\n\r\n    def __getattr__(self, item):\r\n        return self._fail_info.get(item, None) if self._fail_info else None\r\n"
  },
  {
    "path": "DrissionPage/_units/listener.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom queue import Queue\r\nfrom typing import Union, List, Iterable, Optional, Literal, Any\r\n\r\nfrom requests.structures import CaseInsensitiveDict\r\n\r\nfrom .._base.driver import Driver\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.chromium_frame import ChromiumFrame\r\n\r\n__RES_TYPE__ = Literal['Document', 'Stylesheet', 'Image', 'Media', 'Font', 'Script', 'TextTrack', 'XHR', 'Fetch',\r\n'Prefetch', 'EventSource', 'WebSocket', 'Manifest', 'SignedExchange', 'Ping', 'CSPViolationReport', 'Preflight', 'Other']\r\n\r\n\r\nclass Listener(object):\r\n    _owner: ChromiumBase = ...\r\n    _address: str = ...\r\n    _target_id: str = ...\r\n    _targets: Union[str, dict, True, None] = ...\r\n    _method: Union[set, True] = ...\r\n    _res_type: Union[set, True] = ...\r\n    _caught: Optional[Queue] = ...\r\n    _is_regex: bool = ...\r\n    _driver: Optional[Driver] = ...\r\n    _request_ids: Optional[dict] = ...\r\n    _extra_info_ids: Optional[dict] = ...\r\n    _running_requests: int = ...\r\n    _running_targets: int = ...\r\n    listening: bool = ...\r\n\r\n    def __init__(self, owner: ChromiumBase):\r\n        \"\"\"\r\n        :param owner: 页面对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def targets(self) -> Optional[set]: ...\r\n\r\n    def set_targets(self,\r\n                    targets: Union[str, list, tuple, set, bool, None] = True,\r\n                    is_regex: Optional[bool] = False,\r\n                    method: Union[str, list, tuple, set, bool, None] = ('GET', 'POST'),\r\n                    res_type: Union[__RES_TYPE__, list, tuple, set, bool, None] = True) -> None:\r\n        \"\"\"指定要等待的数据包\r\n        :param targets: 要匹配的数据包url特征，可用list等传入多个，为True时获取所有\r\n        :param is_regex: 设置的target是否正则表达式\r\n        :param method: 设置监听的请求类型，可指定多个，为True时监听全部\r\n        :param res_type: 设置监听的资源类型，可指定多个，为True时监听全部，可指定的值有：\r\n        Document, Stylesheet, Image, Media, Font, Script, TextTrack, XHR, Fetch, Prefetch, EventSource, WebSocket,\r\n        Manifest, SignedExchange, Ping, CSPViolationReport, Preflight, Other\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def start(self,\r\n              targets: Union[str, list, tuple, set, bool, None] = None,\r\n              is_regex: Optional[bool] = None,\r\n              method: Union[str, list, tuple, set, bool, None] = None,\r\n              res_type: Union[__RES_TYPE__, list, tuple, set, bool, None] = None) -> None:\r\n        \"\"\"拦截目标请求，每次拦截前清空结果\r\n        :param targets: 要匹配的数据包url特征，可用list等传入多个，为True时获取所有\r\n        :param is_regex: 设置的target是否正则表达式，为None时保持原来设置\r\n        :param method: 设置监听的请求类型，可指定多个，默认('GET', 'POST')，为True时监听全部，为None时保持原来设置\r\n        :param res_type: 设置监听的资源类型，可指定多个，默认为True时监听全部，为None时保持原来设置，可指定的值有：\r\n        Document, Stylesheet, Image, Media, Font, Script, TextTrack, XHR, Fetch, Prefetch, EventSource, WebSocket,\r\n        Manifest, SignedExchange, Ping, CSPViolationReport, Preflight, Other\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def wait(self,\r\n             count: int = 1,\r\n             timeout: float = None,\r\n             fit_count: bool = True,\r\n             raise_err: bool = None) -> Union[List[DataPacket], DataPacket, None]:\r\n        \"\"\"等待符合要求的数据包到达指定数量\r\n        :param count: 需要捕捉的数据包数量\r\n        :param timeout: 超时时间（秒），为None无限等待\r\n        :param fit_count: 是否必须满足总数要求，发生超时，为True返回False，为False返回已捕捉到的数据包\r\n        :param raise_err: 超时时是否抛出错误，为None时根据Settings设置\r\n        :return: count为1时返回数据包对象，大于1时返回列表，超时且fit_count为True时返回False\r\n        \"\"\"\r\n        ...\r\n\r\n    def steps(self,\r\n              count: int = None,\r\n              timeout: float = None,\r\n              gap=1) -> Iterable[Union[DataPacket, List[DataPacket]]]:\r\n        \"\"\"用于单步操作，可实现每收到若干个数据包执行一步操作（如翻页）\r\n        :param count: 需捕获的数据包总数，为None表示无限\r\n        :param timeout: 每个数据包等待时间（秒），为None表示无限\r\n        :param gap: 每接收到多少个数据包返回一次数据\r\n        :return: 用于在接收到监听目标时触发动作的可迭代对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def stop(self) -> None:\r\n        \"\"\"停止监听，清空已监听到的列表\"\"\"\r\n        ...\r\n\r\n    def pause(self, clear: bool = True) -> None:\r\n        \"\"\"暂停监听\r\n        :param clear: 是否清空已获取队列\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def resume(self) -> None:\r\n        \"\"\"继续暂停的监听\"\"\"\r\n        ...\r\n\r\n    def clear(self) -> None:\r\n        \"\"\"清空监听到但还没返回的结果\"\"\"\r\n        ...\r\n\r\n    def wait_silent(self,\r\n                    timeout: float = None,\r\n                    targets_only: bool = False,\r\n                    limit: int = 0) -> bool:\r\n        \"\"\"等待所有请求结束\r\n        :param timeout: 超时时间（秒），为None时无限等待\r\n        :param targets_only: 是否只等待targets指定的请求结束\r\n        :param limit: 剩下多少个连接时视为结束\r\n        :return: 返回是否等待成功\r\n        \"\"\"\r\n        ...\r\n\r\n    def _to_target(self, target_id: str, address: str, owner: ChromiumBase) -> None:\r\n        \"\"\"切换监听的页面对象\r\n        :param target_id: 新页面对象_target_id\r\n        :param address: 新页面对象address\r\n        :param owner: 新页面对象\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _set_callback(self) -> None: ...\r\n\r\n    def _requestWillBeSent(self, **kwargs) -> None: ...\r\n\r\n    def _requestWillBeSentExtraInfo(self, **kwargs) -> None: ...\r\n\r\n    def _response_received(self, **kwargs) -> None: ...\r\n\r\n    def _responseReceivedExtraInfo(self, **kwargs) -> None: ...\r\n\r\n    def _loading_finished(self, **kwargs) -> None: ...\r\n\r\n    def _loading_failed(self, **kwargs) -> None: ...\r\n\r\n\r\nclass FrameListener(Listener):\r\n    _owner: ChromiumFrame = ...\r\n    _is_diff: bool = ...\r\n\r\n    def __init__(self, owner: ChromiumFrame):\r\n        \"\"\"\r\n        :param owner: ChromiumFrame对象\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass DataPacket(object):\r\n    \"\"\"数据包类\"\"\"\r\n\r\n    tab_id: str = ...\r\n    target: str = ...\r\n    is_failed: bool = ...\r\n    _raw_request: Optional[dict] = ...\r\n    _raw_response: Optional[dict] = ...\r\n    _raw_post_data: Optional[str] = ...\r\n    _raw_body: Optional[str] = ...\r\n    _raw_fail_info: Optional[dict] = ...\r\n    _base64_body: bool = ...\r\n    _request: Optional[Request] = ...\r\n    _response: Optional[Response] = ...\r\n    _fail_info: Optional[FailInfo] = ...\r\n    _resource_type: Optional[str] = ...\r\n    _requestExtraInfo: Optional[dict] = ...\r\n    _responseExtraInfo: Optional[dict] = ...\r\n\r\n    def __init__(self, tab_id: str, target: [str, bool]):\r\n        \"\"\"\r\n        :param tab_id: 产生这个数据包的tab的id\r\n        :param target: 监听目标\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def url(self) -> str:\r\n        \"\"\"请求网址\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def method(self) -> str:\r\n        \"\"\"请求类型\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def frameId(self) -> str:\r\n        \"\"\"发出请求的frame id\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def resourceType(self) -> str:\r\n        \"\"\"资源类型\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def request(self) -> Request:\r\n        \"\"\"数据\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def response(self) -> Response:\r\n        \"\"\"Response数据\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def fail_info(self) -> Optional[FailInfo]:\r\n        \"\"\"请求失败数据\"\"\"\r\n        ...\r\n\r\n    def wait_extra_info(self, timeout: float = None) -> bool:\r\n        \"\"\"等待额外的信息加载完成\r\n        :param timeout: 超时时间（秒），None为无限等待\r\n        :return: 是否等待成功\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def _request_extra_info(self) -> Optional[dict]: ...\r\n\r\n    @property\r\n    def _response_extra_info(self) -> Optional[dict]: ...\r\n\r\n\r\nclass Request(object):\r\n    _data_packet: DataPacket = ...\r\n    _request: dict = ...\r\n    _raw_post_data: str = ...\r\n    _postData: Optional[str] = ...\r\n\r\n    url: str = ...\r\n    _headers: Union[CaseInsensitiveDict, None] = ...\r\n    method: str = ...\r\n\r\n    urlFragment: str = ...\r\n    hasPostData: bool = ...\r\n    postDataEntries: List[dict] = ...\r\n    mixedContentType: Literal['blockable', 'optionally-blockable', 'none'] = ...\r\n    initialPriority: Literal['VeryLow', 'Low', 'Medium', 'High', 'VeryHigh'] = ...\r\n    referrerPolicy: Literal['unsafe-url', 'no-referrer-when-downgrade', 'no-referrer', 'origin',\r\n    'origin-when-cross-origin', 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin'] = ...\r\n    isLinkPreload: bool = ...\r\n    trustTokenParams: dict = ...\r\n    isSameSite: bool = ...\r\n\r\n    def __init__(self,\r\n                 data_packet: DataPacket,\r\n                 raw_request: dict,\r\n                 post_data: str):\r\n        \"\"\"\r\n        :param data_packet: DataPacket对象\r\n        :param raw_request: 未处理的请求数据\r\n        :param post_data: post发送的数据\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def headers(self) -> dict:\r\n        \"\"\"以大小写不敏感字典返回headers数据\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def params(self) -> dict:\r\n        \"\"\"dict格式返回请求url中的参数\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def postData(self) -> Any:\r\n        \"\"\"返回postData数据\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def cookies(self) -> List[dict]:\r\n        \"\"\"以list形式返回发送的cookies\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def extra_info(self) -> Optional[RequestExtraInfo]:\r\n        \"\"\"返回额外数据\"\"\"\r\n        ...\r\n\r\n\r\nclass Response(object):\r\n    _data_packet: DataPacket = ...\r\n    _response: dict = ...\r\n    _raw_body: str = ...\r\n    _is_base64_body: bool = ...\r\n    _body: Union[str, dict, bytes, None] = ...\r\n    _headers: Union[dict, CaseInsensitiveDict, None] = ...\r\n\r\n    url: str = ...\r\n    status: int = ...\r\n    statusText: str = ...\r\n    headersText: str = ...\r\n    mimeType: str = ...\r\n    requestHeaders: dict = ...\r\n    requestHeadersText: str = ...\r\n    connectionReused: bool = ...\r\n    connectionId = ...\r\n    remoteIPAddress: str = ...\r\n    remotePort: int = ...\r\n    fromDiskCache: bool = ...\r\n    fromServiceWorker: bool = ...\r\n    fromPrefetchCache: bool = ...\r\n    fromEarlyHints: bool = ...\r\n    serviceWorkerRouterInfo: dict = ...\r\n    encodedDataLength: int = ...\r\n    timing: dict = ...\r\n    serviceWorkerResponseSource: Literal['cache-storage', 'http-cache', 'fallback-code', 'network'] = ...\r\n    responseTime: float = ...\r\n    cacheStorageCacheName: str = ...\r\n    protocol: str = ...\r\n    alternateProtocolUsage: Literal['alternativeJobWonWithoutRace', 'alternativeJobWonRace', 'mainJobWonRace',\r\n    'mappingMissing', 'broken', 'dnsAlpnH3JobWonWithoutRace', 'dnsAlpnH3JobWonRace', 'unspecifiedReason'] = ...\r\n    securityState: Literal['unknown', 'neutral', 'insecure', 'secure', 'info', 'insecure-broken'] = ...\r\n    securityDetails: dict = ...\r\n\r\n    def __init__(self,\r\n                 data_packet: DataPacket,\r\n                 raw_response: dict,\r\n                 raw_body: str,\r\n                 base64_body: bool):\r\n        \"\"\"\r\n        :param data_packet: DataPacket对象\r\n        :param raw_response: 未处理的response信息\r\n        :param raw_body: 未处理的body\r\n        :param base64_body: body是否base64格式\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def headers(self) -> CaseInsensitiveDict:\r\n        \"\"\"以大小写不敏感字典返回headers数据\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def raw_body(self) -> str:\r\n        \"\"\"返回未被处理的body文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def body(self) -> Any:\r\n        \"\"\"返回body内容，如果是json格式，自动进行转换，如果时图片格式，进行base64转换，其它格式直接返回文本\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def extra_info(self) -> Optional[ResponseExtraInfo]:\r\n        \"\"\"额外信息\"\"\"\r\n        ...\r\n\r\n\r\nclass ExtraInfo(object):\r\n    _extra_info: dict = ...\r\n\r\n    def __init__(self, extra_info: dict):\r\n        \"\"\"\r\n        :param extra_info: dict格式信息\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def all_info(self) -> dict:\r\n        \"\"\"以dict形式返回所有额外信息\"\"\"\r\n        ...\r\n\r\n\r\nclass RequestExtraInfo(ExtraInfo):\r\n    requestId: str = ...\r\n    associatedCookies: List[dict] = ...\r\n    headers: dict = ...\r\n    connectTiming: dict = ...\r\n    clientSecurityState: dict = ...\r\n    siteHasCookieInOtherPartition: bool = ...\r\n\r\n\r\nclass ResponseExtraInfo(ExtraInfo):\r\n    requestId: str = ...\r\n    blockedCookies: List[dict] = ...\r\n    headers: dict = ...\r\n    resourceIPAddressSpace: str = ...\r\n    statusCode: int = ...\r\n    headersText: str = ...\r\n    cookiePartitionKey: str = ...\r\n    cookiePartitionKeyOpaque: bool = ...\r\n\r\n\r\nclass FailInfo(object):\r\n    _data_packet: DataPacket\r\n    _fail_info: dict\r\n    errorText: str\r\n    canceled: bool\r\n    blockedReason: Optional[str]\r\n    corsErrorStatus: Optional[str]\r\n\r\n    def __init__(self, data_packet: DataPacket, fail_info: dict):\r\n        \"\"\"\r\n        :param data_packet: DataPacket对象\r\n        :param fail_info: 返回的失败数据\r\n        \"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/rect.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\n\r\n\r\nclass ElementRect(object):\r\n    def __init__(self, ele):\r\n        self._ele = ele\r\n\r\n    @property\r\n    def corners(self):\r\n        vr = self._get_viewport_rect('border')\r\n        r = self._ele.owner._run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']\r\n        sx = r['pageX']\r\n        sy = r['pageY']\r\n        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)]\r\n\r\n    @property\r\n    def viewport_corners(self):\r\n        r = self._get_viewport_rect('border')\r\n        return (r[0], r[1]), (r[2], r[3]), (r[4], r[5]), (r[6], r[7])\r\n\r\n    @property\r\n    def size(self):\r\n        border = self._ele.owner._run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id,\r\n                                          nodeId=self._ele._node_id, objectId=self._ele._obj_id)['model']['border']\r\n        return border[2] - border[0], border[5] - border[1]\r\n\r\n    @property\r\n    def location(self):\r\n        return self._get_page_coord(*self.viewport_location)\r\n\r\n    @property\r\n    def midpoint(self):\r\n        return self._get_page_coord(*self.viewport_midpoint)\r\n\r\n    @property\r\n    def click_point(self):\r\n        return self._get_page_coord(*self.viewport_click_point)\r\n\r\n    @property\r\n    def viewport_location(self):\r\n        m = self._get_viewport_rect('border')\r\n        return m[0], m[1]\r\n\r\n    @property\r\n    def viewport_midpoint(self):\r\n        m = self._get_viewport_rect('border')\r\n        return m[0] + (m[2] - m[0]) // 2, m[3] + (m[5] - m[3]) // 2\r\n\r\n    @property\r\n    def viewport_click_point(self):\r\n        m = self._get_viewport_rect('padding')\r\n        return self.viewport_midpoint[0], m[1] + 3\r\n\r\n    @property\r\n    def screen_location(self):\r\n        vx, vy = self._ele.owner.rect.viewport_location\r\n        ex, ey = self.viewport_location\r\n        pr = self._ele.owner._run_js('return window.devicePixelRatio;')\r\n        if getattr(self._ele.owner, '_is_diff_domain', None):\r\n            x, y = self._ele.owner.rect.screen_location\r\n            return ex * pr + x, ey * pr + y\r\n        else:\r\n            return (vx + ex) * pr, (ey + vy) * pr\r\n\r\n    @property\r\n    def screen_midpoint(self):\r\n        vx, vy = self._ele.owner.rect.viewport_location\r\n        ex, ey = self.viewport_midpoint\r\n        pr = self._ele.owner._run_js('return window.devicePixelRatio;')\r\n        if getattr(self._ele.owner, '_is_diff_domain', None):\r\n            x, y = self._ele.owner.rect.screen_location\r\n            return ex * pr + x, ey * pr + y\r\n        else:\r\n            return (vx + ex) * pr, (ey + vy) * pr\r\n\r\n    @property\r\n    def screen_click_point(self):\r\n        vx, vy = self._ele.owner.rect.viewport_location\r\n        ex, ey = self.viewport_click_point\r\n        pr = self._ele.owner._run_js('return window.devicePixelRatio;')\r\n        if getattr(self._ele.owner, '_is_diff_domain', None):\r\n            x, y = self._ele.owner.rect.screen_location\r\n            return ex * pr + x, ey * pr + y\r\n        else:\r\n            return (vx + ex) * pr, (ey + vy) * pr\r\n\r\n    @property\r\n    def scroll_position(self):\r\n        r = self._ele._run_js('return this.scrollLeft.toString() + \" \" + this.scrollTop.toString();')\r\n        w, h = r.split(' ')\r\n        return int(w), int(h)\r\n\r\n    def _get_viewport_rect(self, quad):\r\n        return self._ele.owner._run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id)['model'][quad]\r\n\r\n    def _get_page_coord(self, x, y):\r\n        r = self._ele.owner._run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']\r\n        sx = r['pageX']\r\n        sy = r['pageY']\r\n        return x + sx, y + sy\r\n\r\n\r\nclass TabRect(object):\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n\r\n    @property\r\n    def window_state(self):\r\n        return self._get_window_rect()['windowState']\r\n\r\n    @property\r\n    def window_location(self):\r\n        r = self._get_window_rect()\r\n        if r['windowState'] in ('maximized', 'fullscreen'):\r\n            return 0, 0\r\n        return r['left'] + 7, r['top']\r\n\r\n    @property\r\n    def window_size(self):\r\n        r = self._get_window_rect()\r\n        if r['windowState'] == 'fullscreen':\r\n            return r['width'], r['height']\r\n        elif r['windowState'] == 'maximized':\r\n            return r['width'] - 16, r['height'] - 16\r\n        else:\r\n            return r['width'] - 16, r['height'] - 7\r\n\r\n    @property\r\n    def page_location(self):\r\n        w, h = self.viewport_location\r\n        r = self._get_page_rect()['layoutViewport']\r\n        return w - r['pageX'], h - r['pageY']\r\n\r\n    @property\r\n    def viewport_location(self):\r\n        w_bl, h_bl = self.window_location\r\n        w_bs, h_bs = self.window_size\r\n        w_vs, h_vs = self.viewport_size_with_scrollbar\r\n        return w_bl + w_bs - w_vs, h_bl + h_bs - h_vs\r\n\r\n    @property\r\n    def size(self):\r\n        r = self._get_page_rect()['contentSize']\r\n        return r['width'], r['height']\r\n\r\n    @property\r\n    def viewport_size(self):\r\n        r = self._get_page_rect()['visualViewport']\r\n        return r['clientWidth'], r['clientHeight']\r\n\r\n    @property\r\n    def viewport_size_with_scrollbar(self):\r\n        r = self._owner._run_js('return window.innerWidth.toString() + \" \" + window.innerHeight.toString();')\r\n        w, h = r.split(' ')\r\n        return int(w), int(h)\r\n\r\n    @property\r\n    def scroll_position(self):\r\n        r = self._get_page_rect()['visualViewport']\r\n        return r['pageX'], r['pageY']\r\n\r\n    def _get_page_rect(self):\r\n        return self._owner._run_cdp_loaded('Page.getLayoutMetrics')\r\n\r\n    def _get_window_rect(self):\r\n        return self._owner.browser._driver.run('Browser.getWindowForTarget', targetId=self._owner.tab_id)['bounds']\r\n\r\n\r\nclass FrameRect(object):\r\n    \"\"\"异域iframe使用\"\"\"\r\n\r\n    def __init__(self, frame):\r\n        self._frame = frame\r\n\r\n    @property\r\n    def location(self):\r\n        return self._frame.frame_ele.rect.location\r\n\r\n    @property\r\n    def viewport_location(self):\r\n        return self._frame.frame_ele.rect.viewport_location\r\n\r\n    @property\r\n    def screen_location(self):\r\n        return self._frame.frame_ele.rect.screen_location\r\n\r\n    @property\r\n    def size(self):\r\n        w = self._frame.doc_ele._run_js('return this.body.scrollWidth')\r\n        h = self._frame.doc_ele._run_js('return this.body.scrollHeight')\r\n        return w, h\r\n\r\n    @property\r\n    def viewport_size(self):\r\n        return self._frame.frame_ele.rect.size\r\n\r\n    @property\r\n    def corners(self):\r\n        return self._frame.frame_ele.rect.corners\r\n\r\n    @property\r\n    def viewport_corners(self):\r\n        return self._frame.frame_ele.rect.viewport_corners\r\n\r\n    @property\r\n    def scroll_position(self):\r\n        r = self._frame.doc_ele._run_js('return this.documentElement.scrollLeft.toString() + \" \" '\r\n                                        '+ this.documentElement.scrollTop.toString();')\r\n        w, h = r.split(' ')\r\n        return int(w), int(h)\r\n"
  },
  {
    "path": "DrissionPage/_units/rect.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Tuple, Union\r\n\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.chromium_frame import ChromiumFrame\r\nfrom .._pages.chromium_page import ChromiumPage\r\nfrom .._pages.chromium_tab import ChromiumTab\r\nfrom .._pages.mix_tab import MixTab\r\nfrom .._pages.web_page import WebPage\r\n\r\n\r\nclass ElementRect(object):\r\n    _ele: ChromiumElement = ...\r\n    def __init__(self, ele: ChromiumElement):\r\n        \"\"\"\r\n        :param ele: ChromiumElement对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def corners(self) -> Tuple[Tuple[float, float], ...]:\r\n        \"\"\"返回元素四个角坐标，顺序：左上、右上、右下、左下，没有大小的元素抛出NoRectError\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_corners(self) -> Tuple[Tuple[float, float], ...]:\r\n        \"\"\"返回元素四个角视口坐标，顺序：左上、右上、右下、左下，没有大小的元素抛出NoRectError\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def size(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素大小，格式(宽, 高)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def location(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素左上角的绝对坐标\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def midpoint(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素中间点的绝对坐标\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def click_point(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素接受点击的点的绝对坐标\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_location(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素左上角在视口中的坐标\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_midpoint(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素中间点在视口中的坐标\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_click_point(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素接受点击的点视口坐标\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def screen_location(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素左上角在屏幕上坐标，左上角为(0, 0)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def screen_midpoint(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素中点在屏幕上坐标，左上角为(0, 0)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def screen_click_point(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素中点在屏幕上坐标，左上角为(0, 0)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def scroll_position(self) -> Tuple[float, float]:\r\n        \"\"\"返回滚动条位置，格式：(x, y)\"\"\"\r\n        ...\r\n\r\n    def _get_viewport_rect(self, quad: str) -> Union[list, None]:\r\n        \"\"\"按照类型返回在可视窗口中的范围\r\n        :param quad: 方框类型，margin border padding\r\n        :return: 四个角坐标\r\n        \"\"\"\r\n        ...\r\n\r\n    def _get_page_coord(self, x: float, y: float) -> Tuple[float, float]:\r\n        \"\"\"根据视口坐标获取绝对坐标\r\n        :param x: 视口x坐标\r\n        :param y: 视口y坐标\r\n        :return: 坐标元组\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass TabRect(object):\r\n    def __init__(self, owner: ChromiumBase):\r\n        \"\"\"\r\n        :param owner: Page对象和Tab对象\r\n        \"\"\"\r\n        self._owner: Union[ChromiumPage, ChromiumTab, WebPage, MixTab] = ...\r\n\r\n    @property\r\n    def window_state(self) -> str:\r\n        \"\"\"返回窗口状态：normal、fullscreen、maximized、minimized\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def window_location(self) -> Tuple[int, int]:\r\n        \"\"\"返回窗口在屏幕上的坐标，左上角为(0, 0)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def window_size(self) -> Tuple[int, int]:\r\n        \"\"\"返回窗口大小\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def page_location(self) -> Tuple[int, int]:\r\n        \"\"\"返回页面左上角在屏幕中坐标，左上角为(0, 0)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_location(self) -> Tuple[int, int]:\r\n        \"\"\"返回视口在屏幕中坐标，左上角为(0, 0)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def size(self) -> Tuple[int, int]:\r\n        \"\"\"返回页面总宽高，格式：(宽, 高)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_size(self) -> Tuple[int, int]:\r\n        \"\"\"返回视口宽高，不包括滚动条，格式：(宽, 高)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_size_with_scrollbar(self) -> Tuple[int, int]:\r\n        \"\"\"返回视口宽高，包括滚动条，格式：(宽, 高)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def scroll_position(self) -> Tuple[int, int]:\r\n        \"\"\"返回滚动条位置，格式：(x, y)\"\"\"\r\n        ...\r\n\r\n    def _get_page_rect(self) -> dict:\r\n        \"\"\"获取页面范围信息\"\"\"\r\n        ...\r\n\r\n    def _get_window_rect(self) -> dict:\r\n        \"\"\"获取窗口范围信息\"\"\"\r\n        ...\r\n\r\n\r\nclass FrameRect(object):\r\n    _frame: ChromiumFrame = ...\r\n\r\n    def __init__(self, frame: ChromiumFrame):\r\n        \"\"\"\r\n        :param frame: ChromiumFrame对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def location(self) -> Tuple[float, float]:\r\n        \"\"\"返回iframe元素左上角的绝对坐标\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_location(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素在视口中坐标，左上角为(0, 0)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def screen_location(self) -> Tuple[float, float]:\r\n        \"\"\"返回元素左上角在屏幕上坐标，左上角为(0, 0)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def size(self) -> Tuple[float, float]:\r\n        \"\"\"返回frame内页面尺寸，格式：(宽, 高)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_size(self) -> Tuple[float, float]:\r\n        \"\"\"返回视口宽高，格式：(宽, 高)\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def corners(self) -> Tuple[Tuple[float, float], ...]:\r\n        \"\"\"返回元素四个角坐标，顺序：左上、右上、右下、左下\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def viewport_corners(self) -> Tuple[Tuple[float, float], ...]:\r\n        \"\"\"返回元素四个角视口坐标，顺序：左上、右上、右下、左下\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def scroll_position(self) -> Tuple[float, float]:\r\n        \"\"\"返回滚动条位置，格式：(x, y)\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/screencast.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom base64 import b64decode\r\nfrom os.path import sep\r\nfrom pathlib import Path\r\nfrom random import randint\r\nfrom shutil import rmtree\r\nfrom tempfile import gettempdir\r\nfrom threading import Thread\r\nfrom time import sleep, time\r\n\r\nfrom .._functions.settings import Settings as _S\r\n\r\n\r\nclass Screencast(object):\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n        self._path = None\r\n        self._tmp_path = None\r\n        self._running = False\r\n        self._enable = False\r\n        self._mode = 'video'\r\n\r\n    @property\r\n    def set_mode(self):\r\n        return ScreencastMode(self)\r\n\r\n    def start(self, save_path=None):\r\n        self.set_save_path(save_path)\r\n        if self._path is None:\r\n            raise RuntimeError(_S._lang.join(_S._lang.NEED_ARG_, 'save_path'))\r\n\r\n        if self._mode in ('frugal_video', 'video'):\r\n            if self._owner.browser._chromium_options.tmp_path:\r\n                self._tmp_path = Path(\r\n                    self._owner.browser._chromium_options.tmp_path) / f'screencast_tmp_{time()}_{randint(0, 100)}'\r\n            else:\r\n                self._tmp_path = Path(gettempdir()) / 'DrissionPage' / f'screencast_tmp_{time()}_{randint(0, 100)}'\r\n            self._tmp_path.mkdir(parents=True, exist_ok=True)\r\n\r\n        if self._mode.startswith('frugal'):\r\n            self._owner.driver.set_callback('Page.screencastFrame', self._onScreencastFrame)\r\n            self._owner._run_cdp('Page.startScreencast', everyNthFrame=1, quality=100)\r\n\r\n        elif not self._mode.startswith('js'):\r\n            self._running = True\r\n            self._enable = True\r\n            Thread(target=self._run).start()\r\n\r\n        else:  # js模式\r\n            js = '''\r\n            async function () {\r\n                stream = await navigator.mediaDevices.getDisplayMedia({video: true, audio: true})\r\n                mime = MediaRecorder.isTypeSupported(\"video/webm; codecs=vp9\")\r\n                               ? \"video/webm; codecs=vp9\"\r\n                               : \"video/webm\"\r\n                mediaRecorder = new MediaRecorder(stream, {mimeType: mime})\r\n                DrissionPage_Screencast_chunks = [];\r\n                mediaRecorder.addEventListener('dataavailable', function(e) {\r\n                    DrissionPage_Screencast_blob_ok = false;\r\n                    DrissionPage_Screencast_chunks.push(e.data);\r\n                    DrissionPage_Screencast_blob_ok = true;\r\n                })\r\n                mediaRecorder.start()\r\n\r\n                mediaRecorder.addEventListener('stop', function(){\r\n                    while(DrissionPage_Screencast_blob_ok==false){}\r\n                    DrissionPage_Screencast_blob = new Blob(DrissionPage_Screencast_chunks, \r\n                                                            {type: DrissionPage_Screencast_chunks[0].type});\r\n                })\r\n              }\r\n            '''\r\n            print(_S._lang.CHOOSE_RECORD_TARGET)\r\n            self._owner._run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;')\r\n            self._owner._run_js(js)\r\n        print(_S._lang.START_RECORD)\r\n\r\n    def stop(self, video_name=None, suffix='mp4', coding='mp4v'):\r\n        video_name = f'{time()}.{suffix}' if not video_name else video_name\r\n        if not video_name.endswith(f'.{suffix}'):\r\n            video_name = f'{video_name}.{suffix}'\r\n        path = f'{self._path}{sep}{video_name}'\r\n\r\n        if self._mode.startswith('js'):\r\n            self._owner._run_js('mediaRecorder.stop();', as_expr=True)\r\n            while not self._owner._run_js('return DrissionPage_Screencast_blob_ok;'):\r\n                sleep(.05)\r\n            with open(path, 'wb') as f:\r\n                f.write(b64decode(self._owner._run_js('return DrissionPage_Screencast_blob;')))\r\n            self._owner._run_js('DrissionPage_Screencast_blob_ok = false;'\r\n                                'DrissionPage_Screencast_chunks = [];'\r\n                                'DrissionPage_Screencast_blob = null', as_expr=True)\r\n            print(_S._lang.STOP_RECORDING)\r\n            return path\r\n\r\n        if self._mode.startswith('frugal'):\r\n            self._owner.driver.set_callback('Page.screencastFrame', None)\r\n            self._owner._run_cdp('Page.stopScreencast')\r\n        else:\r\n            self._enable = False\r\n            while self._running:\r\n                sleep(.01)\r\n\r\n        if self._mode.endswith('imgs'):\r\n            print(_S._lang.STOP_RECORDING)\r\n            return str(Path(self._path).absolute())\r\n\r\n        if not str(self._path).isascii():\r\n            raise ValueError(_S._lang.join(_S._lang.ONLY_ENGLISH, CURR_VAL=self._path))\r\n\r\n        try:\r\n            from cv2 import VideoWriter, imread, VideoWriter_fourcc\r\n            from numpy import fromfile, uint8\r\n        except (ImportError, ModuleNotFoundError):\r\n            raise EnvironmentError(_S._lang.join(_S._lang.NEED_LIB_, 'cv2, numpy',\r\n                                                 TIP='pip install opencv-python\\npip install numpy'))\r\n\r\n        pic_list = Path(self._tmp_path or self._path).glob('*.jpg')\r\n        img = imread(str(next(pic_list)))\r\n        imgInfo = img.shape\r\n        size = (imgInfo[1], imgInfo[0])\r\n\r\n        videoWrite = VideoWriter(path, VideoWriter_fourcc(*coding.upper()), 5, size)\r\n\r\n        for i in pic_list:\r\n            img = imread(str(i))\r\n            videoWrite.write(img)\r\n\r\n        rmtree(self._tmp_path)\r\n        self._tmp_path = None\r\n        print(_S._lang.STOP_RECORDING)\r\n        return f'{self._path}{sep}{video_name}'\r\n\r\n    def set_save_path(self, save_path=None):\r\n        if save_path:\r\n            save_path = Path(save_path)\r\n            if save_path.exists() and save_path.is_file():\r\n                raise ValueError(_S._lang.join(_S._lang.SAVE_PATH_MUST_BE_FOLDER))\r\n            save_path.mkdir(parents=True, exist_ok=True)\r\n            self._path = save_path\r\n\r\n    def _run(self):\r\n        self._running = True\r\n        path = self._tmp_path or self._path\r\n        while self._enable:\r\n            self._owner.get_screenshot(path=path, name=f'{time()}.jpg')\r\n            sleep(.04)\r\n        self._running = False\r\n\r\n    def _onScreencastFrame(self, **kwargs):\r\n        path = self._tmp_path or self._path\r\n        with open(f'{path}{sep}{kwargs[\"metadata\"][\"timestamp\"]}.jpg', 'wb') as f:\r\n            f.write(b64decode(kwargs['data']))\r\n        self._owner._run_cdp('Page.screencastFrameAck', sessionId=kwargs['sessionId'])\r\n\r\n\r\nclass ScreencastMode(object):\r\n    def __init__(self, screencast):\r\n        self._screencast = screencast\r\n\r\n    def video_mode(self):\r\n        self._screencast._mode = 'video'\r\n\r\n    def frugal_video_mode(self):\r\n        self._screencast._mode = 'frugal_video'\r\n\r\n    def js_video_mode(self):\r\n        self._screencast._mode = 'js_video'\r\n\r\n    def frugal_imgs_mode(self):\r\n        self._screencast._mode = 'frugal_imgs'\r\n\r\n    def imgs_mode(self):\r\n        self._screencast._mode = 'imgs'\r\n"
  },
  {
    "path": "DrissionPage/_units/screencast.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Union, Optional\r\n\r\nfrom .._pages.chromium_base import ChromiumBase\r\n\r\n\r\nclass Screencast(object):\r\n    _owner: ChromiumBase = ...\r\n    _path: Optional[Path] = ...\r\n    _tmp_path: Optional[Path] = ...\r\n    _running: bool = ...\r\n    _enable: bool = ...\r\n    _mode: str = ...\r\n\r\n    def __init__(self, owner: ChromiumBase):\r\n        \"\"\"\r\n        :param owner: 页面对象\r\n        \"\"\"\r\n\r\n    @property\r\n    def set_mode(self) -> ScreencastMode:\r\n        \"\"\"返回用于设置录屏幕式的对象\"\"\"\r\n        ...\r\n\r\n    def start(self, save_path: Union[str, Path] = None) -> None:\r\n        \"\"\"开始录屏\r\n        :param save_path: 录屏保存位置\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def stop(self, video_name: str = None, suffix: str='mp4', coding:str='mp4v') -> str:\r\n        \"\"\"停止录屏\r\n        :param video_name: 视频文件名，为None时以当前时间名命\r\n        :param suffix: 文件后缀名\r\n        :param coding: 视频编码格式，仅video_mode模式有效，根据cv2.VideoWriter_fourcc()定义\r\n        :return: 文件路径\r\n        \"\"\"\r\n        ...\r\n\r\n    def set_save_path(self, save_path: Union[str, Path] = None) -> None:\r\n        \"\"\"设置保存路径\r\n        :param save_path: 保存路径\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _run(self) -> None:\r\n        \"\"\"非节俭模式运行方法\"\"\"\r\n        ...\r\n\r\n    def _onScreencastFrame(self, **kwargs) -> None:\r\n        \"\"\"节俭模式运行方法\"\"\"\r\n        ...\r\n\r\n\r\nclass ScreencastMode(object):\r\n    _screencast: Screencast = ...\r\n\r\n    def __init__(self, screencast: Screencast):\r\n        \"\"\"\r\n        :param screencast: Screencast对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def video_mode(self) -> None:\r\n        \"\"\"持续视频模式，生成的视频没有声音\"\"\"\r\n        ...\r\n\r\n    def frugal_video_mode(self) -> None:\r\n        \"\"\"设置节俭视频模式，页面有变化时才录制，生成的视频没有声音\"\"\"\r\n        ...\r\n\r\n    def js_video_mode(self) -> None:\r\n        \"\"\"设置使用js录制视频模式，可生成有声音的视频，但需要手动启动\"\"\"\r\n        ...\r\n\r\n    def frugal_imgs_mode(self) -> None:\r\n        \"\"\"设置节俭视频模式，页面有变化时才截图\"\"\"\r\n        ...\r\n\r\n    def imgs_mode(self) -> None:\r\n        \"\"\"设置图片模式，持续对页面进行截图\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/scroller.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom time import sleep, perf_counter\r\n\r\n\r\nclass Scroller(object):\r\n    \"\"\"用于滚动的对象\"\"\"\r\n\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n        self._t1 = self._t2 = 'this'\r\n        self._wait_complete = False\r\n\r\n    def __call__(self, pixel=300):\r\n        return self.down(pixel)\r\n\r\n    def _run_js(self, js):\r\n        js = js.format(self._t1, self._t2, self._t2)\r\n        self._owner._run_js(js)\r\n        self._wait_scrolled()\r\n\r\n    def to_top(self):\r\n        self._run_js('{}.scrollTo({}.scrollLeft, 0);')\r\n        return self._owner\r\n\r\n    def to_bottom(self):\r\n        self._run_js('{}.scrollTo({}.scrollLeft, {}.scrollHeight);')\r\n        return self._owner\r\n\r\n    def to_half(self):\r\n        self._run_js('{}.scrollTo({}.scrollLeft, {}.scrollHeight/2);')\r\n        return self._owner\r\n\r\n    def to_rightmost(self):\r\n        self._run_js('{}.scrollTo({}.scrollWidth, {}.scrollTop);')\r\n        return self._owner\r\n\r\n    def to_leftmost(self):\r\n        self._run_js('{}.scrollTo(0, {}.scrollTop);')\r\n        return self._owner\r\n\r\n    def to_location(self, x, y):\r\n        self._run_js(f'{{}}.scrollTo({x}, {y});')\r\n        return self._owner\r\n\r\n    def up(self, pixel=300):\r\n        pixel = -pixel\r\n        self._run_js(f'{{}}.scrollBy(0, {pixel});')\r\n        return self._owner\r\n\r\n    def down(self, pixel=300):\r\n        self._run_js(f'{{}}.scrollBy(0, {pixel});')\r\n        return self._owner\r\n\r\n    def left(self, pixel=300):\r\n        pixel = -pixel\r\n        self._run_js(f'{{}}.scrollBy({pixel}, 0);')\r\n        return self._owner\r\n\r\n    def right(self, pixel=300):\r\n        self._run_js(f'{{}}.scrollBy({pixel}, 0);')\r\n        return self._owner\r\n\r\n    def _wait_scrolled(self):\r\n        if not self._wait_complete:\r\n            return\r\n\r\n        owner = self._owner.owner if self._owner._type == 'ChromiumElement' else self._owner\r\n        r = owner._run_cdp('Page.getLayoutMetrics')\r\n        x = r['layoutViewport']['pageX']\r\n        y = r['layoutViewport']['pageY']\r\n\r\n        end_time = perf_counter() + owner.timeout\r\n        while perf_counter() < end_time:\r\n            sleep(.02)\r\n            r = owner._run_cdp('Page.getLayoutMetrics')\r\n            x1 = r['layoutViewport']['pageX']\r\n            y1 = r['layoutViewport']['pageY']\r\n\r\n            if x == x1 and y == y1:\r\n                break\r\n\r\n            x = x1\r\n            y = y1\r\n\r\n\r\nclass ElementScroller(Scroller):\r\n    def to_see(self, center=None):\r\n        self._owner.owner.scroll.to_see(self._owner, center=center)\r\n        return self._owner\r\n\r\n    def to_center(self):\r\n        self._owner.owner.scroll.to_see(self._owner, center=True)\r\n        return self._owner\r\n\r\n\r\nclass PageScroller(Scroller):\r\n    def __init__(self, owner):\r\n        super().__init__(owner)\r\n        self._t1 = 'window'\r\n        self._t2 = 'document.documentElement'\r\n\r\n    def to_see(self, loc_or_ele, center=None):\r\n        ele = self._owner._ele(loc_or_ele)\r\n        self._to_see(ele, center)\r\n        return self._owner\r\n\r\n    def _to_see(self, ele, center):\r\n        txt = 'true' if center else 'false'\r\n        ele._run_js(f'this.scrollIntoViewIfNeeded({txt});')\r\n        if center or (center is not False and ele.states.is_covered):\r\n            ele._run_js('''function getWindowScrollTop() {let scroll_top = 0;\r\n                    if (document.documentElement && document.documentElement.scrollTop) {\r\n                      scroll_top = document.documentElement.scrollTop;\r\n                    } else if (document.body) {scroll_top = document.body.scrollTop;}\r\n                    return scroll_top;}\r\n            const { top, height } = this.getBoundingClientRect();\r\n                    const elCenter = top + height / 2;\r\n                    const center = window.innerHeight / 2;\r\n                    window.scrollTo({top: getWindowScrollTop() - (center - elCenter),\r\n                    behavior: 'instant'});''')\r\n        self._wait_scrolled()\r\n\r\n\r\nclass FrameScroller(PageScroller):\r\n    def __init__(self, owner):\r\n        \"\"\"\r\n        :param owner: ChromiumFrame对象\r\n        \"\"\"\r\n        super().__init__(owner.doc_ele)\r\n        self._t1 = self._t2 = 'this.documentElement'\r\n\r\n    def to_see(self, loc_or_ele, center=None):\r\n        ele = self._owner._ele(loc_or_ele)\r\n        self._to_see(ele, center)\r\n        return self._owner\r\n"
  },
  {
    "path": "DrissionPage/_units/scroller.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Union\r\n\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.chromium_frame import ChromiumFrame\r\nfrom .._pages.chromium_page import ChromiumPage\r\nfrom .._pages.chromium_tab import ChromiumTab\r\nfrom .._pages.mix_tab import MixTab\r\nfrom .._pages.web_page import WebPage\r\n\r\n\r\nclass Scroller(object):\r\n    _owner: Union[ChromiumBase, ChromiumElement] = ...\r\n    _t1: str = ...\r\n    _t2: str = ...\r\n    _wait_complete: bool = ...\r\n\r\n    def __init__(self, owner: Union[ChromiumBase, ChromiumElement]):\r\n        \"\"\"\r\n        :param owner: 元素对象或页面对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self, pixel: int = 300) -> None:\r\n        \"\"\"向下滚动若干像素，水平位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def to_top(self) -> None:\r\n        \"\"\"滚动到顶端，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_bottom(self) -> None:\r\n        \"\"\"滚动到底端，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_half(self) -> None:\r\n        \"\"\"滚动到垂直中间位置，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_rightmost(self) -> None:\r\n        \"\"\"滚动到最右边，垂直位置不变\"\"\"\r\n        ...\r\n\r\n    def to_leftmost(self) -> None:\r\n        \"\"\"滚动到最左边，垂直位置不变\"\"\"\r\n        ...\r\n\r\n    def to_location(self, x: int, y: int) -> None:\r\n        \"\"\"滚动到指定位置\r\n        :param x: 水平距离\r\n        :param y: 垂直距离\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def up(self, pixel: int = 300) -> None:\r\n        \"\"\"向上滚动若干像素，水平位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def down(self, pixel: int = 300) -> None:\r\n        \"\"\"向下滚动若干像素，水平位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def left(self, pixel: int = 300) -> None:\r\n        \"\"\"向左滚动若干像素，垂直位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def right(self, pixel: int = 300) -> None:\r\n        \"\"\"向右滚动若干像素，垂直位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _run_js(self, js: str): ...\r\n\r\n    def _wait_scrolled(self) -> None:\r\n        \"\"\"等待滚动结束\"\"\"\r\n        ...\r\n\r\n\r\nclass ElementScroller(Scroller):\r\n    _owner: ChromiumElement = ...\r\n\r\n    def __init__(self, owner: ChromiumElement):\r\n        \"\"\"\r\n        :param owner: 元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def to_see(self, center: Union[bool, None] = None) -> ChromiumElement:\r\n        \"\"\"滚动页面直到元素可见\r\n        :param center: 是否尽量滚动到页面正中，为None时如果被遮挡，则滚动到页面正中\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def to_center(self) -> ChromiumElement:\r\n        \"\"\"元素尽量滚动到视口中间\"\"\"\r\n        ...\r\n\r\n    def to_top(self) -> ChromiumElement:\r\n        \"\"\"滚动到顶端，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_bottom(self) -> ChromiumElement:\r\n        \"\"\"滚动到底端，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_half(self) -> ChromiumElement:\r\n        \"\"\"滚动到垂直中间位置，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_rightmost(self) -> ChromiumElement:\r\n        \"\"\"滚动到最右边，垂直位置不变\"\"\"\r\n        ...\r\n\r\n    def to_leftmost(self) -> ChromiumElement:\r\n        \"\"\"滚动到最左边，垂直位置不变\"\"\"\r\n        ...\r\n\r\n    def to_location(self, x: int, y: int) -> ChromiumElement:\r\n        \"\"\"滚动到指定位置\r\n        :param x: 水平距离\r\n        :param y: 垂直距离\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def up(self, pixel: int = 300) -> ChromiumElement:\r\n        \"\"\"向上滚动若干像素，水平位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def down(self, pixel: int = 300) -> ChromiumElement:\r\n        \"\"\"向下滚动若干像素，水平位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def left(self, pixel: int = 300) -> ChromiumElement:\r\n        \"\"\"向左滚动若干像素，垂直位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def right(self, pixel: int = 300) -> ChromiumElement:\r\n        \"\"\"向右滚动若干像素，垂直位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass PageScroller(Scroller):\r\n    _owner: Union[ChromiumBase, ChromiumElement] = ...\r\n\r\n    def __init__(self, owner: Union[ChromiumBase, ChromiumElement]):\r\n        \"\"\"\r\n        :param owner: 页面对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def to_see(self,\r\n               loc_or_ele: Union[str, tuple, ChromiumElement],\r\n               center: Union[bool, None] = None) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"滚动页面直到元素可见\r\n        :param loc_or_ele: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param center: 是否尽量滚动到页面正中，为None时如果被遮挡，则滚动到页面正中\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def to_top(self) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"滚动到顶端，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_bottom(self) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"滚动到底端，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_half(self) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"滚动到垂直中间位置，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_rightmost(self) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"滚动到最右边，垂直位置不变\"\"\"\r\n        ...\r\n\r\n    def to_leftmost(self) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"滚动到最左边，垂直位置不变\"\"\"\r\n        ...\r\n\r\n    def to_location(self, x: int, y: int) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"滚动到指定位置\r\n        :param x: 水平距离\r\n        :param y: 垂直距离\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def up(self, pixel: int = 300) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"向上滚动若干像素，水平位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def down(self, pixel: int = 300) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"向下滚动若干像素，水平位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def left(self, pixel: int = 300) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"向左滚动若干像素，垂直位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def right(self, pixel: int = 300) -> Union[ChromiumTab, MixTab, ChromiumPage, WebPage]:\r\n        \"\"\"向右滚动若干像素，垂直位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def _to_see(self, ele: ChromiumElement, center: Union[bool, None]) -> None:\r\n        \"\"\"执行滚动页面直到元素可见\r\n        :param ele: 元素对象\r\n        :param center: 是否尽量滚动到页面正中，为None时如果被遮挡，则滚动到页面正中\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass FrameScroller(PageScroller):\r\n    _owner: ChromiumElement = ...\r\n\r\n    def __init__(self, owner: ChromiumFrame): ...\r\n\r\n    def to_see(self,\r\n               loc_or_ele: Union[str, tuple, ChromiumElement],\r\n               center: Union[bool, None] = None) -> ChromiumFrame:\r\n        \"\"\"滚动页面直到元素可见\r\n        :param loc_or_ele: 元素的定位信息，可以是loc元组，或查询字符串\r\n        :param center: 是否尽量滚动到页面正中，为None时如果被遮挡，则滚动到页面正中\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def to_top(self) -> ChromiumFrame:\r\n        \"\"\"滚动到顶端，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_bottom(self) -> ChromiumFrame:\r\n        \"\"\"滚动到底端，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_half(self) -> ChromiumFrame:\r\n        \"\"\"滚动到垂直中间位置，水平位置不变\"\"\"\r\n        ...\r\n\r\n    def to_rightmost(self) -> ChromiumFrame:\r\n        \"\"\"滚动到最右边，垂直位置不变\"\"\"\r\n        ...\r\n\r\n    def to_leftmost(self) -> ChromiumFrame:\r\n        \"\"\"滚动到最左边，垂直位置不变\"\"\"\r\n        ...\r\n\r\n    def to_location(self, x: int, y: int) -> ChromiumFrame:\r\n        \"\"\"滚动到指定位置\r\n        :param x: 水平距离\r\n        :param y: 垂直距离\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def up(self, pixel: int = 300) -> ChromiumFrame:\r\n        \"\"\"向上滚动若干像素，水平位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def down(self, pixel: int = 300) -> ChromiumFrame:\r\n        \"\"\"向下滚动若干像素，水平位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def left(self, pixel: int = 300) -> ChromiumFrame:\r\n        \"\"\"向左滚动若干像素，垂直位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def right(self, pixel: int = 300) -> ChromiumFrame:\r\n        \"\"\"向右滚动若干像素，垂直位置不变\r\n        :param pixel: 滚动的像素\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/selector.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom time import perf_counter, sleep\n\nfrom .._functions.settings import Settings as _S\n\n\nclass SelectElement(object):\n    \"\"\"用于处理 select 标签\"\"\"\n\n    def __init__(self, ele):\n        if ele.tag != 'select':\n            raise TypeError(_S._lang.join(_S._lang.SELECT_ONLY))\n        self._ele = ele\n\n    def __call__(self, text_or_index, timeout=None):\n        para_type = 'index' if isinstance(text_or_index, int) else 'text'\n        if timeout is None:\n            timeout = self._ele.timeout\n        return self._select(text_or_index, para_type, timeout=timeout)\n\n    @property\n    def is_multi(self):\n        return self._ele.attr('multiple') is not None\n\n    @property\n    def options(self):\n        return [i for i in self._ele.eles('xpath://option') if not isinstance(i, int)]\n\n    @property\n    def selected_option(self):\n        return self._ele._run_js('return this.options[this.selectedIndex];')\n\n    @property\n    def selected_options(self):\n        return [x for x in self.options if x.states.is_selected]\n\n    def all(self):\n        if not self.is_multi:\n            raise TypeError(_S._lang.join(_S._lang.MULTI_SELECT_ONLY))\n        return self._by_loc('tag:option', 1, False)\n\n    def invert(self):\n        if not self.is_multi:\n            raise TypeError(_S._lang.join(_S._lang.MULTI_SELECT_ONLY))\n        change = False\n        for i in self.options:\n            change = True\n            mode = 'false' if i.states.is_selected else 'true'\n            i._run_js(f'this.selected={mode};')\n        if change:\n            self._dispatch_change()\n        return self._ele\n\n    def clear(self):\n        if not self.is_multi:\n            raise TypeError(_S._lang.join(_S._lang.MULTI_SELECT_ONLY))\n        return self._by_loc('tag:option', 1, True)\n\n    def by_text(self, text, timeout=None):\n        return self._select(text, 'text', False, timeout)\n\n    def by_value(self, value, timeout=None):\n        return self._select(value, 'value', False, timeout)\n\n    def by_index(self, index, timeout=None):\n        return self._select(index, 'index', False, timeout)\n\n    def by_locator(self, locator, timeout=None):\n        return self._by_loc(locator, timeout)\n\n    def by_option(self, option):\n        return self._select_options(option, 'true')\n\n    def cancel_by_text(self, text, timeout=None):\n        return self._select(text, 'text', True, timeout)\n\n    def cancel_by_value(self, value, timeout=None):\n        return self._select(value, 'value', True, timeout)\n\n    def cancel_by_index(self, index, timeout=None):\n        return self._select(index, 'index', True, timeout)\n\n    def cancel_by_locator(self, locator, timeout=None):\n        return self._by_loc(locator, timeout, True)\n\n    def cancel_by_option(self, option):\n        return self._select_options(option, 'false')\n\n    def _by_loc(self, loc, timeout=None, cancel=False):\n        eles = self._ele.eles(loc, timeout)\n        if not eles:\n            raise RuntimeError(_S._lang.join(_S._lang.OPTION_NOT_FOUND))\n\n        mode = 'false' if cancel else 'true'\n        if not self.is_multi:\n            eles = eles[0]\n        return self._select_options(eles, mode)\n\n    def _select(self, condition, para_type='text', cancel=False, timeout=None):\n        if not self.is_multi and isinstance(condition, (list, tuple)):\n            raise TypeError(_S._lang.join(_S._lang.STR_FOR_SINGLE_SELECT))\n\n        mode = 'false' if cancel else 'true'\n        if timeout is None:\n            timeout = self._ele.timeout\n        condition = set(condition) if isinstance(condition, (list, tuple)) else {condition}\n\n        if para_type in ('text', 'value'):\n            return self._text_value([str(i) for i in condition], para_type, mode, timeout)\n        elif para_type == 'index':\n            return self._index(condition, mode, timeout)\n\n    def _text_value(self, condition, para_type, mode, timeout):\n        text_len = len(condition)\n        eles = []\n        end_time = perf_counter() + timeout\n        while perf_counter() < end_time:\n            if para_type == 'text':\n                eles = [i for i in self.options if i.text in condition]\n            elif para_type == 'value':\n                eles = [i for i in self.options if i.attr('value') in condition]\n\n            if len(eles) >= text_len:\n                return self._select_options(eles, mode)\n            sleep(.01)\n\n        raise RuntimeError(_S._lang.join(_S._lang.OPTION_NOT_FOUND))\n\n    def _index(self, condition, mode, timeout):\n        condition = [int(i) for i in condition]\n        text_len = abs(max(condition, key=abs))\n        end_time = perf_counter() + timeout\n        while perf_counter() < end_time:\n            if len(self.options) >= text_len:\n                eles = self.options\n                eles = [eles[i - 1] if i > 0 else eles[i] for i in condition]\n                return self._select_options(eles, mode)\n            sleep(.01)\n        raise RuntimeError(_S._lang.join(_S._lang.OPTION_NOT_FOUND))\n\n    def _select_options(self, option, mode):\n        if isinstance(option, (list, tuple, set)):\n            if not self.is_multi and len(option) > 1:\n                option = option[:1]\n            for o in option:\n                o._run_js(f'this.selected={mode};')\n                self._dispatch_change()\n        else:\n            option._run_js(f'this.selected={mode};')\n            self._dispatch_change()\n        return self._ele\n\n    def _dispatch_change(self):\n        self._ele._run_js('this.dispatchEvent(new CustomEvent(\"change\", {bubbles: true}));')\n"
  },
  {
    "path": "DrissionPage/_units/selector.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Union, Tuple, List, Optional\r\n\r\nfrom .._elements.chromium_element import ChromiumElement\r\n\r\n\r\nclass SelectElement(object):\r\n    _ele: ChromiumElement = ...\r\n    def __init__(self, ele: ChromiumElement):\r\n        \"\"\"\r\n        :param ele: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self,\r\n                 text_or_index: Union[str, int, list, tuple],\r\n                 timeout: float = None) -> ChromiumElement:\r\n        \"\"\"选定下拉列表中子元素\r\n        :param text_or_index: 根据文本、值选或序号择选项，若允许多选，传入list或tuple可多选\r\n        :param timeout: 超时时间（秒），不输入默认实用页面超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_multi(self) -> bool:\r\n        \"\"\"返回是否多选表单\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def options(self) -> List[ChromiumElement]:\r\n        \"\"\"返回所有选项元素组成的列表\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def selected_option(self) -> Optional[ChromiumElement]:\r\n        \"\"\"返回第一个被选中的<option>元素\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def selected_options(self) -> List[ChromiumElement]:\r\n        \"\"\"返回所有被选中的<option>元素列表\"\"\"\r\n        ...\r\n\r\n    def all(self) -> ChromiumElement:\r\n        \"\"\"全选\"\"\"\r\n        ...\r\n\r\n    def invert(self) -> ChromiumElement:\r\n        \"\"\"反选\"\"\"\r\n        ...\r\n\r\n    def clear(self) -> ChromiumElement:\r\n        \"\"\"清除所有已选项\"\"\"\r\n        ...\r\n\r\n    def by_text(self,\r\n                text: Union[str, list, tuple],\r\n                timeout: float = None) -> ChromiumElement:\r\n        \"\"\"此方法用于根据text值选择项。当元素是多选列表时，可以接收list或tuple\r\n        :param text: text属性值，传入list或tuple可选择多项\r\n        :param timeout: 超时时间（秒），为None默认使用页面超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def by_value(self,\r\n                 value: Union[str, list, tuple],\r\n                 timeout: float = None) -> ChromiumElement:\r\n        \"\"\"此方法用于根据value值选择项。当元素是多选列表时，可以接收list或tuple\r\n        :param value: value属性值，传入list或tuple可选择多项\r\n        :param timeout: 超时时间，为None默认使用页面超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def by_index(self,\r\n                 index: Union[int, list, tuple],\r\n                 timeout: float = None) -> ChromiumElement:\r\n        \"\"\"此方法用于根据index值选择项。当元素是多选列表时，可以接收list或tuple\r\n        :param index: 序号，从1开始，可传入负数获取倒数第几个，传入list或tuple可选择多项\r\n        :param timeout: 超时时间，为None默认使用页面超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def by_locator(self,\r\n                   locator: Union[Tuple[str, str], str],\r\n                   timeout: float = None) -> ChromiumElement:\r\n        \"\"\"用定位符选择指定的项\r\n        :param locator: 定位符\r\n        :param timeout: 超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def by_option(self, \r\n                  option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]]) -> ChromiumElement:\r\n        \"\"\"选中单个或多个<option>元素\r\n        :param option: <option>元素或它们组成的列表\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def cancel_by_text(self,\r\n                       text: Union[str, list, tuple],\r\n                       timeout: float = None) -> ChromiumElement:\r\n        \"\"\"此方法用于根据text值取消选择项。当元素是多选列表时，可以接收list或tuple\r\n        :param text: 文本，传入list或tuple可取消多项\r\n        :param timeout: 超时时间，不输入默认实用页面超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def cancel_by_value(self,\r\n                        value: Union[str, list, tuple],\r\n                        timeout: float = None) -> ChromiumElement:\r\n        \"\"\"此方法用于根据value值取消选择项。当元素是多选列表时，可以接收list或tuple\r\n        :param value: value属性值，传入list或tuple可取消多项\r\n        :param timeout: 超时时间，不输入默认实用页面超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def cancel_by_index(self,\r\n                        index: Union[int, list, tuple],\r\n                        timeout: float = None) -> ChromiumElement:\r\n        \"\"\"此方法用于根据index值取消选择项。当元素是多选列表时，可以接收list或tuple\r\n        :param index: 序号，从1开始，可传入负数获取倒数第几个，传入list或tuple可取消多项\r\n        :param timeout: 超时时间，不输入默认实用页面超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def cancel_by_locator(self,\r\n                          locator: Union[Tuple[str, str], str],\r\n                          timeout: float = None) -> ChromiumElement:\r\n        \"\"\"用定位符取消选择指定的项\r\n        :param locator: 定位符\r\n        :param timeout: 超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def cancel_by_option(self,\r\n                         option: Union[ChromiumElement, List[ChromiumElement], \r\n                                 Tuple[ChromiumElement]]) -> ChromiumElement:\r\n        \"\"\"取消选中单个或多个<option>元素\r\n        :param option: <option>元素或它们组成的列表\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _by_loc(self,\r\n                loc: Union[str, Tuple[str, str]],\r\n                timeout: float = None,\r\n                cancel: bool = False) -> ChromiumElement:\r\n        \"\"\"用定位符取消选择指定的项\r\n        :param loc: 定位符\r\n        :param timeout: 超时时间\r\n        :param cancel: 是否取消选择\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _select(self,\r\n                condition: Union[str, int, list, tuple] = None,\r\n                para_type: str = 'text',\r\n                cancel: bool = False,\r\n                timeout: float = None) -> ChromiumElement:\r\n        \"\"\"选定或取消选定下拉列表中子元素\r\n        :param condition: 根据文本、值选或序号择选项，若允许多选，传入list或tuple可多选\r\n        :param para_type: 参数类型，可选 'text'、'value'、'index'\r\n        :param cancel: 是否取消选择\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _text_value(self,\r\n                    condition: Union[list, set],\r\n                    para_type: str,\r\n                    mode: str,\r\n                    timeout: float) -> ChromiumElement:\r\n        \"\"\"执行text和value搜索\r\n        :param condition: 条件set\r\n        :param para_type: 参数类型，可选 'text'、'value'\r\n        :param mode: 'true' 或 'false'\r\n        :param timeout: 超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _index(self, condition: set, mode: str, timeout: float) -> ChromiumElement:\r\n        \"\"\"执行index搜索\r\n        :param condition: 条件set\r\n        :param mode: 'true' 或 'false'\r\n        :param timeout: 超时时间\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _select_options(self, \r\n                        option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]],\r\n                        mode: str) -> ChromiumElement:\r\n        \"\"\"选中或取消某个选项\r\n        :param option: options元素对象\r\n        :param mode: 选中还是取消\r\n        :return: <select>元素对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def _dispatch_change(self) -> None:\r\n        \"\"\"触发修改动作\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/setter.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom time import sleep\r\n\r\nfrom requests.structures import CaseInsensitiveDict\r\n\r\nfrom .cookies_setter import (SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter, BrowserCookiesSetter,\r\n                             MixTabCookiesSetter)\r\nfrom .._functions.settings import Settings as _S\r\nfrom .._functions.tools import show_or_hide_browser\r\nfrom .._functions.web import format_headers\r\nfrom ..errors import ElementLostError, JavaScriptError\r\n\r\n\r\nclass BaseSetter(object):\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n\r\n    def NoneElement_value(self, value=None, on_off=True):\r\n        self._owner._none_ele_return_value = on_off\r\n        self._owner._none_ele_value = value\r\n\r\n    def retry_times(self, times):\r\n        self._owner.retry_times = times\r\n\r\n    def retry_interval(self, interval):\r\n        self._owner.retry_interval = interval\r\n\r\n    def download_path(self, path):\r\n        if path is None:\r\n            path = '.'\r\n        self._owner._download_path = str(Path(path).absolute())\r\n\r\n\r\nclass SessionPageSetter(BaseSetter):\r\n    def __init__(self, owner):\r\n        super().__init__(owner)\r\n        self._cookies_setter = None\r\n\r\n    @property\r\n    def cookies(self):\r\n        if self._cookies_setter is None:\r\n            self._cookies_setter = SessionCookiesSetter(self._owner)\r\n        return self._cookies_setter\r\n\r\n    def download_path(self, path):\r\n        super().download_path(path)\r\n        if self._owner._downloader:\r\n            self._owner._downloader.set.save_path(self._owner._download_path)\r\n\r\n    def timeout(self, second):\r\n        self._owner._timeout = second\r\n\r\n    def encoding(self, encoding, set_all=True):\r\n        if set_all:\r\n            self._owner._encoding = encoding if encoding else None\r\n        if self._owner.response:\r\n            self._owner.response.encoding = encoding\r\n\r\n    def headers(self, headers):\r\n        self._owner._headers = CaseInsensitiveDict(format_headers(headers))\r\n\r\n    def header(self, name, value):\r\n        self._owner._headers[name] = value\r\n\r\n    def user_agent(self, ua):\r\n        self._owner._headers['user-agent'] = ua\r\n\r\n    def proxies(self, http=None, https=None):\r\n        self._owner.session.proxies = {'http': http, 'https': https}\r\n\r\n    def auth(self, auth):\r\n        self._owner.session.auth = auth\r\n\r\n    def hooks(self, hooks):\r\n        self._owner.session.hooks = hooks\r\n\r\n    def params(self, params):\r\n        self._owner.session.params = params\r\n\r\n    def verify(self, on_off):\r\n        self._owner.session.verify = on_off\r\n\r\n    def cert(self, cert):\r\n        self._owner.session.cert = cert\r\n\r\n    def stream(self, on_off):\r\n        self._owner.session.stream = on_off\r\n\r\n    def trust_env(self, on_off):\r\n        self._owner.session.trust_env = on_off\r\n\r\n    def max_redirects(self, times):\r\n        self._owner.session.max_redirects = times\r\n\r\n    def add_adapter(self, url, adapter):\r\n        self._owner.session.mount(url, adapter)\r\n\r\n\r\nclass BrowserBaseSetter(BaseSetter):\r\n    def __init__(self, owner):\r\n        super().__init__(owner)\r\n        self._cookies_setter = None\r\n\r\n    @property\r\n    def load_mode(self):\r\n        return LoadMode(self._owner)\r\n\r\n    def timeouts(self, base=None, page_load=None, script=None):\r\n        if base is not None:\r\n            self._owner.timeouts.base = base\r\n\r\n        if page_load is not None:\r\n            self._owner.timeouts.page_load = page_load\r\n\r\n        if script is not None:\r\n            self._owner.timeouts.script = script\r\n\r\n\r\nclass BrowserSetter(BrowserBaseSetter):\r\n\r\n    @property\r\n    def cookies(self):\r\n        if self._cookies_setter is None:\r\n            self._cookies_setter = BrowserCookiesSetter(self._owner)\r\n        return self._cookies_setter\r\n\r\n    def auto_handle_alert(self, on_off=True, accept=True):\r\n        self._owner._auto_handle_alert = None if on_off is None else accept if on_off else 'close'\r\n\r\n    def download_path(self, path):\r\n        super().download_path(path)\r\n        self._owner._dl_mgr.set_path('browser', self._owner._download_path)\r\n\r\n    def download_file_name(self, name=None, suffix=None):\r\n        self._owner._dl_mgr.set_rename('browser', name, suffix)\r\n\r\n    def when_download_file_exists(self, mode):\r\n        types = {'rename': 'rename', 'overwrite': 'overwrite', 'skip': 'skip', 'r': 'rename', 'o': 'overwrite',\r\n                 's': 'skip'}\r\n        mode = types.get(mode, mode)\r\n        if mode not in types:\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'mode',\r\n                                           ALLOW_VAL=\"', '\".join(types.keys()), CURR_VAL=mode))\r\n        self._owner._dl_mgr.set_file_exists('browser', mode)\r\n\r\n\r\nclass ChromiumBaseSetter(BrowserBaseSetter):\r\n\r\n    @property\r\n    def scroll(self):\r\n        return PageScrollSetter(self._owner.scroll)\r\n\r\n    @property\r\n    def cookies(self):\r\n        if self._cookies_setter is None:\r\n            self._cookies_setter = CookiesSetter(self._owner)\r\n        return self._cookies_setter\r\n\r\n    def headers(self, headers):\r\n        self._owner._run_cdp('Network.enable')\r\n        self._owner._run_cdp('Network.setExtraHTTPHeaders', headers=format_headers(headers))\r\n\r\n    def user_agent(self, ua, platform=None):\r\n        keys = {'userAgent': ua}\r\n        if platform:\r\n            keys['platform'] = platform\r\n        self._owner._run_cdp('Emulation.setUserAgentOverride', **keys)\r\n\r\n    def session_storage(self, item, value):\r\n        self._owner._run_cdp_loaded('DOMStorage.enable')\r\n        i = self._owner._run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']\r\n        if value is False:\r\n            self._owner._run_cdp('DOMStorage.removeDOMStorageItem',\r\n                                 storageId={'storageKey': i, 'isLocalStorage': False}, key=item)\r\n        else:\r\n            self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False},\r\n                                 key=item, value=value)\r\n        self._owner._run_cdp_loaded('DOMStorage.disable')\r\n\r\n    def local_storage(self, item, value):\r\n        self._owner._run_cdp_loaded('DOMStorage.enable')\r\n        i = self._owner._run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']\r\n        if value is False:\r\n            self._owner._run_cdp('DOMStorage.removeDOMStorageItem',\r\n                                 storageId={'storageKey': i, 'isLocalStorage': True}, key=item)\r\n        else:\r\n            self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True},\r\n                                 key=item, value=value)\r\n        self._owner._run_cdp_loaded('DOMStorage.disable')\r\n\r\n    def upload_files(self, files):\r\n        if not self._owner._upload_list:\r\n            self._owner.driver.set_callback('Page.fileChooserOpened', self._owner._onFileChooserOpened)\r\n            self._owner._run_cdp('Page.setInterceptFileChooserDialog', enabled=True)\r\n\r\n        if isinstance(files, str):\r\n            files = files.split('\\n')\r\n        elif isinstance(files, Path):\r\n            files = (files,)\r\n        self._owner._upload_list = [str(Path(i).absolute()) for i in files]\r\n\r\n    def auto_handle_alert(self, on_off=True, accept=True):\r\n        self._owner._alert.auto = None if on_off is None else accept if on_off else 'close'\r\n\r\n    def blocked_urls(self, urls):\r\n        if not urls:\r\n            urls = []\r\n        elif isinstance(urls, str):\r\n            urls = (urls,)\r\n        if not isinstance(urls, (list, tuple)):\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'urls',\r\n                                           ALLOW_TYPE='str, list, tuple', CURR_VAL=urls))\r\n        self._owner._run_cdp('Network.enable')\r\n        self._owner._run_cdp('Network.setBlockedURLs', urls=urls)\r\n\r\n\r\nclass TabSetter(ChromiumBaseSetter):\r\n    def __init__(self, owner):\r\n        super().__init__(owner)\r\n\r\n    @property\r\n    def window(self):\r\n        return WindowSetter(self._owner)\r\n\r\n    def download_path(self, path):\r\n        super().download_path(path)\r\n        self._owner.browser._dl_mgr.set_path(self._owner, self._owner._download_path)\r\n        if self._owner._downloader:\r\n            self._owner._downloader.set.save_path(self._owner._download_path)\r\n\r\n    def download_file_name(self, name=None, suffix=None):\r\n        self._owner.browser._dl_mgr.set_rename(self._owner.tab_id, name, suffix)\r\n\r\n    def when_download_file_exists(self, mode):\r\n        types = {'rename': 'rename', 'overwrite': 'overwrite', 'skip': 'skip',\r\n                 'r': 'rename', 'o': 'overwrite', 's': 'skip'}\r\n        mode = types.get(mode, mode)\r\n        if mode not in types:\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'mode',\r\n                                           ALLOW_VAL=\"', '\".join(types.keys()), CURR_VAL=mode))\r\n        self._owner.browser._dl_mgr.set_file_exists(self._owner.tab_id, mode)\r\n\r\n    def activate(self):\r\n        self._owner.browser.activate_tab(self._owner.tab_id)\r\n\r\n\r\nclass ChromiumPageSetter(TabSetter):\r\n\r\n    def NoneElement_value(self, value=None, on_off=True):\r\n        super().NoneElement_value(value, on_off)\r\n        self._owner.browser._none_ele_return_value = on_off\r\n        self._owner.browser._none_ele_value = value\r\n\r\n    def retry_times(self, times):\r\n        super().retry_times(times)\r\n        self._owner.browser.retry_times = times\r\n\r\n    def retry_interval(self, interval):\r\n        super().retry_interval(interval)\r\n        self._owner.browser.retry_interval = interval\r\n\r\n    def download_path(self, path):\r\n        if path is None:\r\n            path = '.'\r\n        self._owner._download_path = str(Path(path).absolute())\r\n        self._owner.browser.set.download_path(path)\r\n        if self._owner._downloader:\r\n            self._owner._downloader.set.save_path(path)\r\n\r\n    def download_file_name(self, name=None, suffix=None):\r\n        self._owner.browser.set.download_file_name(name, suffix)\r\n\r\n    def when_download_file_exists(self, mode):\r\n        self._owner.browser.set.when_download_file_exists(mode)\r\n\r\n\r\nclass WebPageSetter(ChromiumPageSetter):\r\n    def __init__(self, owner):\r\n        super().__init__(owner)\r\n        self._session_setter = SessionPageSetter(self._owner)\r\n        self._chromium_setter = ChromiumPageSetter(self._owner)\r\n\r\n    @property\r\n    def cookies(self):\r\n        if self._cookies_setter is None:\r\n            self._cookies_setter = WebPageCookiesSetter(self._owner)\r\n        return self._cookies_setter\r\n\r\n    def headers(self, headers):\r\n        if self._owner.mode == 's':\r\n            self._session_setter.headers(headers)\r\n        else:\r\n            self._chromium_setter.headers(headers)\r\n\r\n    def user_agent(self, ua, platform=None):\r\n        if self._owner.mode == 's':\r\n            self._session_setter.user_agent(ua)\r\n        else:\r\n            self._chromium_setter.user_agent(ua, platform)\r\n\r\n\r\nclass MixTabSetter(TabSetter):\r\n    def __init__(self, owner):\r\n        super().__init__(owner)\r\n        self._session_setter = SessionPageSetter(self._owner)\r\n        self._chromium_setter = ChromiumBaseSetter(self._owner)\r\n\r\n    @property\r\n    def cookies(self):\r\n        if self._cookies_setter is None:\r\n            self._cookies_setter = MixTabCookiesSetter(self._owner)\r\n        return self._cookies_setter\r\n\r\n    def headers(self, headers):\r\n        if self._owner._session:\r\n            self._session_setter.headers(headers)\r\n        if self._owner._driver and self._owner._driver.is_running:\r\n            self._chromium_setter.headers(headers)\r\n\r\n    def user_agent(self, ua, platform=None):\r\n        if self._owner._session:\r\n            self._session_setter.user_agent(ua)\r\n        if self._owner._driver and self._owner._driver.is_running:\r\n            self._chromium_setter.user_agent(ua, platform)\r\n\r\n    def timeouts(self, base=None, page_load=None, script=None):\r\n        super().timeouts(base=base, page_load=page_load, script=script)\r\n        if base is not None:\r\n            self._owner._timeout = base\r\n\r\n\r\nclass ChromiumElementSetter(object):\r\n    def __init__(self, ele):\r\n        self._ele = ele\r\n\r\n    def attr(self, name, value=''):\r\n        try:\r\n            self._ele.owner._run_cdp('DOM.setAttributeValue',\r\n                                     nodeId=self._ele._node_id, name=name, value=str(value))\r\n        except ElementLostError:\r\n            self._ele._refresh_id()\r\n            self._ele.owner._run_cdp('DOM.setAttributeValue',\r\n                                     nodeId=self._ele._node_id, name=name, value=str(value))\r\n\r\n    def property(self, name, value):\r\n        value = value.replace('\"', r'\\\"')\r\n        self._ele._run_js(f'this.{name}=\"{value}\";')\r\n\r\n    def style(self, name, value):\r\n        try:\r\n            self._ele._run_js(f'this.style.{name}=\"{value}\";')\r\n        except JavaScriptError:\r\n            raise RuntimeError(_S._lang.join(_S._lang.SET_FAILED_, name, VALUE=value))\r\n\r\n    def innerHTML(self, html):\r\n        self.property('innerHTML', html)\r\n\r\n    def value(self, value):\r\n        self.property('value', value)\r\n\r\n\r\nclass ChromiumFrameSetter(ChromiumBaseSetter):\r\n    def attr(self, name, value):\r\n        self._owner.frame_ele.set.attr(name, value)\r\n\r\n    def property(self, name, value):\r\n        self._owner.frame_ele.set.property(name=name, value=value)\r\n\r\n    def style(self, name, value):\r\n        self._owner.frame_ele.set.style(name=name, value=value)\r\n\r\n\r\nclass LoadMode(object):\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n\r\n    def __call__(self, value):\r\n        if value.lower() not in ('normal', 'eager', 'none'):\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_VAL_, 'value',\r\n                                           ALLOW_VAL=\"'normal', 'eager', 'none'\", CURR_VAL=value))\r\n        self._owner._load_mode = value\r\n        if self._owner._type in ('ChromiumPage', 'WebPage'):\r\n            self._owner.browser._load_mode = value\r\n\r\n    def normal(self):\r\n        self.__call__('normal')\r\n\r\n    def eager(self):\r\n        self.__call__('eager')\r\n\r\n    def none(self):\r\n        self.__call__('none')\r\n\r\n\r\nclass PageScrollSetter(object):\r\n    def __init__(self, scroll):\r\n        self._scroll = scroll\r\n\r\n    def wait_complete(self, on_off=True):\r\n        if not isinstance(on_off, bool):\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'on_off',\r\n                                           ALLOW_TYPE='bool', CURR_TYPE=type(on_off)))\r\n        self._scroll._wait_complete = on_off\r\n\r\n    def smooth(self, on_off=True):\r\n        if not isinstance(on_off, bool):\r\n            raise ValueError(_S._lang.join(_S._lang.INCORRECT_TYPE_, 'on_off',\r\n                                           ALLOW_TYPE='bool', CURR_TYPE=type(on_off)))\r\n        b = 'smooth' if on_off else 'auto'\r\n        self._scroll._owner._run_js(f'document.documentElement.style.setProperty(\"scroll-behavior\",\"{b}\");')\r\n        self._scroll._wait_complete = on_off\r\n\r\n\r\nclass WindowSetter(object):\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n        self._window_id = self._get_info()['windowId']\r\n\r\n    def max(self):\r\n        s = self._get_info()['bounds']['windowState']\r\n        if s in ('fullscreen', 'minimized'):\r\n            self._perform({'windowState': 'normal'})\r\n        self._perform({'windowState': 'maximized'})\r\n\r\n    def mini(self):\r\n        s = self._get_info()['bounds']['windowState']\r\n        if s == 'fullscreen':\r\n            self._perform({'windowState': 'normal'})\r\n        self._perform({'windowState': 'minimized'})\r\n\r\n    def full(self):\r\n        s = self._get_info()['bounds']['windowState']\r\n        if s == 'minimized':\r\n            self._perform({'windowState': 'normal'})\r\n        self._perform({'windowState': 'fullscreen'})\r\n\r\n    def normal(self):\r\n        s = self._get_info()['bounds']['windowState']\r\n        if s == 'fullscreen':\r\n            self._perform({'windowState': 'normal'})\r\n        self._perform({'windowState': 'normal'})\r\n\r\n    def size(self, width=None, height=None):\r\n        if width or height:\r\n            s = self._get_info()['bounds']['windowState']\r\n            if s != 'normal':\r\n                self._perform({'windowState': 'normal'})\r\n            info = self._get_info()['bounds']\r\n            width = width - 16 if width else info['width']\r\n            height = height + 7 if height else info['height']\r\n            self._perform({'width': width, 'height': height})\r\n\r\n    def location(self, x=None, y=None):\r\n        if x is not None or y is not None:\r\n            self.normal()\r\n            info = self._get_info()['bounds']\r\n            x = x if x is not None else info['left']\r\n            y = y if y is not None else info['top']\r\n            self._perform({'left': x - 8, 'top': y})\r\n\r\n    def hide(self):\r\n        show_or_hide_browser(self._owner, hide=True)\r\n\r\n    def show(self):\r\n        show_or_hide_browser(self._owner, hide=False)\r\n\r\n    def _get_info(self):\r\n        for _ in range(50):\r\n            try:\r\n                return self._owner._run_cdp('Browser.getWindowForTarget')\r\n            except:\r\n                sleep(.02)\r\n        raise RuntimeError(_S._lang.join(_S._lang.GET_WINDOW_SIZE_FAILED))\r\n\r\n    def _perform(self, bounds):\r\n        try:\r\n            self._owner._run_cdp('Browser.setWindowBounds', windowId=self._window_id, bounds=bounds)\r\n        except:\r\n            raise RuntimeError(_S._lang.join(TIP=_S._lang.SET_WINDOW_NORMAL))\r\n"
  },
  {
    "path": "DrissionPage/_units/setter.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom pathlib import Path\r\nfrom typing import Union, Tuple, Literal, Any, Optional\r\n\r\nfrom requests.adapters import HTTPAdapter\r\nfrom requests.auth import HTTPBasicAuth\r\n\r\nfrom .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter, BrowserCookiesSetter\r\nfrom .scroller import PageScroller\r\nfrom .._base.base import BasePage\r\nfrom .._base.chromium import Chromium\r\nfrom .._elements.chromium_element import ChromiumElement\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.chromium_frame import ChromiumFrame\r\nfrom .._pages.chromium_page import ChromiumPage\r\nfrom .._pages.chromium_tab import ChromiumTab\r\nfrom .._pages.mix_tab import MixTab\r\nfrom .._pages.session_page import SessionPage\r\nfrom .._pages.web_page import WebPage\r\n\r\nFILE_EXISTS = Literal['skip', 'rename', 'overwrite', 's', 'r', 'o']\r\n\r\n\r\nclass BaseSetter(object):\r\n    _owner: Union[Chromium, BasePage] = ...\r\n\r\n    def __init__(self, owner: Union[Chromium, BasePage]):\r\n        \"\"\"\r\n        :param owner: BasePage对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def NoneElement_value(self,\r\n                          value: Any = None,\r\n                          on_off: bool = True) -> None:\r\n        \"\"\"设置空元素是否返回设定值\r\n        :param value: 返回的设定值\r\n        :param on_off: 是否启用\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def retry_times(self, times: int) -> None:\r\n        \"\"\"设置连接失败重连次数\r\n        :param times: 重试次数\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def retry_interval(self, interval: float) -> None:\r\n        \"\"\"设置连接失败重连间隔（秒）\r\n        :param interval: 重试间隔\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def download_path(self, path: Union[str, Path, None]) -> None:\r\n        \"\"\"设置下载路径\r\n        :param path: 下载路径\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass SessionPageSetter(BaseSetter):\r\n    _owner: SessionPage = ...\r\n    _cookies_setter: Optional[SessionCookiesSetter] = ...\r\n\r\n    def __init__(self, owner: SessionPage):\r\n        \"\"\"\r\n        :param owner: SessionPage对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def cookies(self) -> SessionCookiesSetter:\r\n        \"\"\"返回用于设置cookies的对象\"\"\"\r\n        ...\r\n\r\n    def download_path(self, path: Union[str, Path, None]) -> None:\r\n        \"\"\"设置下载路径\r\n        :param path: 下载路径\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def timeout(self, second: float) -> None:\r\n        \"\"\"设置连接超时时间\r\n        :param second: 秒数\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def encoding(self, encoding: Union[str, None], set_all: bool = True) -> None:\r\n        \"\"\"设置编码\r\n        :param encoding: 编码名称，如果要取消之前的设置，传入None\r\n        :param set_all: 是否设置对象参数，为False则只设置当前Response\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def headers(self, headers: Union[str, dict]) -> None:\r\n        \"\"\"设置通用的headers\r\n        :param headers: dict形式的headers\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def header(self, name: str, value: str) -> None:\r\n        \"\"\"设置headers中一个项\r\n        :param name: 设置名称\r\n        :param value: 设置值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def user_agent(self, ua: str) -> None:\r\n        \"\"\"设置user agent\r\n        :param ua: user agent\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def proxies(self, http: str = None, https: str = None) -> None:\r\n        \"\"\"设置proxies参数\r\n        :param http: http代理地址\r\n        :param https: https代理地址\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def auth(self, auth: Union[Tuple[str, str], HTTPBasicAuth, None]) -> None:\r\n        \"\"\"设置认证元组或对象\r\n        :param auth: 认证元组或对象\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def hooks(self, hooks: Union[dict, None]) -> None:\r\n        \"\"\"设置回调方法\r\n        :param hooks: 回调方法\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def params(self, params: Union[dict, None]) -> None:\r\n        \"\"\"设置查询参数字典\r\n        :param params: 查询参数字典\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def verify(self, on_off: Union[bool, None]) -> None:\r\n        \"\"\"设置是否验证SSL证书\r\n        :param on_off: 是否验证 SSL 证书\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def cert(self, cert: Union[str, Tuple[str, str], None]) -> None:\r\n        \"\"\"SSL客户端证书文件的路径(.pem格式)，或('cert', 'key')元组\r\n        :param cert: 证书路径或元组\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def stream(self, on_off: Union[bool, None]) -> None:\r\n        \"\"\"设置是否使用流式响应内容\r\n        :param on_off: 是否使用流式响应内容\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def trust_env(self, on_off: Union[bool, None]) -> None:\r\n        \"\"\"设置是否信任环境\r\n        :param on_off: 是否信任环境\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def max_redirects(self, times: Union[int, None]) -> None:\r\n        \"\"\"设置最大重定向次数\r\n        :param times: 最大重定向次数\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def add_adapter(self, url: str, adapter: HTTPAdapter) -> None:\r\n        \"\"\"添加适配器\r\n        :param url: 适配器对应url\r\n        :param adapter: 适配器对象\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass BrowserBaseSetter(BaseSetter):\r\n    \"\"\"Browser和ChromiumBase设置\"\"\"\r\n    _cookies_setter: Optional[CookiesSetter] = ...\r\n\r\n    def __init__(self, owner: ChromiumBase):\r\n        \"\"\"\r\n        :param owner: ChromiumBase对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def load_mode(self) -> LoadMode:\r\n        \"\"\"返回用于设置页面加载模式的对象\"\"\"\r\n        ...\r\n\r\n    def timeouts(self,\r\n                 base=None,\r\n                 page_load=None,\r\n                 script=None) -> None:\r\n        \"\"\"设置超时时间，单位为秒\r\n        :param base: 基本等待时间，除页面加载和脚本超时，其它等待默认使用\r\n        :param page_load: 页面加载超时时间\r\n        :param script: 脚本运行超时时间\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass BrowserSetter(BrowserBaseSetter):\r\n    _owner: Chromium = ...\r\n    _cookies_setter: BrowserCookiesSetter = ...\r\n\r\n    def __init__(self, owner: Chromium):\r\n        \"\"\"\r\n        :param owner: Chromium对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def cookies(self) -> BrowserCookiesSetter:\r\n        \"\"\"返回用于设置cookies的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def window(self)->WindowSetter:...\r\n\r\n    def auto_handle_alert(self,\r\n                          on_off: bool = True,\r\n                          accept: bool = True) -> None:\r\n        \"\"\"设置本浏览器是否启用自动处理弹窗\r\n        :param on_off: bool表示开或关，传入None表示使用Settings设置\r\n        :param accept: bool表示确定还是取消\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def download_path(self, path: Union[Path, str, None]) -> None:\r\n        \"\"\"设置下载路径\r\n        :param path: 下载路径\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def download_file_name(self,\r\n                           name: str = None,\r\n                           suffix: str = None) -> None:\r\n        \"\"\"设置下一个被下载文件的名称\r\n        :param name: 文件名，可不含后缀，会自动使用远程文件后缀\r\n        :param suffix: 后缀名，显式设置后缀名，不使用远程文件后缀\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def when_download_file_exists(self, mode: FILE_EXISTS) -> None:\r\n        \"\"\"设置当存在同名文件时的处理方式\r\n        :param mode: 可在 'rename', 'overwrite', 'skip', 'r', 'o', 's'中选择\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass ChromiumBaseSetter(BrowserBaseSetter):\r\n    _owner: ChromiumBase = ...\r\n    _cookies_setter: CookiesSetter = ...\r\n\r\n    def __init__(self, owner): ...\r\n\r\n    @property\r\n    def scroll(self) -> PageScrollSetter:\r\n        \"\"\"返回用于设置页面滚动设置的对象\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def cookies(self) -> CookiesSetter:\r\n        \"\"\"返回用于设置cookies的对象\"\"\"\r\n        ...\r\n\r\n    def headers(self, headers: Union[dict, str]) -> None:\r\n        \"\"\"设置固定发送的headers\r\n        :param headers: dict格式的headers数据，或从浏览器复制的headers文本（\\n分行）\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def user_agent(self, ua: str, platform: str = None) -> None:\r\n        \"\"\"为当前tab设置user agent，只在当前tab有效\r\n        :param ua: user agent字符串\r\n        :param platform: platform字符串\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def session_storage(self, item: str, value: Union[str, bool]) -> None:\r\n        \"\"\"设置或删除某项sessionStorage信息\r\n        :param item: 要设置的项\r\n        :param value: 项的值，设置为False时，删除该项\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def local_storage(self, item: str, value: Union[str, bool]) -> None:\r\n        \"\"\"设置或删除某项localStorage信息\r\n        :param item: 要设置的项\r\n        :param value: 项的值，设置为False时，删除该项\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def upload_files(self, files: Union[str, Path, list, tuple]) -> None:\r\n        \"\"\"等待上传的文件路径\r\n        :param files: 文件路径列表或字符串，字符串时多个文件用回车分隔\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def auto_handle_alert(self,\r\n                          on_off: bool = True,\r\n                          accept: bool = True) -> None:\r\n        \"\"\"设置是否启用自动处理弹窗\r\n        :param on_off: bool表示开或关\r\n        :param accept: bool表示确定还是取消\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def blocked_urls(self, urls: Union[list, tuple, str, None]) -> None:\r\n        \"\"\"设置要忽略的url\r\n        :param urls: 要忽略的url，可用*通配符，可输入多个，传入None时清空已设置的内容\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass TabSetter(ChromiumBaseSetter):\r\n    _owner: ChromiumTab = ...\r\n\r\n    def __init__(self, owner: ChromiumTab):\r\n        \"\"\"\r\n        :param owner: 标签页对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def window(self) -> WindowSetter:\r\n        \"\"\"返回用于设置浏览器窗口的对象\"\"\"\r\n        ...\r\n\r\n    def download_path(self, path: Union[str, Path, None]) -> None:\r\n        \"\"\"设置下载路径\r\n        :param path: 下载路径\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def download_file_name(self,\r\n                           name: str = None,\r\n                           suffix: str = None) -> None:\r\n        \"\"\"设置下一个被下载文件的名称\r\n        :param name: 文件名，可不含后缀，会自动使用远程文件后缀\r\n        :param suffix: 后缀名，显式设置后缀名，不使用远程文件后缀\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def when_download_file_exists(self, mode: FILE_EXISTS) -> None:\r\n        \"\"\"设置当存在同名文件时的处理方式\r\n        :param mode: 可在 'rename', 'overwrite', 'skip', 'r', 'o', 's'中选择\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def activate(self) -> None:\r\n        \"\"\"使标签页处于最前面\"\"\"\r\n        ...\r\n\r\n\r\nclass ChromiumPageSetter(TabSetter):\r\n    _owner: ChromiumPage = ...\r\n\r\n    def __init__(self, owner: ChromiumPage):\r\n        \"\"\"\r\n        :param owner: ChromiumPage对象\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass WebPageSetter(ChromiumPageSetter):\r\n    _owner: WebPage = ...\r\n    _session_setter: SessionPageSetter = ...\r\n    _chromium_setter: ChromiumPageSetter = ...\r\n\r\n    def __init__(self, owner: WebPage):\r\n        \"\"\"\r\n        :param owner: WebPage对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def cookies(self) -> WebPageCookiesSetter:\r\n        \"\"\"返回用于设置cookies的对象\"\"\"\r\n        ...\r\n\r\n\r\nclass MixTabSetter(TabSetter):\r\n    _owner: MixTab = ...\r\n    _session_setter: SessionPageSetter = ...\r\n    _chromium_setter: ChromiumBaseSetter = ...\r\n\r\n    def __init__(self, owner: MixTab):\r\n        \"\"\"\r\n        :param owner: MixTab对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def cookies(self) -> WebPageCookiesSetter:\r\n        \"\"\"返回用于设置cookies的对象\"\"\"\r\n        ...\r\n\r\n    def timeouts(self,\r\n                 base: float = None,\r\n                 page_load: float = None,\r\n                 script: float = None) -> None:\r\n        \"\"\"设置超时时间，单位为秒\r\n        :param base: 基本等待时间，除页面加载和脚本超时，其它等待默认使用\r\n        :param page_load: 页面加载超时时间\r\n        :param script: 脚本运行超时时间\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass ChromiumElementSetter(object):\r\n    _ele: ChromiumElement = ...\r\n\r\n    def __init__(self, ele: ChromiumElement):\r\n        \"\"\"\r\n        :param ele: ChromiumElement\r\n        \"\"\"\r\n        ...\r\n\r\n    def attr(self, name: str, value: str = '') -> None:\r\n        \"\"\"设置元素attribute属性\r\n        :param name: 属性名\r\n        :param value: 属性值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def property(self, name: str, value: str) -> None:\r\n        \"\"\"设置元素property属性\r\n        :param name: 属性名\r\n        :param value: 属性值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def style(self, name: str, value: str) -> None:\r\n        \"\"\"设置元素style样式\r\n        :param name: 样式名称\r\n        :param value: 样式值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def innerHTML(self, html: str) -> None:\r\n        \"\"\"设置元素innerHTML\r\n        :param html: html文本\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def value(self, value: str) -> None:\r\n        \"\"\"设置元素value值\r\n        :param value: value值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass ChromiumFrameSetter(ChromiumBaseSetter):\r\n    _owner: ChromiumFrame = ...\r\n\r\n    def attr(self, name: str, value: str) -> None:\r\n        \"\"\"设置frame元素attribute属性\r\n        :param name: 属性名\r\n        :param value: 属性值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def property(self, name, value) -> None:\r\n        \"\"\"设置元素property属性\r\n        :param name: 属性名\r\n        :param value: 属性值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def style(self, name, value) -> None:\r\n        \"\"\"设置元素style样式\r\n        :param name: 样式名称\r\n        :param value: 样式值\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass LoadMode(object):\r\n    \"\"\"用于设置页面加载策略的类\"\"\"\r\n    _owner: Union[Chromium, ChromiumBase] = ...\r\n\r\n    def __init__(self, owner: Union[Chromium, ChromiumBase]):\r\n        \"\"\"\r\n        :param owner: ChromiumBase对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def __call__(self, value: Literal['normal', 'eager', 'none']) -> None:\r\n        \"\"\"设置加载策略\r\n        :param value: 可选 'normal', 'eager', 'none'\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def normal(self) -> None:\r\n        \"\"\"设置页面加载策略为normal\"\"\"\r\n        ...\r\n\r\n    def eager(self) -> None:\r\n        \"\"\"设置页面加载策略为eager\"\"\"\r\n        ...\r\n\r\n    def none(self) -> None:\r\n        \"\"\"设置页面加载策略为none\"\"\"\r\n        ...\r\n\r\n\r\nclass PageScrollSetter(object):\r\n    _scroll: PageScroller = ...\r\n\r\n    def __init__(self, scroll: PageScroller):\r\n        \"\"\"\r\n        :param scroll: PageScroller对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def wait_complete(self, on_off: bool = True):\r\n        \"\"\"设置滚动命令后是否等待完成\r\n        :param on_off: 开或关\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def smooth(self, on_off: bool = True):\r\n        \"\"\"设置页面滚动是否平滑滚动\r\n        :param on_off: 开或关\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n\r\nclass WindowSetter(object):\r\n    \"\"\"用于设置窗口大小的类\"\"\"\r\n    _owner: ChromiumBase = ...\r\n    _window_id: str = ...\r\n\r\n    def __init__(self, owner: Union[ChromiumTab, ChromiumPage]):\r\n        \"\"\"\r\n        :param owner: Tab或Page对象\r\n        \"\"\"\r\n        ...\r\n\r\n    def max(self) -> None:\r\n        \"\"\"窗口最大化\"\"\"\r\n        ...\r\n\r\n    def mini(self) -> None:\r\n        \"\"\"窗口最小化\"\"\"\r\n        ...\r\n\r\n    def full(self) -> None:\r\n        \"\"\"设置窗口为全屏\"\"\"\r\n        ...\r\n\r\n    def normal(self) -> None:\r\n        \"\"\"设置窗口为常规模式\"\"\"\r\n        ...\r\n\r\n    def size(self, width: int = None, height: int = None) -> None:\r\n        \"\"\"设置窗口大小\r\n        :param width: 窗口宽度\r\n        :param height: 窗口高度\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def location(self, x: int = None, y: int = None) -> None:\r\n        \"\"\"设置窗口在屏幕中的位置，相对左上角坐标\r\n        :param x: 距离顶部距离\r\n        :param y: 距离左边距离\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n\r\n    def hide(self) -> None:\r\n        \"\"\"隐藏浏览器窗口，只在Windows系统可用\"\"\"\r\n        ...\r\n\r\n    def show(self) -> None:\r\n        \"\"\"显示浏览器窗口，只在Windows系统可用\"\"\"\r\n        ...\r\n\r\n    def _get_info(self) -> dict:\r\n        \"\"\"获取窗口位置及大小信息\"\"\"\r\n        ...\r\n\r\n    def _perform(self, bounds: dict) -> None:\r\n        \"\"\"执行改变窗口大小操作\r\n        :param bounds: 控制数据\r\n        :return: None\r\n        \"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/states.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom .._functions.web import location_in_viewport\r\nfrom ..errors import CDPError, NoRectError, PageDisconnectedError, ElementLostError\r\n\r\n\r\nclass ElementStates(object):\r\n    def __init__(self, ele):\r\n        self._ele = ele\r\n\r\n    @property\r\n    def is_selected(self):\r\n        return self._ele._run_js('return this.selected;')\r\n\r\n    @property\r\n    def is_checked(self):\r\n        return self._ele._run_js('return this.checked;')\r\n\r\n    @property\r\n    def is_displayed(self):\r\n        return not (self._ele.style('visibility') == 'hidden'\r\n                    or self._ele.style('display') == 'none'\r\n                    or self._ele.property('hidden'))\r\n\r\n    @property\r\n    def is_enabled(self):\r\n        return not self._ele._run_js('return this.disabled;')\r\n\r\n    @property\r\n    def is_alive(self):\r\n        try:\r\n            return self._ele.owner._run_cdp('DOM.describeNode',\r\n                                            backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0\r\n        except ElementLostError:\r\n            return False\r\n\r\n    @property\r\n    def is_in_viewport(self):\r\n        x, y = self._ele.rect.click_point\r\n        return location_in_viewport(self._ele.owner, x, y) if x else False\r\n\r\n    @property\r\n    def is_whole_in_viewport(self):\r\n        x1, y1 = self._ele.rect.location\r\n        w, h = self._ele.rect.size\r\n        x2, y2 = x1 + w, y1 + h\r\n        return location_in_viewport(self._ele.owner, x1, y1) and location_in_viewport(self._ele.owner, x2, y2)\r\n\r\n    @property\r\n    def is_covered(self):\r\n        lx, ly = self._ele.rect.click_point\r\n        try:\r\n            bid = self._ele.owner._run_cdp('DOM.getNodeForLocation', x=int(lx), y=int(ly)).get('backendNodeId')\r\n            return bid if bid != self._ele._backend_id else False\r\n        except CDPError:\r\n            return False\r\n\r\n    @property\r\n    def is_clickable(self):\r\n        return self.has_rect and self.is_enabled and self.is_displayed and self._ele.style('pointer-events') != 'none'\r\n\r\n    @property\r\n    def has_rect(self):\r\n        try:\r\n            return self._ele.rect.corners\r\n        except NoRectError:\r\n            return False\r\n\r\n\r\nclass ShadowRootStates(object):\r\n    def __init__(self, ele):\r\n        self._ele = ele\r\n\r\n    @property\r\n    def is_enabled(self):\r\n        return not self._ele._run_js('return this.disabled;')\r\n\r\n    @property\r\n    def is_alive(self):\r\n        try:\r\n            return self._ele.owner._run_cdp('DOM.describeNode',\r\n                                            backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0\r\n        except ElementLostError:\r\n            return False\r\n\r\n\r\nclass BrowserStates(object):\r\n    def __init__(self, browser):\r\n        self._browser = browser\r\n        self._incognito = None\r\n\r\n    @property\r\n    def is_alive(self):\r\n        return self._browser._driver.is_running\r\n\r\n    @property\r\n    def is_headless(self):\r\n        return self._browser._is_headless\r\n\r\n    @property\r\n    def is_existed(self):\r\n        return self._browser._is_exists\r\n\r\n    @property\r\n    def is_incognito(self):\r\n        if self._incognito is None:\r\n            self._incognito = \"'Browser.WindowCount.Incognito'\" in str(self._browser._run_cdp('Browser.getHistograms'))\r\n        return self._incognito\r\n\r\n\r\nclass PageStates(object):\r\n    \"\"\"Page对象、Tab对象使用\"\"\"\r\n\r\n    def __init__(self, owner):\r\n        self._owner = owner\r\n\r\n    @property\r\n    def is_loading(self):\r\n        return self._owner._is_loading\r\n\r\n    @property\r\n    def is_alive(self):\r\n        try:\r\n            self._owner._run_cdp('Page.getLayoutMetrics')\r\n            return True\r\n        except PageDisconnectedError:\r\n            return False\r\n\r\n    @property\r\n    def ready_state(self):\r\n        return self._owner._ready_state\r\n\r\n    @property\r\n    def has_alert(self):\r\n        return self._owner._has_alert\r\n\r\n    @property\r\n    def is_headless(self):\r\n        return self._owner.browser.states.is_headless\r\n\r\n    @property\r\n    def is_existed(self):\r\n        return self._owner.browser.states.is_existed\r\n\r\n    @property\r\n    def is_incognito(self):\r\n        return self._owner.browser.states.is_incognito\r\n\r\n\r\nclass FrameStates(object):\r\n    def __init__(self, frame):\r\n        self._frame = frame\r\n\r\n    @property\r\n    def is_loading(self):\r\n        return self._frame._is_loading\r\n\r\n    @property\r\n    def is_alive(self):\r\n        try:\r\n            node = self._frame._target_page._run_cdp('DOM.describeNode',\r\n                                                     backendNodeId=self._frame._frame_ele._backend_id)['node']\r\n        except (ElementLostError, PageDisconnectedError):\r\n            return False\r\n        return 'frameId' in node\r\n\r\n    @property\r\n    def ready_state(self):\r\n        return self._frame._ready_state\r\n\r\n    @property\r\n    def is_displayed(self):\r\n        return not (self._frame.frame_ele.style('visibility') == 'hidden'\r\n                    or self._frame.frame_ele._run_js('return this.offsetParent === null;')\r\n                    or self._frame.frame_ele.style('display') == 'none')\r\n\r\n    @property\r\n    def has_alert(self):\r\n        return self._frame._has_alert\r\n"
  },
  {
    "path": "DrissionPage/_units/states.pyi",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom typing import Union, Tuple, List, Optional, Literal\r\n\r\nfrom .._base.chromium import Chromium\r\nfrom .._elements.chromium_element import ShadowRoot, ChromiumElement\r\nfrom .._pages.chromium_base import ChromiumBase\r\nfrom .._pages.chromium_frame import ChromiumFrame\r\n\r\n\r\nclass ElementStates(object):\r\n    _ele: ChromiumElement = ...\r\n\r\n    def __init__(self, ele: ChromiumElement):\r\n        \"\"\"\r\n        :param ele: ChromiumElement\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_selected(self) -> bool:\r\n        \"\"\"返回列表元素是否被选择\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_checked(self) -> bool:\r\n        \"\"\"返回元素是否被选择\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_displayed(self) -> bool:\r\n        \"\"\"返回元素是否显示\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_enabled(self) -> bool:\r\n        \"\"\"返回元素是否可用\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_alive(self) -> bool:\r\n        \"\"\"返回元素是否仍在DOM中\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_in_viewport(self) -> bool:\r\n        \"\"\"返回元素是否出现在视口中，以元素click_point为判断\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_whole_in_viewport(self) -> bool:\r\n        \"\"\"返回元素是否整个都在视口内\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_covered(self) -> Union[Literal[False], int]:\r\n        \"\"\"返回元素是否被覆盖，与是否在视口中无关，如被覆盖返回覆盖元素的backend id，否则返回False\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_clickable(self) -> bool:\r\n        \"\"\"返回元素是否可被模拟点击，从是否有大小、是否可用、是否显示、是否响应点击判断，不判断是否被遮挡\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def has_rect(self) -> Union[Literal[False], List[Tuple[float, float]]]:\r\n        \"\"\"返回元素是否拥有位置和大小，没有返回False，有返回四个角在页面中坐标组成的列表\"\"\"\r\n        ...\r\n\r\n\r\nclass ShadowRootStates(object):\r\n    _ele: ShadowRoot = ...\r\n\r\n    def __init__(self, ele: ShadowRoot):\r\n        \"\"\"\r\n        :param ele: ChromiumElement\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_enabled(self) -> bool:\r\n        \"\"\"返回元素是否可用\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_alive(self) -> bool:\r\n        \"\"\"返回元素是否仍在DOM中\"\"\"\r\n        ...\r\n\r\n\r\nclass BrowserStates(object):\r\n    _browser: Chromium = ...\r\n    _incognito: Optional[bool] = ...\r\n\r\n    def __init__(self, browser: Chromium):\r\n        \"\"\"\r\n        :param browser: Chromium对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_alive(self) -> bool:\r\n        \"\"\"返回浏览器是否仍可用\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_headless(self) -> bool:\r\n        \"\"\"返回浏览器是否无头模式\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_existed(self) -> bool:\r\n        \"\"\"返回浏览器是否接管的\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_incognito(self) -> bool:\r\n        \"\"\"返回浏览器是否无痕模式\"\"\"\r\n        ...\r\n\r\n\r\nclass PageStates(object):\r\n    _owner: ChromiumBase = ...\r\n\r\n    def __init__(self, owner: ChromiumBase):\r\n        \"\"\"\r\n        :param owner: ChromiumBase对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_loading(self) -> bool:\r\n        \"\"\"返回页面是否在加载状态\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_alive(self) -> bool:\r\n        \"\"\"返回页面对象是否仍然可用\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def ready_state(self) -> Optional[str]:\r\n        \"\"\"返回当前页面加载状态，'connecting' 'loading' 'interactive' 'complete'\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def has_alert(self) -> bool:\r\n        \"\"\"返回当前页面是否存在弹窗\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_headless(self) -> bool:\r\n        \"\"\"返回浏览器是否无头模式\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_existed(self) -> bool:\r\n        \"\"\"返回浏览器是否接管的\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_incognito(self) -> bool:\r\n        \"\"\"返回浏览器是否无痕模式\"\"\"\r\n        ...\r\n\r\n\r\nclass FrameStates(object):\r\n    _frame: ChromiumFrame = ...\r\n\r\n    def __init__(self, frame: ChromiumFrame):\r\n        \"\"\"\r\n        :param frame: ChromiumFrame对象\r\n        \"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_loading(self) -> bool:\r\n        \"\"\"返回页面是否在加载状态\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_alive(self) -> bool:\r\n        \"\"\"返回frame元素是否可用，且里面仍挂载有frame\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def ready_state(self) -> str:\r\n        \"\"\"返回加载状态\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def is_displayed(self) -> bool:\r\n        \"\"\"返回iframe是否显示\"\"\"\r\n        ...\r\n\r\n    @property\r\n    def has_alert(self) -> bool:\r\n        \"\"\"返回当前页面是否存在弹窗\"\"\"\r\n        ...\r\n"
  },
  {
    "path": "DrissionPage/_units/waiter.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom time import sleep, perf_counter\n\nfrom .._functions.locator import get_loc\nfrom .._functions.settings import Settings as _S\nfrom ..errors import WaitTimeoutError, NoRectError\n\n\nclass OriginWaiter(object):\n    def __init__(self, owner):\n        self._owner = owner\n\n    def __call__(self, second, scope=None):\n        if scope is None:\n            sleep(second)\n        else:\n            from random import uniform\n            sleep(uniform(second, scope))\n        return self._owner\n\n\nclass BrowserWaiter(OriginWaiter):\n    def new_tab(self, timeout=None, curr_tab=None, raise_err=None):\n        if not curr_tab:\n            curr_tab = self._owner._newest_tab_id\n        elif hasattr(curr_tab, '_type'):\n            curr_tab = curr_tab.tab_id\n        if timeout is None:\n            timeout = self._owner.timeout\n        end_time = perf_counter() + timeout\n        while perf_counter() < end_time:\n            if curr_tab != self._owner._newest_tab_id:\n                return self._owner._newest_tab_id\n            sleep(.01)\n\n        if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\n            raise WaitTimeoutError(_S._lang.WAITING_FAILED_, _S._lang.NEW_TAB, timeout)\n        else:\n            return False\n\n    def download_begin(self, timeout=None, cancel_it=False):\n        if not self._owner._dl_mgr._running:\n            raise RuntimeError(_S._lang.join(_S._lang.NEED_DOWNLOAD_PATH, TIP=_S._lang.SET_DOWNLOAD_PATH))\n        self._owner._dl_mgr.set_flag('browser', False if cancel_it else True)\n        if timeout is None:\n            timeout = self._owner.timeout\n        return wait_mission(self._owner, 'browser', timeout)\n\n    def downloads_done(self, timeout=None, cancel_if_timeout=True):\n        if not self._owner._dl_mgr._running:\n            raise RuntimeError(_S._lang.join(_S._lang.NEED_DOWNLOAD_PATH, TIP=_S._lang.SET_DOWNLOAD_PATH))\n        if not timeout:\n            while self._owner._dl_mgr._missions:\n                sleep(.5)\n            return True\n\n        else:\n            end_time = perf_counter() + timeout\n            while perf_counter() < end_time:\n                if not self._owner._dl_mgr._missions:\n                    return True\n                sleep(.5)\n\n            if self._owner._dl_mgr._missions:\n                if cancel_if_timeout:\n                    for m in list(self._owner._dl_mgr._missions.values()):\n                        m.cancel()\n                return False\n            else:\n                return True\n\n\nclass BaseWaiter(OriginWaiter):\n    def ele_deleted(self, loc_or_ele, timeout=None, raise_err=None):\n        ele = self._owner._ele(loc_or_ele, raise_err=False, timeout=0)\n        return ele.wait.deleted(timeout, raise_err=raise_err) if ele else True\n\n    def ele_displayed(self, loc_or_ele, timeout=None, raise_err=None):\n        if timeout is None:\n            timeout = self._owner.timeout\n        end_time = perf_counter() + timeout\n        ele = self._owner._ele(loc_or_ele, raise_err=False, timeout=timeout)\n        if not ele:\n            if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\n                raise WaitTimeoutError(_S._lang.WAITING_FAILED_, _S._lang.ELE_DISPLAYED, timeout)\n            else:\n                return False\n        return ele.wait.displayed(end_time - perf_counter(), raise_err=raise_err)\n\n    def ele_hidden(self, loc_or_ele, timeout=None, raise_err=None):\n        if timeout is None:\n            timeout = self._owner.timeout\n        end_time = perf_counter() + timeout\n        ele = self._owner._ele(loc_or_ele, raise_err=False, timeout=timeout)\n        timeout = end_time - perf_counter()\n        if timeout <= 0:\n            if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\n                raise WaitTimeoutError(_S._lang.WAITING_FAILED_, _S._lang.ELE_DISPLAYED, timeout)\n            else:\n                return False\n        return ele.wait.hidden(timeout, raise_err=raise_err)\n\n    def eles_loaded(self, locators, timeout=None, any_one=False, raise_err=None):\n\n        def _find(loc, driver):\n            r = driver.run('DOM.performSearch', query=loc, includeUserAgentShadowDOM=True)\n            if not r or 'error' in r:\n                return False\n            elif r['resultCount'] == 0:\n                driver.run('DOM.discardSearchResults', searchId=r['searchId'])\n                return False\n            searchId = r['searchId']\n            ids = driver.run('DOM.getSearchResults', searchId=searchId, fromIndex=0,\n                             toIndex=r['resultCount'])\n            if 'error' in ids:\n                return False\n\n            ids = ids['nodeIds']\n            res = False\n            for i in ids:\n                r = driver.run('DOM.describeNode', nodeId=i)\n                if 'error' in r or r['node']['nodeName'] in ('#text', '#comment'):\n                    continue\n                else:\n                    res = True\n                    break\n            driver.run('DOM.discardSearchResults', searchId=searchId)\n            return res\n\n        by = ('id', 'xpath', 'link text', 'partial link text', 'name', 'tag name', 'class name', 'css selector')\n        locators = ((get_loc(locators)[1],) if (isinstance(locators, str) or isinstance(locators, tuple)\n                                                and locators[0] in by and len(locators) == 2)\n                    else [get_loc(x)[1] for x in locators])\n        method = any if any_one else all\n\n        if timeout is None:\n            timeout = self._owner.timeout\n        end_time = perf_counter() + timeout\n        while perf_counter() < end_time:\n            if method([_find(l, self._owner.driver) for l in locators]):\n                return True\n            sleep(.01)\n        if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\n            raise WaitTimeoutError(_S._lang.WAITING_FAILED_, _S._lang.ELE_LOADED, timeout, LOCATOR=locators)\n        else:\n            return False\n\n    def load_start(self, timeout=None, raise_err=None):\n        return self._loading(timeout=timeout, gap=.002, raise_err=raise_err)\n\n    def doc_loaded(self, timeout=None, raise_err=None):\n        return self._loading(timeout=timeout, start=False, raise_err=raise_err)\n\n    def upload_paths_inputted(self):\n        end_time = perf_counter() + self._owner.timeout\n        while perf_counter() < end_time:\n            if not self._owner._upload_list:\n                return True\n            sleep(.01)\n        return False\n\n    def download_begin(self, timeout=None, cancel_it=False):\n        if not self._owner.browser._dl_mgr._running:\n            raise RuntimeError(_S._lang.join(_S._lang.NEED_DOWNLOAD_PATH, TIP=_S._lang.SET_DOWNLOAD_PATH))\n        self._owner.browser._dl_mgr.set_flag(self._owner.tab_id, False if cancel_it else True)\n        if timeout is None:\n            timeout = self._owner.timeout\n        return wait_mission(self._owner.browser, self._owner.tab_id, timeout)\n\n    def url_change(self, text, exclude=False, timeout=None, raise_err=None):\n        return self._owner if self._change('url', text, exclude, timeout, raise_err) else False\n\n    def title_change(self, text, exclude=False, timeout=None, raise_err=None):\n        return self._owner if self._change('title', text, exclude, timeout, raise_err) else False\n\n    def _change(self, arg, text, exclude=False, timeout=None, raise_err=None):\n\n        def do():\n            if arg == 'url':\n                v = self._owner._run_cdp('Target.getTargetInfo', targetId=self._owner._target_id)['targetInfo']['url']\n            elif arg == 'title':\n                v = self._owner._run_cdp('Target.getTargetInfo', targetId=self._owner._target_id)['targetInfo']['title']\n            else:\n                raise ValueError\n            if (not exclude and text in v) or (exclude and text not in v):\n                return True\n\n        if do():\n            return True\n\n        if timeout is None:\n            timeout = self._owner.timeout\n\n        end_time = perf_counter() + timeout\n        while perf_counter() < end_time:\n            if do():\n                return True\n            sleep(.05)\n\n        if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\n            raise WaitTimeoutError(_S._lang.WAITING_FAILED_, _S._lang.ARG, timeout, ARG=arg)\n        else:\n            return False\n\n    def _loading(self, timeout=None, start=True, gap=.01, raise_err=None):\n        if timeout is None:\n            timeout = self._owner.timeout\n        timeout = .1 if timeout <= 0 else timeout\n        end_time = perf_counter() + timeout\n        while perf_counter() < end_time:\n            if self._owner._is_loading == start:\n                return True\n            sleep(gap)\n\n        if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\n            raise WaitTimeoutError(_S._lang.WAITING_FAILED_, _S._lang.PAGE_LOADED, timeout)\n        else:\n            return False\n\n\nclass TabWaiter(BaseWaiter):\n    def downloads_done(self, timeout=None, cancel_if_timeout=True):\n        if not self._owner.browser._dl_mgr._running:\n            raise RuntimeError(_S._lang.join(_S._lang.NEED_DOWNLOAD_PATH, TIP=_S._lang.SET_DOWNLOAD_PATH))\n        if not timeout:\n            while self._owner.browser._dl_mgr.get_tab_missions(self._owner.tab_id):\n                sleep(.5)\n            return self._owner\n\n        else:\n            end_time = perf_counter() + timeout\n            while perf_counter() < end_time:\n                if not self._owner.browser._dl_mgr.get_tab_missions(self._owner.tab_id):\n                    return self._owner\n                sleep(.5)\n\n            if self._owner.browser._dl_mgr.get_tab_missions(self._owner.tab_id):\n                if cancel_if_timeout:\n                    for m in self._owner.browser._dl_mgr.get_tab_missions(self._owner.tab_id):\n                        m.cancel()\n                return False\n            else:\n                return self._owner\n\n    def alert_closed(self, timeout=None):\n        if timeout is None:\n            while not self._owner.states.has_alert:\n                sleep(.2)\n            while self._owner.states.has_alert:\n                sleep(.2)\n\n        else:\n            end_time = perf_counter() + timeout\n            while not self._owner.states.has_alert and perf_counter() < end_time:\n                sleep(.2)\n            while self._owner.states.has_alert and perf_counter() < end_time:\n                sleep(.2)\n\n        return False if self._owner.states.has_alert else self._owner\n\n\nclass ChromiumPageWaiter(TabWaiter):\n    def new_tab(self, timeout=None, raise_err=None):\n        return self._owner.browser.wait.new_tab(timeout=timeout, raise_err=raise_err)\n\n    def download_begin(self, timeout=None, cancel_it=False):\n        return self._owner.browser.wait.download_begin(timeout=timeout, cancel_it=cancel_it)\n\n    def all_downloads_done(self, timeout=None, cancel_if_timeout=True):\n        return self._owner.browser.wait.downloads_done(timeout=timeout, cancel_if_timeout=cancel_if_timeout)\n\n\nclass ElementWaiter(OriginWaiter):\n    def __init__(self, owner):\n        super().__init__(owner)\n        self._ele = owner\n\n    @property\n    def _timeout(self):\n        return self._ele.timeout\n\n    def deleted(self, timeout=None, raise_err=None):\n        return self._wait_state('is_alive', False, timeout, raise_err,\n                                err_text=_S._lang.WAITING_FAILED_.format(_S._lang.ELE_DEL, timeout))\n\n    def displayed(self, timeout=None, raise_err=None):\n        return self._wait_state('is_displayed', True, timeout, raise_err,\n                                err_text=_S._lang.WAITING_FAILED_.format(_S._lang.ELE_DISPLAYED, timeout))\n\n    def hidden(self, timeout=None, raise_err=None):\n        return self._wait_state('is_displayed', False, timeout, raise_err,\n                                err_text=_S._lang.WAITING_FAILED_.format(_S._lang.ELE_HIDDEN, timeout))\n\n    def covered(self, timeout=None, raise_err=None):\n        return self._ele if self._wait_state('is_covered', True, timeout, raise_err,\n                                             err_text=_S._lang.WAITING_FAILED_.format(_S._lang.ELE_COVERED,\n                                                                                      timeout)) else False\n\n    def not_covered(self, timeout=None, raise_err=None):\n        return self._wait_state('is_covered', False, timeout, raise_err,\n                                err_text=_S._lang.WAITING_FAILED_.format(_S._lang.ELE_NOT_COVERED, timeout))\n\n    def enabled(self, timeout=None, raise_err=None):\n        return self._wait_state('is_enabled', True, timeout, raise_err,\n                                err_text=_S._lang.WAITING_FAILED_.format(_S._lang.ELE_AVAILABLE, timeout))\n\n    def disabled(self, timeout=None, raise_err=None):\n        return self._wait_state('is_enabled', False, timeout, raise_err,\n                                err_text=_S._lang.WAITING_FAILED_.format(_S._lang.ELE_NOT_AVAILABLE, timeout))\n\n    def disabled_or_deleted(self, timeout=None, raise_err=None):\n        if not self._ele.states.is_enabled or not self._ele.states.is_alive:\n            return self._ele\n\n        if timeout is None:\n            timeout = self._timeout\n        end_time = perf_counter() + timeout\n        while perf_counter() < end_time:\n            if not self._ele.states.is_enabled or not self._ele.states.is_alive:\n                return self._ele\n            sleep(.05)\n\n        if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\n            raise WaitTimeoutError(_S._lang.WAITING_FAILED_, _S._lang.ELE_HIDDEN_DEL, timeout)\n        else:\n            return False\n\n    def clickable(self, wait_moved=True, timeout=None, raise_err=None):\n        if timeout is None:\n            timeout = self._timeout\n        t1 = perf_counter()\n        r = self._wait_state('is_clickable', True, timeout, raise_err,\n                             err_text=_S._lang.WAITING_FAILED_.format(_S._lang.ELE_CLICKABLE, timeout))\n        r = self.stop_moving(timeout=timeout - perf_counter() + t1) if wait_moved and r else r\n        if raise_err and not r:\n            raise WaitTimeoutError(_S._lang.WAITING_FAILED_, _S._lang.ELE_CLICKABLE, timeout)\n        return r\n\n    def has_rect(self, timeout=None, raise_err=None):\n        return self._ele if self._wait_state('has_rect', True, timeout, raise_err,\n                                             err_text=_S._lang.WAITING_FAILED_.format(_S._lang.ELE_HAS_RECT,\n                                                                                      timeout)) else False\n\n    def stop_moving(self, timeout=None, gap=.1, raise_err=None):\n        if timeout is None:\n            timeout = self._timeout\n        if timeout <= 0:\n            timeout = .1\n\n        end_time = perf_counter() + timeout\n        while perf_counter() < end_time:\n            try:\n                size = self._ele.states.has_rect\n                location = self._ele.rect.location\n                break\n            except NoRectError:\n                pass\n            sleep(.005)\n        else:\n            raise NoRectError\n\n        while perf_counter() < end_time:\n            sleep(gap)\n            if self._ele.rect.size == size and self._ele.rect.location == location:\n                return self._ele\n            size = self._ele.rect.size\n            location = self._ele.rect.location\n\n        if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\n            raise WaitTimeoutError(_S._lang.WAITING_FAILED_, _S._lang.ELE_STOP_MOVING, timeout)\n        else:\n            return False\n\n    def _wait_state(self, attr, mode=False, timeout=None, raise_err=None, err_text=None):\n        a = self._ele.states.__getattribute__(attr)\n        if (a and mode) or (not a and not mode):\n            return self._ele if isinstance(a, bool) else a\n\n        if timeout is None:\n            timeout = self._timeout\n        end_time = perf_counter() + timeout\n        while perf_counter() < end_time:\n            a = self._ele.states.__getattribute__(attr)\n            if (a and mode) or (not a and not mode):\n                return self._ele if isinstance(a, bool) else a\n            sleep(.05)\n\n        err_text = err_text or _S._lang.ELE_STATE_CHANGED_.format(timeout)\n        if raise_err is True or (_S.raise_when_wait_failed is True and raise_err is None):\n            raise WaitTimeoutError(err_text)\n        else:\n            return False\n\n\nclass FrameWaiter(BaseWaiter, ElementWaiter):\n    def __init__(self, owner):\n        super().__init__(owner)\n        self._ele = owner.frame_ele\n\n    @property\n    def _timeout(self):\n        return self._owner.timeout\n\n\ndef wait_mission(browser, tid, timeout=None):\n    r = False\n    end_time = perf_counter() + timeout\n    while perf_counter() < end_time:\n        v = browser._dl_mgr.get_flag(tid)\n        if not isinstance(v, bool):\n            r = v\n            break\n        sleep(.005)\n\n    browser._dl_mgr.set_flag(tid, None)\n    return r\n"
  },
  {
    "path": "DrissionPage/_units/waiter.pyi",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom typing import Union, Tuple, Any\n\nfrom .downloader import DownloadMission\nfrom .._base.chromium import Chromium\nfrom .._elements.chromium_element import ChromiumElement\nfrom .._pages.chromium_base import ChromiumBase\nfrom .._pages.chromium_frame import ChromiumFrame\nfrom .._pages.chromium_page import ChromiumPage\nfrom .._pages.chromium_tab import ChromiumTab\nfrom .._pages.mix_tab import MixTab\nfrom .._pages.web_page import WebPage\n\n\nclass OriginWaiter(object):\n    _owner: Any = ...\n\n    def __init__(self, owner: Any): ...\n\n    def __call__(self, second: float, scope: float = None):\n        \"\"\"等待若干秒，如传入两个参数，等待时间为这两个数间的一个随机数\n        :param second: 秒数\n        :param scope: 随机数范围\n        :return: 调用等待的对象\n        \"\"\"\n        ...\n\n\nclass BrowserWaiter(OriginWaiter):\n    _owner: Chromium = ...\n\n    def __init__(self, owner: Chromium):\n        \"\"\"\n        :param owner: Chromium对象\n        \"\"\"\n        ...\n\n    def __call__(self, second: float, scope: float = None) -> Chromium:\n        \"\"\"等待若干秒，如传入两个参数，等待时间为这两个数间的一个随机数\n        :param second: 秒数\n        :param scope: 随机数范围\n        :return: Chromium对象\n        \"\"\"\n        ...\n\n    def new_tab(self,\n                timeout: float = None,\n                curr_tab: Union[str, ChromiumTab, MixTab] = None,\n                raise_err: bool = None) -> Union[str, bool]:\n        \"\"\"等待新标签页出现\n        :param timeout: 超时时间（秒），为None则使用对象timeout属性\n        :param curr_tab: 指定当前最新的tab对象或tab id，用于判断新tab出现，为None自动获取\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等到新标签页返回其id，否则返回False\n        \"\"\"\n        ...\n\n    def download_begin(self, timeout: float = None, cancel_it: bool = False) -> Union[DownloadMission, False]:\n        \"\"\"等待浏览器下载开始，可将其拦截\n        :param timeout: 超时时间（秒），None使用页面对象超时时间\n        :param cancel_it: 是否取消该任务\n        :return: 成功返回任务对象，失败返回False\n        \"\"\"\n        ...\n\n    def downloads_done(self, timeout: float = None, cancel_if_timeout: bool = True) -> bool:\n        \"\"\"等待所有浏览器下载任务结束\n        :param timeout: 超时时间（秒），为None时无限等待\n        :param cancel_if_timeout: 超时时是否取消剩余任务\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n\nclass BaseWaiter(OriginWaiter):\n    _owner: ChromiumBase = ...\n\n    def ele_deleted(self,\n                    loc_or_ele: Union[str, tuple, ChromiumElement],\n                    timeout: float = None,\n                    raise_err: bool = None) -> bool:\n        \"\"\"等待元素从DOM中删除\n        :param loc_or_ele: 要等待的元素，可以是已有元素、定位符\n        :param timeout: 超时时间（秒），默认读取页面超时时间\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def ele_displayed(self,\n                      loc_or_ele: Union[str, tuple, ChromiumElement],\n                      timeout: float = None,\n                      raise_err: bool = None) -> bool:\n        \"\"\"等待元素变成显示状态\n        :param loc_or_ele: 要等待的元素，可以是已有元素、定位符\n        :param timeout: 超时时间（秒），默认读取页面超时时间\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def ele_hidden(self,\n                   loc_or_ele: Union[str, tuple, ChromiumElement],\n                   timeout: float = None,\n                   raise_err: bool = None) -> bool:\n        \"\"\"等待元素变成隐藏状态\n        :param loc_or_ele: 要等待的元素，可以是已有元素、定位符\n        :param timeout: 超时时间（秒），默认读取页面超时时间\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def eles_loaded(self,\n                    locators: Union[Tuple[str, str], str, list, tuple],\n                    timeout: float = None,\n                    any_one: bool = False,\n                    raise_err: bool = None) -> bool:\n        \"\"\"等待元素加载到DOM，可等待全部或任意一个\n        :param locators: 要等待的元素，输入定位符，用list输入多个\n        :param timeout: 超时时间（秒），默认读取页面超时时间\n        :param any_one: 是否等待到一个就返回\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回True，失败返回False\n        \"\"\"\n        ...\n\n    def load_start(self, timeout: float = None, raise_err: bool = None) -> bool:\n        \"\"\"等待页面开始加载\n        :param timeout: 超时时间（秒），为None时使用页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def doc_loaded(self, timeout: float = None, raise_err: bool = None) -> bool:\n        \"\"\"等待页面加载完成\n        :param timeout: 超时时间（秒），为None时使用页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def upload_paths_inputted(self) -> bool:\n        \"\"\"等待自动填写上传文件路径\"\"\"\n        ...\n\n    def download_begin(self, timeout: float = None, cancel_it: bool = False) -> Union[DownloadMission, bool, dict]:\n        \"\"\"等待浏览器下载开始，可将其拦截\n        :param timeout: 超时时间（秒），None使用页面对象超时时间\n        :param cancel_it: 是否取消该任务\n        :return: 成功返回任务对象（cancel_it为True时返回dict格式的下载信息），失败返回False\n        \"\"\"\n        ...\n\n    def url_change(self,\n                   text: str,\n                   exclude: bool = False,\n                   timeout: float = None,\n                   raise_err: bool = None) -> ChromiumBase:\n        \"\"\"等待url变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当url不包含text指定文本时返回True\n        :param timeout: 超时时间（秒）\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n    def title_change(self,\n                     text: str,\n                     exclude: bool = False,\n                     timeout: float = None,\n                     raise_err: bool = None) -> ChromiumBase:\n        \"\"\"等待title变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当title不包含text指定文本时返回True\n        :param timeout: 超时时间（秒），为None使用页面设置\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n    def _change(self,\n                arg: str,\n                text: str,\n                exclude: bool = False,\n                timeout: float = None,\n                raise_err: bool = None) -> bool:\n        \"\"\"等待指定属性变成包含或不包含指定文本\n        :param arg: 要被匹配的属性\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当属性不包含text指定文本时返回True\n        :param timeout: 超时时间（秒）\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def _loading(self,\n                 timeout: float = None,\n                 start: bool = True,\n                 gap: float = .01,\n                 raise_err: bool = None) -> bool:\n        \"\"\"等待页面开始加载或加载完成\n        :param timeout: 超时时间（秒），为None时使用页面timeout属性\n        :param start: 等待开始还是结束\n        :param gap: 间隔秒数\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n\nclass TabWaiter(BaseWaiter):\n    _owner: ChromiumTab = ...\n\n    def __init__(self, owner: ChromiumTab):\n        \"\"\"\n        :param owner: Tab对象\n        \"\"\"\n        ...\n\n    def __call__(self,\n                 second: float,\n                 scope: float = None) -> ChromiumTab:\n        \"\"\"等待若干秒，如传入两个参数，等待时间为这两个数间的一个随机数\n        :param second: 秒数\n        :param scope: 随机数范围\n        :return: ChromiumTab对象\n        \"\"\"\n        ...\n\n    def downloads_done(self,\n                       timeout: float = None,\n                       cancel_if_timeout: bool = True) -> Union[False, ChromiumTab]:\n        \"\"\"等待所有浏览器下载任务结束\n        :param timeout: 超时时间（秒），为None时无限等待\n        :param cancel_if_timeout: 超时时是否取消剩余任务\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def alert_closed(self, timeout: float = None) -> ChromiumTab:\n        \"\"\"等待弹出框关闭\n        :param timeout: 超时时间，为None无限等待\n        :return: 标签页对象自己\n        \"\"\"\n        ...\n\n    def url_change(self,\n                   text: str,\n                   exclude: bool = False,\n                   timeout: float = None,\n                   raise_err: bool = None) -> Union[False, ChromiumTab]:\n        \"\"\"等待url变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当url不包含text指定文本时返回True\n        :param timeout: 超时时间（秒）\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n    def title_change(self,\n                     text: str,\n                     exclude: bool = False,\n                     timeout: float = None,\n                     raise_err: bool = None) -> Union[False, ChromiumTab]:\n        \"\"\"等待title变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当title不包含text指定文本时返回True\n        :param timeout: 超时时间（秒），为None使用页面设置\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n\nclass MixTabWaiter(BaseWaiter):\n    _owner: MixTab = ...\n\n    def __init__(self, owner: MixTab):\n        \"\"\"\n        :param owner: Tab对象\n        \"\"\"\n        ...\n\n    def __call__(self,\n                 second: float,\n                 scope: float = None) -> MixTab:\n        \"\"\"等待若干秒，如传入两个参数，等待时间为这两个数间的一个随机数\n        :param second: 秒数\n        :param scope: 随机数范围\n        :return: MixTab对象\n        \"\"\"\n        ...\n\n    def downloads_done(self,\n                       timeout: float = None,\n                       cancel_if_timeout: bool = True) -> Union[False, MixTab]:\n        \"\"\"等待所有浏览器下载任务结束\n        :param timeout: 超时时间（秒），为None时无限等待\n        :param cancel_if_timeout: 超时时是否取消剩余任务\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def alert_closed(self, timeout: float = None) -> MixTab:\n        \"\"\"等待弹出框关闭\n        :param timeout: 超时时间，为None无限等待\n        :return: 标签页对象自己\n        \"\"\"\n        ...\n\n    def url_change(self,\n                   text: str,\n                   exclude: bool = False,\n                   timeout: float = None,\n                   raise_err: bool = None) -> Union[False, MixTab]:\n        \"\"\"等待url变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当url不包含text指定文本时返回True\n        :param timeout: 超时时间（秒）\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n    def title_change(self,\n                     text: str,\n                     exclude: bool = False,\n                     timeout: float = None,\n                     raise_err: bool = None) -> Union[False, MixTab]:\n        \"\"\"等待title变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当title不包含text指定文本时返回True\n        :param timeout: 超时时间（秒），为None使用页面设置\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n\nclass ChromiumPageWaiter(TabWaiter):\n    _owner: Union[ChromiumPage, WebPage] = ...\n\n    def __init__(self, owner: ChromiumPage):\n        \"\"\"\n        :param owner: Page对象\n        \"\"\"\n        ...\n\n    def __call__(self,\n                 second: float,\n                 scope: float = None) -> ChromiumPage:\n        \"\"\"等待若干秒，如传入两个参数，等待时间为这两个数间的一个随机数\n        :param second: 秒数\n        :param scope: 随机数范围\n        :return: ChromiumPage对象\n        \"\"\"\n        ...\n\n    def new_tab(self,\n                timeout: float = None,\n                raise_err: bool = None) -> Union[str, bool]:\n        \"\"\"等待新标签页出现\n        :param timeout: 超时时间（秒），为None则使用页面对象timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等到新标签页返回其id，否则返回False\n        \"\"\"\n        ...\n\n    def all_downloads_done(self,\n                           timeout: float = None,\n                           cancel_if_timeout: bool = True) -> bool:\n        \"\"\"等待所有浏览器下载任务结束\n        :param timeout: 超时时间（秒），为None时无限等待\n        :param cancel_if_timeout: 超时时是否取消剩余任务\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def url_change(self,\n                   text: str,\n                   exclude: bool = False,\n                   timeout: float = None,\n                   raise_err: bool = None) -> Union[False, ChromiumPage]:\n        \"\"\"等待url变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当url不包含text指定文本时返回True\n        :param timeout: 超时时间（秒）\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n    def title_change(self,\n                     text: str,\n                     exclude: bool = False,\n                     timeout: float = None,\n                     raise_err: bool = None) -> Union[False, ChromiumPage]:\n        \"\"\"等待title变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当title不包含text指定文本时返回True\n        :param timeout: 超时时间（秒），为None使用页面设置\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n\nclass WebPageWaiter(TabWaiter):\n    _owner: Union[ChromiumPage, WebPage] = ...\n\n    def __init__(self, owner: WebPage):\n        \"\"\"\n        :param owner: Page对象\n        \"\"\"\n        ...\n\n    def __call__(self,\n                 second: float,\n                 scope: float = None) -> WebPage:\n        \"\"\"等待若干秒，如传入两个参数，等待时间为这两个数间的一个随机数\n        :param second: 秒数\n        :param scope: 随机数范围\n        :return: WebPage对象\n        \"\"\"\n        ...\n\n    def new_tab(self,\n                timeout: float = None,\n                raise_err: bool = None) -> Union[str, bool]:\n        \"\"\"等待新标签页出现\n        :param timeout: 超时时间（秒），为None则使用页面对象timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等到新标签页返回其id，否则返回False\n        \"\"\"\n        ...\n\n    def all_downloads_done(self,\n                           timeout: float = None,\n                           cancel_if_timeout: bool = True) -> bool:\n        \"\"\"等待所有浏览器下载任务结束\n        :param timeout: 超时时间（秒），为None时无限等待\n        :param cancel_if_timeout: 超时时是否取消剩余任务\n        :return: 是否等待成功\n        \"\"\"\n        ...\n\n    def url_change(self,\n                   text: str,\n                   exclude: bool = False,\n                   timeout: float = None,\n                   raise_err: bool = None) -> Union[False, WebPage]:\n        \"\"\"等待url变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当url不包含text指定文本时返回True\n        :param timeout: 超时时间（秒）\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n    def title_change(self,\n                     text: str,\n                     exclude: bool = False,\n                     timeout: float = None,\n                     raise_err: bool = None) -> Union[False, WebPage]:\n        \"\"\"等待title变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当title不包含text指定文本时返回True\n        :param timeout: 超时时间（秒），为None使用页面设置\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n\nclass ElementWaiter(OriginWaiter):\n    _owner: ChromiumElement = ...\n    _ele: ChromiumElement = ...\n\n    def __init__(self, owner: ChromiumElement):\n        \"\"\"\n        :param owner: ChromiumElement对象\n        \"\"\"\n        ...\n\n    def __call__(self,\n                 second: float,\n                 scope: float = None) -> ChromiumElement:\n        \"\"\"等待若干秒，如传入两个参数，等待时间为这两个数间的一个随机数\n        :param second: 秒数\n        :param scope: 随机数范围\n        :return: ChromiumElement对象\n        \"\"\"\n        ...\n\n    @property\n    def _timeout(self) -> float:\n        \"\"\"返回超时设置\"\"\"\n        ...\n\n    def deleted(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待元素从dom删除\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def displayed(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待元素从dom显示\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def hidden(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待元素从dom隐藏\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def covered(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待当前元素被遮盖\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回覆盖元素id，返回False\n        \"\"\"\n        ...\n\n    def not_covered(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待当前元素不被遮盖\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def enabled(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待当前元素变成可用\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def disabled(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待当前元素变成不可用\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def disabled_or_deleted(self, timeout: float = None, raise_err: bool = None) -> bool:\n        \"\"\"等待当前元素变成不可用或从DOM移除\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def clickable(self,\n                  wait_moved: bool = True,\n                  timeout: float = None,\n                  raise_err: bool = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待当前元素可被点击\n        :param wait_moved: 是否等待元素运动结束\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def has_rect(self,\n                 timeout: float = None,\n                 raise_err: bool = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待当前元素有大小及位置属性\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def stop_moving(self,\n                    timeout: float = None,\n                    gap: float = .1,\n                    raise_err: bool = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待当前元素停止运动\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param gap: 检测间隔时间\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def _wait_state(self,\n                    attr: str,\n                    mode: bool = False,\n                    timeout: float = None,\n                    raise_err: bool = None,\n                    err_text: str = None) -> Union[ChromiumElement, False]:\n        \"\"\"等待元素某个元素状态到达指定状态\n        :param attr: 状态名称\n        :param mode: 等待True还是False\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :param err_text: 抛出错误时显示的信息\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n\nclass FrameWaiter(BaseWaiter, ElementWaiter):\n    _owner: ChromiumFrame = ...\n\n    def __init__(self, owner: ChromiumFrame):\n        \"\"\"\n        :param owner: ChromiumFrame对象\n        \"\"\"\n        ...\n\n    def __call__(self,\n                 second: float,\n                 scope: float = None) -> ChromiumFrame:\n        \"\"\"等待若干秒，如传入两个参数，等待时间为这两个数间的一个随机数\n        :param second: 秒数\n        :param scope: 随机数范围\n        :return: ChromiumFrame对象\n        \"\"\"\n        ...\n\n    def url_change(self,\n                   text: str,\n                   exclude: bool = False,\n                   timeout: float = None,\n                   raise_err: bool = None) -> Union[False, ChromiumFrame]:\n        \"\"\"等待url变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当url不包含text指定文本时返回True\n        :param timeout: 超时时间（秒）\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n    def title_change(self,\n                     text: str,\n                     exclude: bool = False,\n                     timeout: float = None,\n                     raise_err: bool = None) -> Union[False, ChromiumFrame]:\n        \"\"\"等待title变成包含或不包含指定文本\n        :param text: 用于识别的文本\n        :param exclude: 是否排除，为True时当title不包含text指定文本时返回True\n        :param timeout: 超时时间（秒），为None使用页面设置\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 等待成功返回页面对象，否则返回False\n        \"\"\"\n        ...\n\n    def deleted(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待元素从dom删除\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def displayed(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待元素从dom显示\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def hidden(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待元素从dom隐藏\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def has_rect(self,\n                 timeout: float = None,\n                 raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待当前元素有大小及位置属性\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def covered(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待当前元素被遮盖\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回覆盖元素id，返回False\n        \"\"\"\n        ...\n\n    def not_covered(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待当前元素不被遮盖\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def enabled(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待当前元素变成可用\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def disabled(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待当前元素变成不可用\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def disabled_or_deleted(self, timeout: float = None, raise_err: bool = None) -> bool:\n        \"\"\"等待当前元素变成不可用或从DOM移除\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def clickable(self,\n                  wait_moved: bool = True,\n                  timeout: float = None,\n                  raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待当前元素可被点击\n        :param wait_moved: 是否等待元素运动结束\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n    def stop_moving(self,\n                    timeout: float = None,\n                    gap: float = .1,\n                    raise_err: bool = None) -> Union[ChromiumFrame, False]:\n        \"\"\"等待当前元素停止运动\n        :param timeout: 超时时间（秒），为None使用元素所在页面timeout属性\n        :param gap: 检测间隔时间\n        :param raise_err: 等待失败时是否报错，为None时根据Settings设置\n        :return: 成功返回元素对象，失败返回False\n        \"\"\"\n        ...\n\n\ndef wait_mission(browser: Chromium, tid: str, timeout: float = None) -> Union[DownloadMission, False]:\n    \"\"\"等待下载任务\n    :param browser: Chromium对象\n    :param tid: 标签页id\n    :param timeout: 超时时间\n    :return:\n    \"\"\"\n    ...\n"
  },
  {
    "path": "DrissionPage/common.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom ._base.chromium import Chromium\r\nfrom ._configs.chromium_options import ChromiumOptions\r\nfrom ._elements.session_element import make_session_ele\r\nfrom ._functions.by import By\r\nfrom ._functions.keys import Keys\r\nfrom ._functions.settings import Settings\r\nfrom ._functions.tools import wait_until, configs_to_here\r\nfrom ._functions.web import get_blob, tree\r\nfrom ._units.actions import Actions\r\n\r\n__all__ = ['make_session_ele', 'Actions', 'Keys', 'By', 'Settings', 'wait_until', 'configs_to_here', 'get_blob',\r\n           'tree', 'from_selenium', 'from_playwright']\r\n\r\n\r\ndef from_selenium(driver):\r\n    \"\"\"从selenium的WebDriver对象生成Chromium对象\"\"\"\r\n    address, port = driver.caps.get('goog:chromeOptions', {}).get('debuggerAddress', ':').split(':')\r\n    if not address:\r\n        raise RuntimeError(Settings._lang.join(Settings._lang.GET_OBJ_FAILED))\r\n    co = ChromiumOptions().set_local_port(port)\r\n    co._ua_set = True\r\n    return Chromium(co)\r\n\r\n\r\ndef from_playwright(page_or_browser):\r\n    \"\"\"从playwright的Page或Browser对象生成Chromium对象\"\"\"\r\n    if hasattr(page_or_browser, 'context'):\r\n        page_or_browser = page_or_browser.context.browser\r\n    try:\r\n        processes = page_or_browser.new_browser_cdp_session().send('SystemInfo.getProcessInfo')['processInfo']\r\n        for process in processes:\r\n            if process['type'] == 'browser':\r\n                pid = process['id']\r\n                break\r\n        else:\r\n            raise RuntimeError(Settings._lang.join(Settings._lang.GET_OBJ_FAILED))\r\n    except:\r\n        raise RuntimeError(Settings._lang.join(Settings._lang.GET_OBJ_FAILED))\r\n\r\n    from psutil import net_connections\r\n    for con_info in net_connections():\r\n        if con_info.pid == pid:\r\n            port = con_info.laddr.port\r\n            break\r\n    else:\r\n        raise RuntimeError(Settings._lang.join(Settings._lang.GET_OBJ_FAILED, TIP=Settings._lang.RUN_BY_ADMIN))\r\n    co = ChromiumOptions().set_local_port(f'127.0.0.1:{port}')\r\n    co._ua_set = True\r\n    return Chromium(co)\r\n"
  },
  {
    "path": "DrissionPage/errors.py",
    "content": "# -*- coding:utf-8 -*-\r\n\"\"\"\r\n@Author   : g1879\r\n@Contact  : g1879@qq.com\r\n@Website  : https://DrissionPage.cn\r\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\r\n\"\"\"\r\nfrom ._functions.settings import Settings as _S\r\n\r\n\r\nclass BaseError(Exception):\r\n\r\n    def __init__(self, *args, **kwargs):\r\n        self._kwargs = kwargs\r\n        self._args = args if args else [_S._lang.get(self.__class__.__name__.upper())]\r\n\r\n    def __str__(self):\r\n        return _S._lang.join(*self._args, **self._kwargs)\r\n\r\n\r\nclass ElementNotFoundError(BaseError):\r\n    pass\r\n\r\n\r\nclass AlertExistsError(BaseError):\r\n    pass\r\n\r\n\r\nclass ContextLostError(BaseError):\r\n    pass\r\n\r\n\r\nclass ElementLostError(BaseError):\r\n    pass\r\n\r\n\r\nclass CDPError(BaseError):\r\n    pass\r\n\r\n\r\nclass PageDisconnectedError(BaseError):\r\n    pass\r\n\r\n\r\nclass JavaScriptError(BaseError):\r\n    pass\r\n\r\n\r\nclass NoRectError(BaseError):\r\n    pass\r\n\r\n\r\nclass BrowserConnectError(BaseError):\r\n    pass\r\n\r\n\r\nclass NoResourceError(BaseError):\r\n    pass\r\n\r\n\r\nclass CanNotClickError(BaseError):\r\n    pass\r\n\r\n\r\nclass GetDocumentError(BaseError):\r\n    pass\r\n\r\n\r\nclass WaitTimeoutError(BaseError):\r\n    pass\r\n\r\n\r\nclass IncorrectURLError(BaseError):\r\n    pass\r\n\r\n\r\nclass StorageError(BaseError):\r\n    pass\r\n\r\n\r\nclass CookieFormatError(BaseError):\r\n    pass\r\n\r\n\r\nclass TargetNotFoundError(BaseError):\r\n    pass\r\n\r\n\r\nclass LocatorError(BaseError):\r\n    pass\r\n\r\n\r\nclass UnknownError(BaseError):\r\n    pass\r\n"
  },
  {
    "path": "DrissionPage/items.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\nfrom ._elements.chromium_element import ChromiumElement, ShadowRoot\nfrom ._elements.none_element import NoneElement\nfrom ._elements.session_element import SessionElement\nfrom ._pages.chromium_frame import ChromiumFrame\nfrom ._pages.chromium_tab import ChromiumTab\nfrom ._pages.mix_tab import MixTab\nfrom ._pages.mix_tab import MixTab as WebPageTab\n\n__all__ = ['ChromiumElement', 'ShadowRoot', 'NoneElement', 'SessionElement', 'ChromiumFrame', 'ChromiumTab',\n           'MixTab', 'WebPageTab']\n"
  },
  {
    "path": "DrissionPage/version.py",
    "content": "# -*- coding:utf-8 -*-\n\"\"\"\n@Author   : g1879\n@Contact  : g1879@qq.com\n@Website  : https://DrissionPage.cn\n@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.\n\"\"\"\n__version__ = '4.1.1.2'\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2020, g1879\r\nAll rights reserved.\r\n\r\n允许任何人以个人身份使用或分发本项目源代码，但仅限于学习和合法非盈利目的。\r\n\r\n个人或组织如未获得版权持有人授权，不得将本项目以源代码或二进制形式用于商业行为。\r\n\r\n使用本项目需满足以下条款，如使用过程中出现违反任意一项条款的情形，授权自动失效。\r\n\r\n* 禁止将DrissionPage应用到任何可能违反当地法律规定和道德约束的项目中\r\n\r\n* 禁止将DrissionPage用于任何可能有损他人利益的项目中\r\n\r\n* 禁止将DrissionPage用于攻击与骚扰行为\r\n\r\n* 遵守Robots协议，禁止将DrissionPage用于采集法律或系统Robots协议不允许的数据\r\n\r\n使用DrissionPage发生的一切行为均由使用人自行负责。\r\n因使用DrissionPage进行任何行为所产生的一切纠纷及后果均与版权持有人无关，\r\n版权持有人不承担任何使用DrissionPage带来的风险和损失。\r\n版权持有人不对DrissionPage可能存在的缺陷导致的任何损失负任何责任。\r\n\r\n---------------------------------------------------------\r\n\r\nAnyone may use or distribute the source code of this project in their personal capacity, \r\nbut only for the purpose of learning and legal non-profit activities. \r\n\r\nAn individual or organization may not use the project's source code or binary form for \r\ncommercial purposes without authorization from the copyright holder. \r\n\r\nThe following terms and conditions must be met in order to use this project. Authorization\r\n will automatically expire if any of the terms are violated during use. \r\n\r\n* It is strictly prohibited to use the DrissionPage app for any project that may violate local \r\n  laws and ethical constraints. \r\n\r\n* It is strictly prohibited to use DrissionPage for any project that may harm the interests of others. \r\n\r\n* It is strictly prohibited to use DrissionPage for attack and harassment. \r\n\r\n* Follow the Robots protocol and do not use the DrissionPage to collect data that is prohibited \r\n  by law or the system's Robots protocol. \r\n\r\nAll actions taken using DrissionPage are the responsibility of the user.\r\nThe copyright holder is not involved in any disputes or consequences arising from the use of \r\nDrissionPage for any actions, and the copyright holder shall not bear any risks and losses arising \r\nfrom the use of DrissionPage.\r\nThe copyright holder shall not bear any responsibility for any losses resulting from any defects in \r\nDrissionPage."
  },
  {
    "path": "MANIFEST.in",
    "content": "include DrissionPage/_configs/configs.ini\r\ninclude DrissionPage/_functions/suffixes.dat\r\ninclude DrissionPage/*.pyi\r\ninclude DrissionPage/*/*.py\r\ninclude DrissionPage/*/*.pyi"
  },
  {
    "path": "README.md",
    "content": "# ✨️ 概述\r\n\r\nDrissionPage 是一个基于 python 的网页自动化工具。\r\n\r\n它既能控制浏览器，也能收发数据包，还能把两者合而为一。\r\n\r\n可兼顾浏览器自动化的便利性和 requests 的高效率。\r\n\r\n它功能强大，内置无数人性化设计和便捷功能。\r\n\r\n它的语法简洁而优雅，代码量少，对新手友好。\r\n\r\n<a href=\"https://www.tgebrowser.com/zh\" target=\"_blank\"><img src=\"https://raw.githubusercontent.com/g1879/DrissionPage/refs/heads/master/img/ad.png\"/></a>\r\n\r\n---\r\n\r\n官方网站：[https://DrissionPage.cn](https://drissionpage.cn)\r\n\r\n项目地址：[gitee](https://gitee.com/g1879/DrissionPage)    |    [github](https://github.com/g1879/DrissionPage)     |    [gitcode](https://gitcode.com/g1879/DrissionPage) \r\n\r\n您的星星是对我最大的支持💖\r\n\r\n--- \r\n\r\n支持系统：Windows、Linux、Mac\r\n\r\npython 版本：3.6 及以上\r\n\r\n支持浏览器：Chromium 内核浏览器(如 Chrome 和 Edge)，electron 应用\r\n\r\n---\r\n\r\n# 🛠 如何使用\r\n\r\n**📖 使用文档：**  [点击查看](https://DrissionPage.cn)\r\n\r\n**交流 QQ 群：**  见使用文档\r\n\r\n![](https://drissionpage.cn/codes.png)\r\n\r\n---\r\n\r\n# 💡 理念\r\n\r\n简洁而强大！\r\n\r\n--- \r\n\r\n# ☀️ 特性和亮点\r\n\r\n作者经过长期实践，踩过无数坑，总结出的经验全写到这个库里了。\r\n\r\n## 🎇 强大的自研内核\r\n\r\n本库采用全自研的内核，内置无数实用功能，对常用功能作了整合和优化，对比 selenium，有以下优点：\r\n\r\n- 不基于 webdriver\r\n- 无需为不同版本的浏览器下载不同的驱动\r\n- 运行速度更快\r\n- 可以跨 iframe 查找元素，无需切入切出\r\n- 把 iframe 看作普通元素，逻辑更清晰\r\n- 可同时操作多个标签页，无需切换\r\n- 可以直接读取浏览器缓存保存图片，无需用 GUI 点击另存\r\n- 可以对整个网页截图，包括视口外的部分\r\n- 可处理非`open`状态的 shadow-root\r\n\r\n## 🎇 亮点功能\r\n\r\n除了以上优点，本库还内置了无数人性化设计。\r\n\r\n- 极简的定位语法，查找元素更加容易\r\n- 集成大量常用功能，代码更优雅，功能强大稳定\r\n- 无处不在的等待和自动重试，使不稳定的网络变得易于控制，程序更稳定，编写更省心\r\n- 提供强大的下载工具，操作浏览器时也能享受快捷可靠的下载功能\r\n- 允许反复使用已经打开的浏览器，无需每次运行从头启动浏览器，调试方便\r\n- 使用 ini 文件保存常用配置，自动调用，提供便捷的设置，远离繁杂的配置项\r\n- 内置 lxml 作为解析引擎，解析速度成几个数量级提升\r\n- 使用 POM 模式封装，可直接用于测试，便于扩展\r\n- 高度集成的便利功能，从每个细节中体现\r\n- 还有很多细节，这里不一一列举，欢迎实际使用中体验：D\r\n\r\n--- \r\n\r\n# 📝 使用条款\r\n\r\n允许任何人以个人身份使用或分发本项目源代码，但仅限于学习和合法非盈利目的。\r\n个人或组织如未获得版权持有人授权，不得将本项目以源代码或二进制形式用于商业行为。\r\n\r\n使用本项目需满足以下条款，如使用过程中出现违反任意一项条款的情形，授权自动失效。\r\n- 禁止将DrissionPage应用到任何可能违反当地法律规定和道德约束的项目中\r\n- 禁止将DrissionPage用于任何可能有损他人利益的项目中\r\n- 禁止将DrissionPage用于攻击与骚扰行为\r\n- 遵守Robots协议，禁止将DrissionPage用于采集法律或系统Robots协议不允许的数据\r\n\r\n使用DrissionPage发生的一切行为均由使用人自行负责。\r\n因使用DrissionPage进行任何行为所产生的一切纠纷及后果均与版权持有人无关，\r\n版权持有人不承担任何使用DrissionPage带来的风险和损失。\r\n版权持有人不对DrissionPage可能存在的缺陷导致的任何损失负任何责任。\r\n\r\n---  \r\n\r\n# ☕ 请我喝咖啡\r\n\r\n作者是个人开发者，开发和写文档工作量较为繁重。\r\n\r\n如果本项目对您有所帮助，不妨打赏一下作者 ：）\r\n\r\n![](https://raw.githubusercontent.com/g1879/DrissionPage/refs/heads/master/img/code.jpg)\r\n"
  },
  {
    "path": "docs_en/.nojekyll",
    "content": ""
  },
  {
    "path": "docs_en/ChromiumPage/actions.md",
    "content": "## 🚤 Actions Chain\n\nActions Chain can perform a series of interactive actions on the browser, such as mouse movement, mouse clicks, keyboard input, etc.\n\nThe `ChromiumPage`, `WebPage`, `ChromiumTab`, and `ChromiumFrame` objects support the use of actions chains.\n\nActions can be chained together or executed separately, and each action takes effect immediately without the need for `perform()`.\n\nThese actions are all simulated, so the actual mouse will not move, allowing multiple tabs to be operated simultaneously.\n\n## ✅️ Usage\n\nYou can call the actions chain using the built-in `actions` attribute of the aforementioned objects, or you can create an actions chain object and pass in the page object to use.\n\nThe only difference between these two methods is that the former will wait for the page to finish loading before executing, while the latter will not.\n\n### 📌 Using the built-in `actions` attribute\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('https://www.baidu.com')\npage.actions.move_to('#kw').click().type('DrissionPage')\npage.actions.move_to('#su').click()\n```\n\n---\n\n### 📌 Using a new object\n\nImport the actions chain using `from DrissionPage.common import Actions`.\n\nJust pass in the `WebPage` object or `ChromiumPage` object. The actions chain only works on this page.\n\n| Initialization Parameter |                       Type                       | Default | Description                               |\n|:------------------------:|:-----------------------------------------------:|:-------:|-------------------------------------------|\n|           page           | ChromiumPage<br/>WebPage<br/>ChromiumTab | Required | The browser page that the actions chain should operate on |\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom DrissionPage.common import Actions\n\npage = ChromiumPage()\nac = Actions(page)\npage.get('https://www.baidu.com')\nac.move_to('#kw').click().type('DrissionPage')\nac.move_to('#su').click()\n```\n\n---\n\n### 📌 Operation Modes\n\nMultiple actions can be chained together:\n\n```python\nac.move_to(ele).click().type('some text')\n```\n\nor the actions can be executed separately:\n\n```python\nac.move_to(ele)\nac.click()\nac.type('some text')\n```\n\nBoth methods produce the same result, and each action will be executed sequentially.\n\n---\n\n## ✅️ Moving the Mouse\n\n### 📌 `move_to()`\n\nThis method moves the mouse to the center of an element or to an absolute coordinate on the page. You can set an offset, which is relative to the top-left corner of the element.\n\n|   Initialization Parameter    |                       Type                        | Default | Description                                                                                                        |\n|:----------------------------:|:------------------------------------------------:|:-------:|------------------------------------------------------------------------------------------------------------------|\n|         `ele_or_loc`         | ChromiumElement<br/>str<br/>Tuple[int, int] | Required | The element object, text locator, or absolute coordinates (as a `tuple`(int, int))                                   |\n|          `offset_x`          |                      int                       |   0   | The x-axis offset, with positive values to the right and negative values to the left                                |\n|          `offset_y`          |                      int                       |   0   | The y-axis offset, with positive values downwards and negative values upwards                                       |\n|          `duration`          |                     float                      |  0.5  | The duration of the movement, pass in `0` to instantly move                                                      |\n\n|        Return Type         |                 Description                 |\n|:------------------------:|--------------------------------------------|\n|         `Actions`         | The actions chain object itself             |\n\n**Example:** Move the mouse to the element `ele`\n\n```python\nele = page('tag:a')\nac.move_to(ele_or_loc=ele)\n```\n\n---\n\n### 📌 `move()`\n\nThis method moves the mouse a certain distance relative to its current position.\n\n| Parameter Name |  Type   | Default | Description                             |\n|:-------------:|:------:|:-------:|----------------------------------------|\n|   `offset_x`  |  int   |    0    | The x-axis offset, with positive values to the right and negative values to the left |\n|   `offset_y`  |  int   |    0    | The y-axis offset, with positive values downwards and negative values upwards     |\n|   `duration`  | float  |   0.5   | The duration of the movement, pass in `0` to instantly move                     |\n\n|        Return Type         |                 Description                 |\n|:------------------------:|--------------------------------------------|\n|         `Actions`         | The actions chain object itself             |\n\n**Example:** Move the mouse 300 pixels to the right\n\n```python\nac.move(300, 0)\n```\n\n---\n\n### 📌 `up()`\n\nThis method moves the mouse a certain distance upwards from its current position.\n\n| Parameter Name |  Type   | Default | Description        |\n|:-------------:|:------:|:-------:|-------------------|\n|     `pixel`    |  int   | Required   | The distance to move the mouse upwards |\n\n|        Return Type         |                 Description                 |\n|:------------------------:|--------------------------------------------|\n|         `Actions`         | The actions chain object itself             |\n\n**Example:** Move the mouse 50 pixels upwards\n\n```python\nac.up(50)\n```\n\n---\n\n### 📌 `down()`\n\nThis method moves the mouse a certain distance downwards from its current position.\n\n| Parameter Name |  Type   | Default | Description          |\n|:-------------:|:------:|:-------:|---------------------|\n|   `pixel`    |  int   | Required   | The distance to move the mouse downwards |\n\n|        Return Type         |                 Description                 |\n|:------------------------:|--------------------------------------------|\n|         `Actions`         | The actions chain object itself             |\n\n**Example:**\n\nThis method is used to move the mouse left by a certain distance relative to the current position.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:|-------------|\n| `pixel` | `int` | Required | The number of pixels to move the mouse by |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n**Example:**\n\n```python\nac.left(50)\n```\n\n---\n\n### 📌 `right()`\n\nThis method is used to move the mouse right by a certain distance relative to the current position.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:|-------------|\n| `pixel` | `int` | Required | The number of pixels to move the mouse by |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n**Example:**\n\n```python\nac.right(50)\n```\n\n---\n\n## ✅️ Mouse Buttons\n\n### 📌 `click()`\n\nThis method is used to click the left mouse button, and can be preceded by moving to an element.\n\n| Parameter Name |                      Type                     | Default Value |    Description     |\n|:--------------:|:---------------------------------------------:|:-------------:|:------------------:|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |     `None`    | The element object or text locator to click on |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n**Example:**\n\n```python\nac.click('#div1')\n```\n\n---\n\n### 📌 `r_click()`\n\nThis method is used to click the right mouse button, and can be preceded by moving to an element.\n\n| Parameter Name |                      Type                     | Default Value |    Description     |\n|:--------------:|:---------------------------------------------:|:-------------:|:------------------:|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |     `None`    | The element object or text locator to click on |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n**Example:**\n\n```python\nac.r_click('#div1')\n```\n\n---\n\n### 📌 `m_click()`\n\nThis method is used to click the middle mouse button, and can be preceded by moving to an element.\n\n| Parameter Name |                      Type                     | Default Value |    Description     |\n|:--------------:|:---------------------------------------------:|:-------------:|:------------------:|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |     `None`    | The element object or text locator to click on |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n**Example:**\n\n```python\nac.m_click('#div1')\n```\n\n---\n\n### 📌 `db_click()`\n\nThis method is used to double-click the left mouse button, and can be preceded by moving to an element.\n\n| Parameter Name |                      Type                     | Default Value |    Description     |\n|:--------------:|:---------------------------------------------:|:-------------:|:------------------:|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |     `None`    | The element object or text locator to click on |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n---\n\n### 📌 `hold()`\n\nThis method is used to hold down the left mouse button without releasing it, and can be preceded by moving to an element.\n\n| Parameter Name |                      Type                     | Default Value |    Description     |\n|:--------------:|:---------------------------------------------:|:-------------:|:------------------:|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |     `None`    | The element object or text locator to hold down the button on |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n**Example:**\n\n```python\nac.hold('#div1')\n```\n\n---\n\n### 📌 `release()`\n\nThis method is used to release the left mouse button, and can be preceded by moving to an element.\n\n| Parameter Name |                      Type                     | Default Value |    Description     |\n|:--------------:|:---------------------------------------------:|:-------------:|:------------------:|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |     `None`    | The element object or text locator to release the button on |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n**Example:** Move to an element and then release the left mouse button\n\n```python\nac.release('#div1')\n```\n\n---\n\n### 📌 `r_hold()`\n\nThis method is used to hold down the right mouse button without releasing it, and can be preceded by moving to an element.\n\n| Parameter Name |                      Type                     | Default Value |    Description     |\n|:--------------:|:---------------------------------------------:|:-------------:|:------------------:|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |     `None`    | The element object or text locator to hold down the button on |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n---\n\n### 📌 `r_release()`\n\nThis method is used to release the right mouse button, and can be preceded by moving to an element.\n\n| Parameter Name |                      Type                     | Default Value |    Description     |\n|:--------------:|:---------------------------------------------:|:-------------:|:------------------:|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |     `None`    | The element object or text locator to release the button on |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `Actions` | The action chain object itself |\n\n---\n\n### 📌 `m_hold()`\n\nThis method is used to hold down the middle mouse button. You can move to the element before holding.\n\n|   Parameter    |           Type           | Default | Description                        |\n|:-------------:|:------------------------:|:-------:|-----------------------------------|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |  `None` | The element object or text locator to be held |\n\n|  Return Type  | Description                   |\n|:------------:|------------------------------|\n|  `Actions`   | The action chain object itself |\n\n---\n\n### 📌 `m_release()`\n\nThis method is used to release the middle mouse button. You can move to the element before releasing.\n\n|   Parameter    |           Type           | Default | Description                          |\n|:-------------:|:------------------------:|:-------:|-------------------------------------|\n|   `on_ele`    | `ChromiumElement`<br/>`str` |  `None` | The element object or text locator to be released |\n\n|   Return Type   | Description                    |\n|:--------------:|-------------------------------|\n|   `Actions`    | The action chain object itself |\n\n---\n\n## ✅️ Scroll the Mouse Wheel\n\n### 📌 `scroll()`\n\nThis method is used to scroll the mouse wheel. You can move to the element before scrolling.\n\n|   Parameter   |  Type  | Default | Description                                        |\n|:------------:|:-----:|:------:|---------------------------------------------------|\n|  `delta_x`   | `int` |  `0`   | The change of the x-axis of the mouse wheel scrolling, positive when scrolling to the right and negative to the left |\n|  `delta_y`   | `int` |  `0`   | The change of the y-axis of the mouse wheel scrolling, positive when scrolling down and negative when scrolling up |\n|   `on_ele`   | `ChromiumElement`<br/>`str` | `None` | The element object or text locator to be scrolled |\n\n| Return Type  | Description                   |\n|:------------:|------------------------------|\n|  `Actions`   | The action chain object itself |\n\n---\n\n## ✅️ Keyboard Keys and Text Input\n\n### 📌 `key_down()`\n\nThis method is used to press a key on the keyboard. Non-string keys (such as ENTER) can input their names or use the Keys class to obtain them.\n\n| Parameter |  Type  | Default | Description                                            |\n|:---------:|:-----:|:------:|-------------------------------------------------------|\n|   `key`   | `str` | Required  | The name of the key, or the key value obtained from `Keys` class |\n\n|  Return Type  | Description                   |\n|:------------:|------------------------------|\n|  `Actions`   | The action chain object itself |\n\n**Example:** Press the ENTER key\n\n```python\nfrom DrissionPage.common import Keys\n\nac.key_down('ENTER')  # Input the key name\n\nac.key_down(Keys.ENTER)  # Get the key from Keys\n```\n\n---\n\n### 📌 `key_up()`\n\nThis method is used to release a key on the keyboard. Non-string keys (such as ENTER) can input their names or use the Keys class to obtain them.\n\n| Parameter |  Type  | Default | Description                                            |\n|:---------:|:-----:|:------:|-------------------------------------------------------|\n|   `key`   | `str` | Required  | The name of the key, or the key value obtained from `Keys` class |\n\n|  Return Type  | Description                   |\n|:------------:|------------------------------|\n|  `Actions`   | The action chain object itself |\n\n**Example:** Release the ENTER key\n\n```python\nfrom DrissionPage.common import Keys\n\nac.key_up('ENTER')  # Input the key name\n\nac.key_up(Keys.ENTER)  # Get the key from Keys\n```\n\n---\n\n### 📌 `type()`\n\nThis method is used to enter text or keys as if typing on the keyboard. Combining keys or entering multiple segments of text.\n\nOnly supports keys that exist on the keyboard. For other text input, use `actions.input()`.\n\n|  Parameter  |           Type           | Default | Description                                                    |\n|:----------:|:------------------------:|:-------:|----------------------------------------------------------------|\n|   `keys`   | `str`<br/>`list`<br/>`tuple` | Required  | The text or keys to be entered. Multiple segments or combination keys can be passed in as a `list` or `tuple` |\n\n|  Return Type  | Description                   |\n|:------------:|------------------------------|\n|  `Actions`   | The action chain object itself |\n\n**Example:**\n\n```python\n# Enter a segment of text\nac.type('text')\n\n# Enter multiple segments of text\nac.type(('ab', 'cd'))\n\n# Move the cursor to the left by one character and then enter text\nac.type((Keys.LEFT, 'abc'))\n```\n\n---\n\n### 📌 `input()`\n\nThis method is used to enter a segment or multiple segments of text, or keys. Combining keys or entering multiple segments of text.\n\nMultiple segments or combination keys are passed in as a list.\n\n|  Parameter  |           Type           | Default | Description                                                    |\n|:----------:|:------------------------:|:-------:|----------------------------------------------------------------|\n|   `text`   | `str`<br/>`list`<br/>`tuple` | Required  | The text or keys to be entered. Multiple segments or combination keys can be passed in as a `list` or `tuple` |\n\n|  Return Type  | Description                   |\n|:------------:|------------------------------|\n|  `Actions`   | The action chain object itself |\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\n\np = ChromiumPage()\np.get('https://www.baidu.com')\np.actions.click('#kw').input('DrissionPage')\n```\n\n---\n\n## ✅️ Waiting\n\n### 📌 `wait()`\n\nThis method is used to pause the action chain.\n\n| Parameter |  Type  | Default | Description                            |\n|:---------:|:-----:|:------:|---------------------------------------|\n| `second`  | `float` | Required  | The number of seconds to wait |\n\n|  Return Type  | Description                   |\n|:------------:|------------------------------|\n|  `Actions`   | The action chain object itself |\n\n**Example:** Pause for 3 seconds\n\n```python\nac.wait(3)\n```\n\n---\n\n## ✅️ Example\n\n### 📌 Simulate pressing ctrl+a\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom DrissionPage.common import Keys, Actions\n\n# Create a page\npage = ChromiumPage()\n# Create an Actions object\nac = Actions(page)\n\n# Move the mouse to the <input> element\nac.move_to('tag:input')\n# Click the mouse to place the cursor in the element\nac.click()\n# Press the ctrl key\nac.key_down(Keys.CTRL)\n# Type 'a'\nac.type('a')\n# Release the ctrl key\nac.key_up(Keys.CTRL)\n```\n\nChain writing:\n\n```python\nac.click('tag:input').key_down(Keys.CTRL).type('a').key_up(Keys.CTRL)\n```\n\n---\n\n### 📌 Dragging an element\n\nDrag an element 300 pixels to the right:\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom DrissionPage.common import Actions\n\n# Create a page\npage = ChromiumPage()\n# Create an Actions object\nac = Actions(page)\n\n# Hold down the left mouse button on the element\nac.hold('#div1')\n# Move the mouse 300 pixels to the right\nac.right(300)\n# Release the left mouse button\nac.release()\n```\n\nDrag an element to another element:\n\n```python\nac.hold('#div1').release('#div2')\n```\n\n## ✅️ Built-in Actions in the Page Object\n\nThe `actions` attribute of the Page Object provides an Actions object dedicated to that page object.\n\nThe usage of this object is the same as described above.\n\nThe only difference is that the built-in Actions object will wait for the page to finish loading before executing, while the external one will not.\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.actions.move_to((300, 500)).hold().move(300).release()\n```\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/browser_options.md",
    "content": "🚤 Browser Startup Settings\n---\n\nThe browser's startup configuration is very complicated. This library uses the `ChromiumOptions` class to manage startup configurations and provides built-in interfaces for commonly used configurations.\n\n:::warning Note\n    This object can only be used for browser startup. After the browser is started, modifying this configuration has no effect. When taking over an already opened browser, the startup configuration is also invalid.\n:::\n\n## ✅️️ Creating Objects\n\n### 📌 Import\n\n```python\nfrom DrissionPage import ChromiumOptions\n```\n\n---\n\n### 📌 `ChromiumOptions`\n\nThe `ChromiumOptions` object is used to manage browser initialization configurations. Configurations can be read from a configuration file for initialization.\n\n| Initialization Parameter |       Type       | Default Value | Description                                                  |\n| :----------------------: | :--------------: | :-----------: | ------------------------------------------------------------ |\n|       `read_file`        |      `bool`      |    `True`     | Whether to read configurations from an ini file.<br/>If `False`, default configurations will be used. |\n|        `ini_path`        | `Path`<br/>`str` |    `None`     | The path to the ini file. If `None`, the built-in ini file will be used. |\n\nCreating the configuration object:\n\n```python\nfrom DrissionPage import ChromiumOptions\n\nco = ChromiumOptions()\n```\n\nBy default, the `ChromiumOptions` object reads configurations from an ini file. If the `read_file` parameter is set to `False`, default configurations will be used.\n\n---\n\n## ✅️️ Usage\n\nAfter creating the configuration object, you can adjust the configuration content and pass it as a parameter when creating a page object. The page object will initialize the browser based on the configuration object.\n\nThe configuration object supports chaining operations.\n\n```python\nfrom DrissionPage import WebPage, ChromiumOptions\n\n# Creating the configuration object (reading configurations from an ini file by default)\nco = ChromiumOptions()\n# Setting not to load images and mute\nco.no_imgs(True).mute(True)\n\n# Creating the page object with this configuration\npage = WebPage(chromium_options=co)\n```\n\n```python\nfrom DrissionPage import ChromiumOptions, ChromiumPage\n\nco = ChromiumOptions()\nco.incognito()  # Incognito mode\nco.headless()  # Headless mode\nco.set_argument('--no-sandbox')  # No sandbox mode\npage = ChromiumPage(co)\n```\n\n---\n\n## ✅️️ Command Line Arguments\n\nThe Chromium-based browsers have a series of startup configurations that start with `--`. They can be passed in when creating the browser to control browser behavior and initial state.\n\nThere are many startup parameters. For details, see: [List of Chromium Command Line Switches](https://peter.sh/experiments/chromium-command-line-switches/)\n\nThe `set_argument()` and `remove_argument()` methods are used to set command line arguments for browser startup.\n\n### 📌 `set_argument()`\n\nThis method is used to set a startup argument.\n\n|   Parameter   |         Type           |  Default Value  | Description                                        |\n|:-------------:|:----------------------:|:---------------:|----------------------------------------------------|\n|     `arg`     |         `str`          |      Required         | The name of the startup argument.                                             |\n|    `value`    |     `str`<br/>`None`<br/>`False`  |      `None`       | The value of the argument. For arguments with values, pass the desired value. For arguments without values, pass `None`.<br/>If `False` is passed, the argument will be removed. |\n\n|     Return Type         | Description                                        |\n|:----------------------:|----------------------------------------------------|\n|   `ChromiumOptions`   | The configuration object itself. |\n\n**Example:** Setting arguments with and without values\n\n```python\n# Setting to start in maximized mode\nco.set_argument('--start-maximized')\n# Setting the initial window size\nco.set_argument('--window-size', '800,600')\n# Opening the browser in guest mode\nco.set_argument('--guest')\n```\n\n---\n\n### 📌 `remove_argument()`\n\nThis method is used to remove a startup argument from the configuration. Simply pass the argument name, no value is needed.\n\n|    Parameter    |          Type          | Default Value  | Description                                        |\n|:---------------:|:----------------------:|:--------------:|----------------------------------------------------|\n|      `arg`      |         `str`          |    Required         | The name of the argument to be removed.                                             |\n\n|     Return Type         | Description                                        |\n|:----------------------:|----------------------------------------------------|\n|   `ChromiumOptions`   | The configuration object itself. |\n\n**Example:** Removing arguments with and without values\n\n```python\n# Removing the --start-maximized argument\nco.remove_argument('--start-maximized')\n# Removing the --window-size argument\nco.remove_argument('--window-size')\n```\n\n---\n\n## ✅️️ Running Path and Port\n\nThis section is for settings related to browser path, user folder path, and port.\n\n### 📌 `set_browser_path()`\n\nThis method is used to set the path to the browser executable.\n\n|     Parameter   |        Type        |  Default Value   | Description                                      |\n|:---------------:|:-----------------:|:----------------:|--------------------------------------------------|\n|      `path`     | `str`<br/>`Path`  |      Required          | The path to the browser executable file.          |\n\n|    Return Type       | Description                                      |\n|:-------------------:|--------------------------------------------------|\n|   `ChromiumOptions` | The configuration object itself. |\n\nIf the passed string is not a path to a browser executable file, the default path will be used.\n\n---\n\n### 📌 `set_tmp_path()`\n\nThis method is used to set the path for temporary files.\n\n|     Parameter   |        Type        |  Default Value   | Description                                      |\n|:---------------:|:-----------------:|:----------------:|--------------------------------------------------|\n|      `path`     | `str`<br/>`Path`  |      Required          | The path for temporary files.          |\n\n|    Return Type       | Description                                      |\n|:-------------------:|--------------------------------------------------|\n|   `ChromiumOptions` | The configuration object itself. |\n\n---\n\n### 📌 `set_local_port()`\n\nThis method is used to set the local startup port.\n\n| Parameter Name |     Type    | Default Value | Description |\n|:--------------:|:-----------:|:-------------:|-------------|\n|    `port`      | `str`<br/>`int` |    Required   | Port number |\n\n| Return Type         | Description |\n|---------------------|-------------|\n| `ChromiumOptions`  | Configuration object itself |\n\n---\n\n### 📌 `set_address()`\n\nThis method is used to set the browser address in the format 'ip:port'.\n\nIt is mutually exclusive with `set_local_port()`.\n\n| Parameter Name |  Type  | Default Value | Description |\n|:--------------:|:-----:|:-----:|------------|\n|   `address`    | `str` | Required | Browser address |\n\n| Return Type         | Description |\n|---------------------|-------------|\n| `ChromiumOptions`  | Configuration object itself |\n\n---\n\n### 📌 `auto_port()`\n\nThis method is used to set whether to use an automatically assigned port and start a new browser.\n\nIf set to `True`, the program will automatically find an available port and create a folder in the specified path or the system temporary folder to store browser data.\n\nSince the port and user folder are unique, browsers started in this way will not conflict with each other, but they cannot take over the same browser when starting the program multiple times.\n\nThe `set_local_port()`, `set_address()`, and `set_user_data_path()` methods will override `auto_port()`, i.e., the most recent call takes effect.\n\n:::warning Note\n    `auto_port()` supports multithreading but not multiprocessing.  \n    When using multiprocessing, you can specify the port range for each process using the `scope` parameter to avoid conflicts.\n:::\n\n|   Parameter Name  |          Type          | Default Value |           Description           |\n|:-----------------:|:---------------------:|:-------------:|---------------------------------|\n|     `on_off`      |         `bool`         |     `True`    | Whether to enable automatic allocation of port and user folder |\n|    `tmp_path`     | `str` `Path` object |     `None`    | Temporary file storage path. If `None`, it is saved in the system temporary folder. This parameter is invalid when `on_off` is `False` |\n|      `scope`      |   `Tuple[int, int]`    |     `None`    | Specify the port range, excluding the last number. If `None`, use `[9600-19600)` |\n\n| Return Type         | Description |\n|---------------------|-------------|\n| `ChromiumOptions`  | Configuration object itself |\n\n**Example:**\n\n```python\nco.auto_port(True)\n```\n\n:::warning Note\n    Once this feature is enabled, the port and a new temporary user data folder will be obtained. If you save the configuration to an ini file using the `save()` method at this time, the settings in the ini file will be overridden by the port and folder path. This override does not have a significant impact on usage.\n:::\n\n---\n\n### 📌 `set_user_data_path()`\n\nThis method is used to set the user folder path. The user folder is used to store traces left by the account logged in the browser when using the browser, including setting options.\n\nUsually, the name of the user folder is `User Data`. For the Chrome browser in Windows installed by default, this folder is located at `%USERPROFILE%\\AppData\\Local\\Google\\Chrome\\User Data\\`, which is inside the user directory of the current system login. The actual situation may vary. Please enter `chrome://version/` in the browser to check the `Profile Path` or `User Data Directory`. If you want to use independent user information, you can copy the entire `User Data` directory to another custom location and use the `set_user_data_path()` method in the code to fill in the custom location path. This way, you can use an independent user folder.\n\n| Parameter Name |      Type       | Default Value |    Description    |\n|:--------------:|:--------------:|:-------------:|------------------|\n|     `path`     | `str`  `Path` |    Required   | User folder path |\n\n\n\n| Return Type         | Description |\n|---------------------|-------------|\n| `ChromiumOptions`  | Configuration object itself |\n\n---\n\n### 📌 `use_system_user_path()`\n\nThis method sets whether to use the default user folder of the system-installed browser.\n\n|  Parameter Name  |  Type  | Default Value |     Description     |\n|:----------------:|:-----:|:-------------:|---------------------|\n|     `on_off`     | `bool` |    `True`     | Boolean representing the on/off switch |\n\n| Return Type         | Description |\n|---------------------|-------------|\n| `ChromiumOptions`  | Configuration object itself |\n\n---\n\n### 📌 `set_cache_path()`\n\nThis method is used to set the cache path.\n\n| Parameter Name |      Type       | Default Value | Description |\n|:--------------:|:--------------:|:-------------:|-------------|\n|     `path`     | `str`<br/>`Path` |    Required   | Cache path |\n\n| Return Type         | Description |\n|---------------------|-------------|\n| `ChromiumOptions`  | Configuration object itself |\n\n---\n\n### 📌 `existing_only()`\n\nThis method sets whether to only use an already started browser. If failed to connect to the target browser, an exception will be thrown and a new browser will not be started.\n\n|  Parameter Name  |  Type  | Default Value |     Description     |\n|:----------------:|:-----:|:-------------:|---------------------|\n|     `on_off`     | `bool` |    `True`     | Boolean representing the on/off switch |\n\n## 翻译 private_upload\\default_user\\2024-01-24-17-01-44\\browser_options.md.part-2.md\n\n# ✅️️ Using Plugins\n\n`add_extension()` and `remove_extensions()` are used to set the plugins to be loaded when the browser starts. You can specify an unlimited number of plugins.\n\n### 📌 `add_extension()`\n\nThis method is used to add a plugin to the browser.\n\n| Parameter | Type            | Default | Description |\n|-----------|-----------------|---------|-------------|\n| `path`    | `str`<br/>`Path` | Required | Plugin path |\n\n| Return Type      | Description          |\n|------------------|----------------------|\n| `ChromiumOptions` | The configuration object itself |\n\n:::tip Tips\n    According to the author's experience, it is more stable to unzip the plugin files into a separate folder and then point the plugin path to this folder.\n:::\n\n**Example:**\n\n```python\nco.add_extension(r'D:\\SwitchyOmega')\n```\n\n---\n\n### 📌 `remove_extensions()`\n\nThis method is used to remove all saved plugin paths in the configuration object. if you want to remove specific plugins, please remove all plugins first and then re-add the plugins you need.\n\n**Parameters:** None\n\n**Return:** The configuration object itself\n\n```python\nco.remove_extensions()\n```\n\n---\n\n# ✅️️ User File Settings\n\nIn addition to startup parameters, a large amount of configuration information is saved in the browser's `preferences` file. If you want to use a separate user file for configuration information, refer to the [`set_user_data_path()`](https://g1879.gitee.io/drissionpagedocs/ChromiumPage/browser_options/#set_user_data_path) method on this page.\n\n:::warning Note\n    The `preferences` file is the configuration file for Chromium-based browsers, which is completely different from DrissionPage's `configs.ini`.\n:::\n\nThe following methods are used to configure browser user files.\n\n### 📌 `set_user()`\n\nThe Chromium browser supports multiple user configurations, and we can choose which one to use. The default is `'Default'`.\n\n| Parameter | Type   | Default     | Description               |\n|-----------|--------|-------------|---------------------------|\n| `user`    | `str`  | `'Default'` | User profile folder name  |\n\n| Return Type      | Description          |\n|------------------|----------------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nco.set_user(user='Profile 1')\n```\n\n---\n\n### 📌 `set_pref()`\n\nThis method is used to set a configuration item in the user profile.\n\nWhere can all the configuration items be found? The author couldn't find them either. Please let me know if you know. Thank you.\n\n| Parameter | Type   | Default | Description    |\n|-----------|--------|---------|----------------|\n| `arg`     | `str`  | Required | Setting name   |\n| `value`   | `str`  | Required | Setting value  |\n\n| Return Type      | Description          |\n|------------------|----------------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\n# Disable all pop-up windows\nco.set_pref(arg='profile.default_content_settings.popups', value='0')\n# Hide the prompt to save passwords\nco.set_pref('credentials_enable_service', False)\n```\n\n---\n\n### 📌 `remove_pref()`\n\nThis method is used to delete a `pref` configuration item in the current configuration object.\n\n| Parameter | Type   | Default | Description    |\n|-----------|--------|---------|----------------|\n| `arg`     | `str`  | Required | Setting name   |\n\n| Return Type      | Description          |\n|------------------|----------------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nco.remove_pref(arg='profile.default_content_settings.popups')\n```\n\n---\n\n### 📌 `remove_pref_from_file()`\n\nThis method is used to delete a configuration item in the user profile. Note that it is different from the previous method. If a certain item already exists in the user profile, it cannot be deleted using `remove_pref()`, but can only be deleted using `remove_pref_from_file()`.\n\n| Parameter | Type   | Default | Description    |\n|-----------|--------|---------|----------------|\n| `arg`     | `str`  | Required | Setting name   |\n\n| Return Type      | Description          |\n|------------------|----------------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nco.remove_pref_from_file(arg='profile.default_content_settings.popups')\n```\n\n---\n\n# ✅️️ Running Parameter Settings\n\nThe parameters required for the page object to run can also be set in `ChromiumOptions`.\n\n### 📌 `set_timeouts()`\n\nThis method is used to set several timeout times in seconds. For the usage of timeout, refer to the Usage section.\n\n| Parameter | Type   | Default | Description                                                                                                                                                                      |\n|-----------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `base`    | `float` | `None`  | Default timeout, used for element waiting, alert waiting, `WebPage`'s s mode connection, etc.  In scenes other than the following two parameters, this setting is used by default. |\n| `pageLoad`   | `float` | `None`  | Page loading timeout                                                                                                                              |\n| `script`  | `float` | `None`  | JavaScript execution timeout                                                                                                                      |\n\n| Return Type      | Description          |\n|------------------|----------------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nco.set_timeouts(base=10)\n```\n\n---\n\n### 📌 `set_retry()`\n\nThis method is used to set the number of retries and interval when the page connection times out.\n\nThis method is used to set whether to disable JavaScript.\n\n| Parameter Name | Type  | Default Value | Description |\n|:--------------:|:-----:|:-------------:|-------------|\n|   `on_off`     | `bool`|     `True`    | `True` to enable, `False` to disable |\n\n| Return Type     | Description |\n|-----------------|-------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nco.no_js(True)\n```\n\nThis method is used to set whether JavaScript is disabled.\n\n| Parameter Name | Type  | Default Value | Description |\n|--------------|------|--------------|------------|\n| `on_off`     | `bool` | `True`       | `True` and `False` indicate on or off |\n\n| Return Type      | Description |\n|------------------|-------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nco.no_js(True)\n```\n\n---\n\n### 📌 `mute()`\n\nThis method is used to set whether to mute.\n\n| Parameter Name | Type  | Default Value | Description |\n|--------------|------|--------------|------------|\n| `on_off`     | `bool` | `True`       | `True` and `False` indicate on or off |\n\n| Return Type      | Description |\n|------------------|-------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nco.mute(True)\n```\n\n---\n\n### 📌 `set_user_agent()`\n\nThis method is used to set the user agent.\n\n|   Parameter Name    | Type  | Default Value | Description |\n|--------------------|-------|--------------|------------|\n| `user_agent`       | `str` | Required     | user agent text |\n\n| Return Type      | Description |\n|------------------|-------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nco.set_user_agent(user_agent='Mozilla/5.0 (Macintos.....')\n```\n\n---\n\n### 📌 `set_paths()`\n\nThis method is used to set various path information. Set the paths with input values, and ignore those with `None`.\n\nThe functionality of this method is repetitive with the previously introduced path setting methods, but integrates several methods together.\n\n|    Parameter Name    |        Type        | Default Value | Description |\n|----------------------|--------------------|---------------|-------------|\n|  `browser_path`      | `str`<br/>`Path`   | `None`        | The path of the browser executable file |\n|  `local_port`        | `str`<br/>`int`    | `None`        | The local port number that the browser will use |\n|  `address`           | `str`              | `None`        | The browser address, e.g. 127.0.0.1:9222. If set together with` local_port`, it will override the value of `local_port` |\n|  `download_path`     | `str`<br/>`Path`   | `None`        | The default save path for downloaded files |\n|  `user_data_path`    | `str`<br/>`Path`   | `None`        | The path of the user data folder |\n|  `cache_path`        | `str`<br/>`Path`   | `None`        | The path of the cache |\n\n| Return Type      | Description |\n|------------------|-------------|\n| `ChromiumOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nco.set_paths(local_port=9333, user_data_path=r'D:\\tmp')\n```\n\n---\n\n## ✅️️ Save settings to file\n\nThe ini file is the configuration file of DrissionPage, which persistently records some configuration parameters. You can save different configurations to separate ini files in order to adapt to different scenarios.\n\n### 📌 `save()`\n\nThis method is used to save configuration items to an ini file.\n\n| Parameter Name |       Type        | Default Value | Description |\n|--------------|----------------|---------------|-------------|\n| `path`       | `str`<br/>`Path` | `None`        | The path of the ini file. If `None` is passed in, it will be saved to the currently read configuration file |\n\n| Return Type  | Description |\n|--------------|-------------|\n| `str`        | The absolute path of the saved ini file |\n\n**Example:**\n\n```python\n# Save the currently read ini file\nco.save()\n\n# Save the current configuration to the specified path\nco.save(path=r'D:\\tmp\\settings.ini')\n```\n\nIf the custom ini file path was not specified with ChromiumPage() before, then the default ini file will be used. This is when `save()` is used, its functionality is consistent with `save_to_default()`, both of which save to the default configuration file.\n\n---\n\n### 📌 `save_to_default()`\n\nThis method is used to save configuration items to a fixed default ini file. The default ini file refers to the one built into DrissionPage.\n\n**Parameters:** None\n\n| Return Type  | Description |\n|--------------|-------------|\n| `str`        | The absolute path of the saved ini file |\n\n**Example:**\n\n```python\nco.save_to_default()\n```\n\nBy default, the default ini file is located in the Python installation directory at `Lib\\site-packages\\DrissionPage\\_configs\\configs.ini`. Please do not modify the default ini file without necessity. For the initial contents of the ini file, please [click this link](https://g1879.gitee.io/drissionpagedocs/advance/ini_file/).\n\n---\n\n## ✅️️ `ChromiumOptions` Properties\n\n### 📌 `address`\n\nThis property is the address of the browser to be controlled, in the format of ip:port, default is `'127.0.0.0:9222'`.\n\n**Type:** `str`\n\n---\n\n### 📌 `browser_path`\n\nThis property returns the path to the browser executable file.\n\n**Type:** `str`\n\n---\n\n### 📌 `user_data_path`\n\nThis property returns the path of the user data folder.\n\n**Type:** `str`\n\n---\n\n### 📌 `tmp_path`\n\nThis property returns the path of the temporary folder, which can be used to save the automatically assigned user folder path.\n\n**Type:** `str`\n\n---\n\n### 📌 `download_path`\n\nThis property returns the path of the default download path file.\n\n**Type:** `str`\n\n---\n\n### 📌 `user`\n\nThis property returns the name of the user's configuration folder.\n\n**Type:** `str`\n\n---\n\n### 📌 `page_load_strategy`\n\nThis property returns the page loading strategy. There are three options: `'normal'`, `'eager'`, and `'none'`.\n\n**Type:** `str`\n\n---\n\n### 📌 `timeouts`\n\nThis property returns the timeout settings. It includes three options: `'base'`, `'pageLoad'`, `'script'`.\n\n**Type:** `dict`\n\n```python\nprint(co.timeouts)\n```\n\n**Output:**\n\n```shell\n{\n    'base': 10,\n    'pageLoad': 30,\n    'script': 30\n}\n```\n\n---\n\n### 📌 `retry_times`\n\nThis property returns the number of retries when the connection fails.\n\n**Type:** `int`\n\n---\n\n### 📌 `retry_interval`\n\nThis property returns the retry interval (in seconds) when the connection fails.\n\n**Type:** `float`\n\n---\n\n### 📌 `proxy`\n\nThis property returns the proxy settings.\n\n**Type:** `str`\n\n---\n\n### 📌 `arguments`\n\nThis property returns the browser startup arguments as a list.\n\n**Type:** `list`\n\n---\n\n### 📌 `extensions`\n\nThis property returns the paths of the extensions to be loaded as a list.\n\n**Type:** `list`\n\n---\n\n### 📌 `preferences`\n\nThis property returns the user preference configuration.\n\n**Type:** `dict`\n\n---\n\n### 📌 `system_user_path`\n\nThis property returns whether to use the browser's user folder as per the system.\n\n**Type:** `bool`\n\n---\n\n### 📌 `is_existing_only`\n\nThis property returns whether to only use already opened browsers.\n\n**Type:** `bool`\n\n---\n\n### 📌 `is_auto_port`\n\nThis property returns whether to only use automatically assigned ports and user folder paths.\n\n**Type:** `bool`\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/create_page_object.md",
    "content": "🚤 Create Page Object\n---\n\nBoth `ChromiumPage` and `WebPage` objects can send and receive data packets in d-mode. This section only introduces the creation of the `ChromiumPage` object, and will be further discussed in the chapter of `WebPage`.\n\nUse `ChromiumPage()` to create a page object. Depending on different configurations, you can take over an already open browser or start a new browser.\n\nWhen the program ends, the opened browser will not close automatically so that it can be used for the next program run. Beginners using headless mode should be aware that when the program is closed, the browser process is still running, just not visible.\n\nBoth `ChromiumPage` and `WebPage` objects are singletons and there can only be one object per browser. The same object will be obtained when repeating the use of `ChromiumPage` for the same browser.\n\n## ✅️ Initialization Parameters for `ChromiumPage`\n\n|   Initialization Parameters  |              Type              | Default | Explanation                                                                                 |\n|:---------------------------:|:-----------------------------:|:-------:| ------------------------------------------------------------------------------------------- |\n|       `addr_or_opts`        | `str`<br/>`int`<br/>`ChromiumOptions` |  `None` | Browser startup configuration or takeover information.<br/>When passing in a string with the format 'ip:port', a port number, or a `ChromiumOptions` object, the browser will be started or taken over according to the configuration;<br/>If `None`, the browser will be started using the configuration file. |\n|          `tab_id`           |            `str`              |  `None` | The ID of the tab to be controlled. If `None`, the active tab will be controlled.               |\n|         `timeout`           |           `float`             |  `None` | The overall timeout period. If `None`, it will be read from the configuration file, with a default value of 10. |\n\n---\n\n## ✅️ Create Directly\n\nThis method has the simplest code. The program will read the configuration from the default ini file and generate a page object automatically.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\n```\n\nWhen creating a `ChromiumPage` object, the browser will be started on the specified port or take over an existing browser on that port.\n\nBy default, the program uses port 9222 and the path to the browser executable is `'chrome'`. If the browser executable is not found in the path, on Windows, the program will search for it in the registry.\n\nIf it is still not found, manual configuration using the next method is required.\n\n:::warning Note\n    Programs created using this method cannot be directly packaged because they rely on the ini file. Please refer to the methods in the section \"Packaging Programs\".\n:::\n\n:::tip Tips\n    You can modify the configuration in the ini file to start all programs according to your needs. See the chapter \"Startup Configuration\" for more details.\n:::\n\n---\n\n## ✅️ Creating Using Configuration Information\n\nIf you need to start the browser in a specified way, you can use `ChromiumOptions` for configuration. It is a class specifically used to set the initial status of the browser and comes with built-in common configurations. See the section \"Browser Startup Configuration\" for detailed usage methods.\n\n### 📌 Usage\n\n`ChromiumOptions` is used to manage the configuration when creating the browser. It comes with built-in common configurations and supports chain operations. See the section \"Startup Configuration\" for detailed usage methods.\n\n|   Initialization Parameters  |              Type              | Default | Explanation                 |\n|:---------------------------:|:-----------------------------:|:-------:| -------------------------- |\n|       `read_file`           |          `bool`               |  `True` | Whether to read the configuration information from the ini file. If `False`, use the default configuration. |\n|       `ini_path`            |           `str`               |  `None` | The path of the file. If `None`, read the default ini file.   |\n\n:::warning Note\n    - Configuration objects only take effect when the browser is started.\n    - Modifying these configurations after the browser is created will have no effect.\n    - Changing the configuration when taking over an already opened browser will also have no effect.\n:::\n\n```python\n# Import ChromiumOptions\nfrom DrissionPage import ChromiumPage, ChromiumOptions\n\n# Create a browser configuration object and specify the browser path\nco = ChromiumOptions().set_browser_path(r'D:\\chrome.exe')\n# Create a page object using this configuration\npage = ChromiumPage(addr_or_opts=co)\n```\n\n---\n\n### 📌 Creating Directly with a Specified Address\n\n`ChromiumPage` can create a page directly by accepting the browser address in the format 'ip:port'.\n\nUsing this method, if the browser already exists, the program will directly take it over; if it does not exist, the program will read the configuration from the default ini file and start the browser on the specified port.\n\n```python\npage = ChromiumPage(addr_or_opts='127.0.0.1:9333')\n```\n\n---\n\n### 📌 Creating Using a Specified ini File\n\nThe above methods create objects by using configuration information saved in the default ini file. You can save an ini file to another location and specify to use it when creating objects.\n\n```python\nfrom DrissionPage import ChromiumPage, ChromiumOptions\n\n# Specify the ini file path when creating the configuration object\nco = ChromiumOptions(ini_path=r'./config1.ini')\n# Use this configuration object to create the page\npage = ChromiumPage(addr_or_opts=co)\n```\n\n---\n\n## ✅️ Taking Over an Already Opened Browser\n\nWhen creating a page object, as long as there is already a browser running on the specified address (ip: port), it will be directly taken over, regardless of how the browser was started.\n\n### 📌 Browser Started by the Program\n\nBy default, when creating a browser page object, it will automatically start a browser. As long as this browser is not closed, it will be taken over and continued to operate in the next program run (the configured ip: port information remains the same).\n\nThis method greatly facilitates program debugging, allowing the program to debug a specific function without having to restart every time.\n\n```python\nfrom DrissionPage import ChromiumPage\n\n# Create an object and start the browser. If the browser already exists, take over it.\npage = ChromiumPage()\n```\n\n---\n\n### 📌 Manually Opened Browser\n\nIf you need to manually open the browser before taking over, you can do the following:\n\n- Right-click on the browser icon and select Properties.\n\n- Add ` --remote-debugging-port=port number --remote-allow-origins=*` after the \"Target\" path (note that there is a space at the beginning).\n\n- Click OK.\n\n- Specify the browser being taken over by the program in the browser configuration.\n\nTarget path of the file shortcut:\n\n```\nD:\\chrome.exe --remote-debugging-port=9222 --remote-allow-origins=*\n```\n\nProgram code:\n\n```python\nfrom DrissionPage import ChromiumPage, ChromiumOptions\n\nco = ChromiumOptions().set_local_port(9222)\npage = ChromiumPage(addr_or_opts=co)\n```\n\n:::warning Note\n    When taking over the browser, only the `local_port` and `address` parameters are valid.\n:::\n\n---\n\n### 📌 Browser Started by bat File\n\nYou can write the target path setting of the previous method into a bat file (for Windows system), run the bat file to start the browser, and then take over with the program.\n\nCreate a new text file and enter the following content in it (change the path to your own computer's):\n\n```shell\n\"D:\\chrome.exe\" --remote-debugging-port=9222 --remote-allow-origins=*\n```\n\nSave it and change the extension to bat, then double-click to run it to start a browser on port 9222. The program code is the same as the previous method.\n\n---\n\n## ✅️ Multiple Browsers Coexistence\n\nIf you want to operate multiple browsers at the same time, or if you are using one of them to surf the Internet and controlling the rest automatically, you need to set separate **ports** and **user folders** for these browsers controlled by the program, otherwise conflicts may occur.\n\n### 📌 Specify Independent Ports and Data Folders\n\nEach browser to be started uses a separate `ChromiumOptions` object for configuration:\n\n```python\nfrom DrissionPage import ChromiumPage, ChromiumOptions\n\n# Create multiple configuration objects, each specifying a different port number and user folder path\ndo1 = ChromiumOptions().set_paths(local_port=9111, user_data_path=r'D:\\data1')\ndo2 = ChromiumOptions().set_paths(local_port=9222, user_data_path=r'D:\\data2')\n\n# Create multiple page objects\npage1 = ChromiumPage(addr_or_opts=do1)\npage2 = ChromiumPage(addr_or_opts=do2)\n\n# Each page object controls a browser\npage1.get('https://www.baidu.com')\npage2.get('http://www.163.com')\n```\n\n:::tip Tips\n    Each browser must set a separate port number and user folder, none of them can be missing.\n:::\n\n---\n\n### 📌 `auto_port()` Method\n\nThe `auto_port()` method of the `ChromiumOptions` object can be used to specify that the program creates a browser using an available port and a temporary user folder each time. Each browser must also use a separate `ChromiumOptions` object.\n\nHowever, browsers created using this method cannot be reused.\n\n:::tip Tips\n    `auto_port()` supports multi-threading, but not multi-processing.  \n    When using multi-processing, the `scope` parameter can be used to specify the range of ports used by each process to avoid conflicts.\n:::\n\n```python\nfrom DrissionPage import ChromiumPage, ChromiumOptions\n\nco1 = ChromiumOptions().auto_port()\nco2 = ChromiumOptions().auto_port()\n\npage1 = ChromiumPage(addr_or_opts=co1)\npage2 = ChromiumPage(addr_or_opts=co2)\n\npage1.get('https://www.baidu.com')\npage2.get('http://www.163.com')\n```\n\n---\n\n### 📌 Automatically Assign in ini File\n\nThe automatically assigned configuration can be recorded in an ini file, so there is no need to create `ChromiumOptions`, and each browser started is independent and does not conflict. However, like `auto_port()`, these browsers cannot be reused.\n\n```python\nfrom DrissionPage import ChromiumOptions\n\nChromiumOptions().auto_port().save()\n```\n\nThis piece of code records this configuration to the ini file. It only needs to be executed once, and if you want to close it, change the parameter to `False` and run it again.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage1 = ChromiumPage()\npage2 = ChromiumPage()\n\npage1.get('https://www.baidu.com')\npage2.get('http://www.163.com')\n```\n\n## ✅️ Use the System Browser's User Directory\n\nBy default, under the initial default configuration, the program will create an empty user directory for each port used, and use it each time it takes over, which effectively avoids browser conflicts.\n\nSometimes we want to use the default user folder of the system-installed browser in order to reuse user information and plugins, etc.\n\nWe can set it up like this:\n\n### 📌 Use `ChromiumOptions`\n\nConfigure it each time it is started using `ChromiumOptions`.\n\n```python\nfrom DrissionPage import ChromiumPage, ChromiumOptions\n\nco = ChromiumOptions().use_system_user_path()\npage = ChromiumPage(co)\n```\n\n### 📌 Use ini File\n\nRecord this configuration to an ini file, so you don't have to configure it every time.\n\n```python\nfrom DrissionPage import ChromiumOptions\n\nChromiumOptions().use_system_user_path().save()\n```\n\n### 📌 Handling conflicts\n\nIf conflicts occur with an already open browser, an exception will be thrown and the user will be notified to close the browser.\n\n```shell\nDrissionPage.errors.BrowserConnectError: \nFailed to connect to 127.0.0.1:9222.\nPlease make sure:\n1. The port belongs to a browser.\n2. '--remote-debugging-port=9222' flag has been added as a startup option.\n3. There is no conflict with an already open browser in the user's folder.\n4. For headless systems, '--headless=new' parameter should be added.\n5. For Linux systems, '--no-sandbox' startup parameter might also be required.\nYou can set the port and user folder path using ChromiumOptions.\n```\n\n---\n\n## ✅️ Creating a brand new browser\n\nBy default, the program reuses the previously used browser user data, including login data and browsing history.\n\nIf you want to open a completely new browser, you can use the following methods:\n\n### 📌 Using `auto_port()`\n\nAs mentioned before, by setting `auto_port()` method, each opened browser will be completely new.\n\nAn example can be seen in the previous section.\n\n---\n\n### 📌 Manually specifying the port and path\n\nTo open a completely new browser, specify an empty path for the browser user data folder and choose an available port.\n\n```python\nfrom DrissionPage import ChromiumPage, ChromiumOptions\n\nco = ChromiumOptions().set_local_port(9333).set_user_data_path(r'C:\\tmp')\npage = ChromiumPage(co)\n```\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/element_operation.md",
    "content": "🚤 Element Interaction\n---\n\nThis section introduces the interaction with browser elements. The browser element object is `ChromiumElement`.\n\n## ✅️️ Clicking Elements\n\n### 📌 `click()` and `click.left()`\n\nThese two methods have the same effect, used to left-click on elements. They can simulate clicking or use JavaScript to click.\n\n|    Parameter    |   Type    |  Default  | Description                                                                                      |\n|:----------:|:-------:|:-------:|------------------------------------------------------------------------------------------------|\n|   `by_js`   | `bool`  | `False` | Specifies the way to click.<br/>If `None`, simulate clicking if not obstructed, otherwise click with JS.<br/>If `True`, click directly with JS.<br/>If `False`, force simulated clicking, even if obstructed |\n|  `timeout`  | `float` |  `1.5`  | Timeout for simulating clicks, waiting for the element to become visible, available, and entering the viewport                                                                       |\n| `wait_stop` | `bool`  | `True`  | Whether to wait for the element to stop moving before clicking                                                                                  |\n\n|   Return   | Description                                    |\n|:-------:|---------------------------------------|\n| `False` | When `by_js` is `False` and the element is not available or visible, returns `False` |\n| `True`  | Returns `True` in all other cases                   |\n\n**Example:**\n\n```python\n# Simulate clicking on the element ele, click if obstructed\nele.click()\n\n# Click the element ele with JS, regardless of any coverings\nele.click(by_js=True)\n\n# If the element is not obstructed, simulate clicking, otherwise click with JS\nele.click(by_js=None)\n```\n\nBy default, `by_js` is `None`, which means simulated clicking is preferred. If there are obstructions, the element is not available, not visible, or unable to automatically enter the viewport, it will wait until the timeout is reached and then click with JS.\n\nWhen `by_js` is `False`, the program will force simulated clicking, even if the element is obstructed. If the element is not visible or available, it will return `False`. If the element cannot be automatically scrolled into the viewport, click with JS instead.\n\nWhen `by_js` is `True`, it is possible to ignore any obstructions. As long as the element is in the DOM, it can be clicked, but whether the element responds to the click depends on the architecture of the web page.\n\nThe elements can be flexibly operated as needed.\n\nBefore simulating a click, the program will attempt to scroll the element into the viewport.\n\nBy default, if a simulated click cannot be performed (the element cannot enter the viewport, is not available, or is hidden), a left-click will return `False`. However, it can also be set globally to throw an exception:\n\n```python\nfrom DrissionPage.common import Settings\n\nSettings.raise_click_failed = True\nele.click()  # Throw an exception if unable to click\n```\n\n---\n\n### 📌 `click.right()`\n\nThis method performs a right-click on the element.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n**Example:**\n\n```python\nele.click.right()\n```\n\n---\n\n### 📌 `click.middle()`\n\nThis method performs a middle-click on the element.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n**Example:**\n\n```python\nele.click.middle()\n```\n\n---\n\n### 📌 `click.multiple()`\n\nThis method performs multiple left-clicks on the element.\n\n|  Parameter   |  Type   | Default | Description   |\n|:-------:|:-----:|:---:|------|\n| `times` | `int` | `2` | Number of clicks |\n\n**Returns:** `None`\n\n---\n\n### 📌 `click.at()`\n\nThis method is used to click on the element with offsets relative to the top-left corner of the element. Clicking the middle of the element when `offset_x` and `offset_y` are not provided.   \nThe target of the click does not have to be on the element, negative values or values greater than the size of the element can be used to click areas near the element. Positive values are to the right and down, and negative values are to the left and up.\n\n|   Parameter   |   Type    |   Default    | Description                                                         |\n|:----------:|:-------:|:--------:|-------------------------------------------------------------------|\n| `offset_x` | `float` |  `None`  | The x-axis offset relative to the top-left corner of the element, positive to the right and down                                   |\n| `offset_y` | `float` |  `None`  | The y-axis offset relative to the top-left corner of the element, positive to the right and down                                   |\n|  `button`  |  `str`  | `'left'` | The button to click, pass in `'left'`, `'right'`, `'middle'`, `'back'`, `'forward'` |\n|  `count`   |  `int`  |   `1`    | Number of clicks                                                   |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\n# Click 50*50 above the element\nele.click.at(50, -50)\n\n# Click the upper middle part of the element, offset_x is 50 relative to the top-left corner to the right, offset_y stays at the middle of the element\nele.click.at(offset_x=50)\n\n# Same as click(), but without retrying\nele.click.at()\n```\n\n---\n\n## ✅️️ Inputting Content\n\n### 📌 `clear()`\n\nThis method is used to clear the text of the element, and can choose to simulate keystrokes or use JS.\n\nThis is a Markdown file, translate it into English, do not modify any existing Markdown commands:\n\n\nThe simulated keystroke method will automatically input the `ctrl-a-del` combination key to clear the text box, while the js method directly sets the value attribute of the element to `''`.\n\n| Parameter Name | Type   | Default Value | Description                                    |\n|:----------:|:-------:|:------------:|-------------------------------------------|\n| `by_js`    | `bool`  | `False`      | Whether to clear the text box by js method |\n\n**Return:** `None`\n\n**Example:**\n\n```python\nele.clear()\n```\n\n---\n\n### 📌 `input()`\n\nThis method is used to input text or combination keys to the element, and can also be used to input file paths to the upload control. You can choose whether to clear the element before inputting.\n\n| Parameter Name | Type   | Default Value | Description                                                                                               |\n|:----------:|:-------:|:------------:|------------------------------------------------------------------------------------------------------|\n| `vals`     | `Any`   | `False`      | Text value or keystroke combination<br/>When inputting a path string or a list of paths to the file upload control |\n| `clear`    | `bool`  | `True`       | Whether to clear the text box before inputting                                                        |\n| `by_js`    | `bool`  | `False`      | Whether to use js method for inputting, it cannot input combination keys when set to `True`              |\n\n**Return:** `None`\n\n:::tip Tips\n    - Some text boxes can receive the enter key instead of clicking the button, and you can directly add `'\\n'` to the end of the text.\n    - Non-`str` data will be automatically converted to `str`.\n:::\n\n**Example:**\n\n```python\n# Input text\nele.input('Hello world!')\n\n# Input text and press enter\nele.input('Hello world!\\n')\n```\n\n---\n\n### 📌 Input Combination Keys\n\nBefore using combination keys or special keys, you need to import the key class `Keys`.\n\n```python\nfrom DrissionPage.common import Keys\n```\n\nThen put the combination keys in a `tuple` and pass it to `element.input()`.\n\n```python\nele.input((Keys.CTRL, 'a', Keys.DEL))  # ctrl+a+del\n```\n\n---\n\n### 📌 `focus()`\n\nThis method is used to make the element get focus.\n\n**Parameter:** None\n\n**Return:** `None`\n\n---\n\n## ✅️️ Drag and Hover\n\n:::tip Tips\n    In addition to the methods mentioned below, this library also provides more flexible action chain functions, see the later sections for details.\n:::\n\n### 📌 `drag()`\n\nThis method is used to drag the element to a new position relative to the current position, and the speed can be set.\n\n| Parameter Name | Type    | Default Value | Description                                                     |\n|:----------:|:------:|:-------------:|----------------------------------------------------------------|\n| `offset_x` |  `int` |     `0`      | x-axis offset, positive when moving down or to the right         |\n| `offset_y` |  `int` |     `0`      | y-axis offset, positive when moving down or to the right         |\n| `duration` | `float`|    `0.5`     | Time used, in seconds, input `0` to reach instantaneously       |\n\n**Return:** `None`\n\n**Example:**\n\n```python\n# Drag the current element to the position 50*50, taking 1 second\nele.drag(50, 50, 1)\n```\n\n---\n\n### 📌 `drag_to()`\n\nThis method is used to drag the element to another element or a coordinate.\n\n|  Parameter Name |                           Type                          | Default Value | Description                  |\n|:------------:|:-----------------------------------------------------:|:-------------:|-----------------------------|\n| `ele_or_loc` | `ChromiumElement`<br/>`Tuple[int, int]`                |    Required   | Another element object or coordinate tuple |\n|  `duration`  |                      `float`                          |    `0.5`      | Time used, in seconds, input `0` to reach instantaneously |\n\n**Return:** `None`\n\n**Example:**\n\n```python\n# Drag ele1 to ele2\nele1 = page.ele('#div1')\nele2 = page.ele('#div2')\nele1.drag_to(ele2)\n\n# Drag ele1 to the position 50, 50 on the webpage\nele1.drag_to((50, 50))\n```\n\n---\n\n### 📌 `hover()`\n\nThis method is used to simulate hovering the mouse over the element, and can accept offsets, the offsets are relative to the upper left corner of the element. When no`offset_x` and `offset_y` values are passed, the hover is on the center of the element.\n\n| Parameter Name | Type   | Default Value | Description                                   |\n|:----------:|:-----:|:------------:|----------------------------------------------|\n| `offset_x` | `int` |    `None`    | x-axis offset relative to the upper left corner of the element, positive when moving down or to the right |\n| `offset_y` | `int` |    `None`    | y-axis offset relative to the upper left corner of the element, positive when moving down or to the right |\n\n**Return:** `None`\n\n**Example:**\n\n```python\n# Hover 50*50 above the element at the upper right corner\nele.hover(50, -50)\n\n# Hover in the middle of the element, x offset 50 relative to the upper left corner, y remains at the center of the element\nele.hover(offset_x=50)\n\n# Hover in the center of the element\nele.hover()\n```\n\n---\n\n## ✅️️ Modify Element\n\n### 📌 `set.innerHTML()`\n\nThis method is used to set the innerHTML content of the element.\n\n| Parameter Name | Type   | Default Value | Description                                    |\n|:----------:|:-----:|:------------:|--------------------------------------------|\n| `html`     | `str` |   Required   | HTML text                                  |\n\n**Return:** `None`\n\n---\n\n### 📌 `set.prop()`\n\nThis method is used to set the `property` attribute of the element.\n\n| Parameter Name | Type  | Default Value | Description |\n|:--------------:|:-----:|:-------------:|-------------|\n|     prop       | `str` |    Required   | Property name |\n|     value      | `str` |    Required   | Property value |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\nele.set.prop('value', 'Hello world!')\n```\n\n---\n\n### 📌 `set.attr()`\n\nThis method is used to set the `attribute` property of an element.\n\n| Parameter Name | Type  | Default Value | Description |\n|:--------------:|:-----:|:-------------:|-------------|\n|     attr       | `str` |    Required   | Attribute name |\n|     value      | `str` |    Required   | Attribute value |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\nele.set.attr('href', 'http://www.gitee.com')\n```\n\n---\n\n### 📌 `remove_attr()`\n\nThis method is used to remove an element's `attribute` property.\n\n| Parameter Name | Type  | Default Value | Description |\n|:--------------:|:-----:|:-------------:|-------------|\n|     attr       | `str` |    Required   | Attribute name |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\nele.remove_attr('href')\n```\n\n---\n\n### 📌 `check()`\n\nThis method is used to select or deselect an element.\n\n| Parameter Name | Type   | Default Value | Description |\n|:--------------:|:------:|:-------------:|-------------|\n|    uncheck     | `bool` |    `False`    | Whether to deselect |\n|    by_js       | `bool` |    `False`    | Whether to select with JS |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ Execute JavaScript code\n\n### 📌 `run_js()`\n\nThis method is used to execute JS code on an element, where `this` represents the element itself.\n\n|  Parameter Name |  Type  | Default Value | Description                                            |\n|:------------:|:-----:|:-------------:|-------------------------------------------------------|\n|    script    | `str` |    Required   | JS script text                                         |\n|    *args     |   -   |       -       | Arguments passed, corresponding to `arguments[0]`, `arguments[1]`, ... in the JS script |\n|   as_expr    | `bool`|    `False`    | Whether to run as an expression. When `True`, `args` parameter is invalid |\n|   timetout   |`float`|     `None`    | JS timeout, use the page `timeouts.script` setting when `None`|\n\n| Return Type | Description           |\n|:-----------:|-----------------------|\n|   `Any`     | Result of the script  |\n\n:::warning Note\n    Remember to include `return` in the JS code to get the result.\n:::\n\n**Example:**\n\n```python\n# Click on the element by executing JS\nele.run_js('this.click();')\n\n# Get the height of the element using JS\nheight = ele.run_js('return this.offsetHeight;')\n```\n\n---\n\n### 📌 `run_async_js()`\n\nThis method is used to execute JS code asynchronously on an element, where `this` represents the element itself.\n\n|  Parameter Name |  Type  | Default Value | Description                                            |\n|:------------:|:-----:|:-------------:|-------------------------------------------------------|\n|    script    | `str` |    Required   | JS script text                                         |\n|    *args     |   -   |       -       | Arguments passed, corresponding to `arguments[0]`, `arguments[1]`, ... in the JS script |\n|   as_expr    | `bool`|    `False`    | Whether to run as an expression. When `True`, `args` parameter is invalid |\n\n**Returns:** `None`\n\n---\n\n### 📌 `add_init_js()`\n\nThis method is used to add initialization script to be executed before any script is loaded on the page.\n\n|  Parameter Name |  Type  | Default Value | Description                                            |\n|:------------:|:-----:|:-------------:|-------------------------------------------------------|\n|    script    | `str` |    Required   | JS script text                                         |\n\n| Return Type | Description            |\n|:-----------:|------------------------|\n|    `str`    | ID of the added script |\n\n---\n\n### 📌 `remove_init_js()`\n\nThis method is used to remove initialization scripts. Use `None` for `script_id` to remove all.\n\n|  Parameter Name |  Type  | Default Value | Description |\n|:------------:|:-----:|:-------------:|-------------|\n|  script_id   | `str` |     `None`    | Script ID, `None` to remove all scripts |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ Element scrolling\n\nThe element scrolling functionality is hidden in the `scroll` attribute. It is used to scroll the contents of a scrollable container element or scroll the element itself into view.\n\n```python\n# Scroll to the bottom\nele.scroll.to_bottom()\n\n# Scroll to the rightmost\nele.scroll.to_rightmost()\n\n# Scroll down 200 pixels\nele.scroll.down(200)\n\n# Scroll to a specific position\nele.scroll.to_location(100, 300)\n\n# Scroll the page to make the element visible\nele.scroll.to_see()\n```\n\n---\n\n### 📌 `scroll.to_top()`\n\nThis method is used to scroll the element to the top, while the horizontal position remains unchanged.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.scroll.to_top()\n```\n\n---\n\n### 📌 `scroll.to_bottom()`\n\nThis method is used to scroll the element to the bottom, while the horizontal position remains unchanged.\n\nThis method is used to select an option from the dropdown list by its value.\n\n| Parameter Name |   Type   | Default Value | Description                 |\n|:-------------:|:-------:|:-----------:|---------------------------|\n|    `value`    | `str`<br/>`list`<br/>`tuple` |    Required   | The value of the option(s) to be selected. If a list or tuple is passed, multiple options can be selected. |\n|   `timeout`   |  `float`  |     `None`    | The timeout duration. If `None`, the default page timeout will be used. |\n\n| Return Type | Description   |\n|:-----------:|-------------|\n|    `bool`   | Whether the selection was successful. |\n\nThis method is used to select list items based on the `value` attribute. If it is a multiple selection list, you can select multiple items.\n\n|   Parameter   |             Type             | Default | Description                               |\n|:------------:|:---------------------------:|:-------:|-------------------------------------------|\n|    `value`   | `str`<br/>`list`<br/>`tuple` | Required | The `value` value used as the selection condition, passing in a `list` or `tuple` can select multiple items |\n|   `timeout`  |           `float`           |  `None`  | The timeout duration, `None` by default, uses the page timeout duration if not specified |\n\n| Return Type | Description |\n|:------------:|-------------|\n|    `bool`    | Whether the selection is successful or not |\n\n---\n\n### 📌 `select.by_index()`\n\nThis method is used to select list items based on their index, starting from `1`. If it is a multiple selection list, you can select multiple items.\n\n|   Parameter   |             Type             | Default | Description                                               |\n|:------------:|:---------------------------:|:-------:|-----------------------------------------------------------|\n|    `index`   | `int`<br/>`list`<br/>`tuple` | Required | The index of the item to be selected, passing in a `list` or `tuple` can select multiple items |\n|   `timeout`  |           `float`           |  `None`  | The timeout duration, `None` by default, uses the page timeout duration if not specified |\n\n| Return Type | Description |\n|:------------:|-------------|\n|    `bool`    | Whether the selection is successful or not |\n\n---\n\n### 📌 `select.by_locator()`\n\nThis method can be used to select option elements by using locators. If it is a multiple selection list, you can select multiple items.\n\n|   Parameter   |             Type             | Default | Description                                         |\n|:------------:|:---------------------------:|:-------:|-----------------------------------------------------|\n|   `locator`  | `str`<br/>`list`<br/>`tuple` | Required | The locator, passing in a `list` or `tuple` can select multiple items |\n|   `timeout`  |           `float`           |  `None`  | The timeout duration, `None` by default, uses the page timeout duration if not specified |\n\n| Return Type | Description |\n|:------------:|-------------|\n|    `bool`    | Whether the selection is successful or not |\n\n---\n\n### 📌 `select.by_option()`\n\nThis method is used to select single or multiple list items. If it is a multiple selection list, you can select multiple items.\n\n| Parameter  |                          Type                          | Default | Description                                                   |\n|:---------:|:-----------------------------------------------------:|:-------:|---------------------------------------------------------------|\n|  `option` | `ChromiumElement`<br/>`List[ChromiumElement]` | Required | The `<option>` element(s) or a list of such elements |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\nselect = page('t:select')\noption = select('t:option')\nselect.select.by_option(option)\n```\n\n---\n\n### 📌 `select.cancel_by_text()`\n\nThis method is used to cancel the selection of list items based on their text. If it is a multiple selection list, you can cancel multiple items.\n\n|   Parameter   |             Type             | Default | Description                                                     |\n|:------------:|:---------------------------:|:-------:|-----------------------------------------------------------------|\n|    `text`    | `str`<br/>`list`<br/>`tuple` | Required | The text used as the selection condition, passing in a `list` or `tuple` can select multiple items |\n|   `timeout`  |           `float`           |  `None`  | The timeout duration, `None` by default, uses the page timeout duration if not specified |\n\n| Return Type | Description |\n|:------------:|-------------|\n|    `bool`    | Whether the selection is successful or not |\n\n---\n\n### 📌 `select.cancel_by_value()`\n\nThis method is used to cancel the selection of list items based on the `value` attribute. If it is a multiple selection list, you can cancel multiple items.\n\n|   Parameter   |             Type             | Default | Description                                                     |\n|:------------:|:---------------------------:|:-------:|-----------------------------------------------------------------|\n|    `value`   | `str`<br/>`list`<br/>`tuple` | Required | The `value` value used as the selection condition, passing in a `list` or `tuple` can select multiple items |\n|   `timeout`  |           `float`           |  `None`  | The timeout duration, `None` by default, uses the page timeout duration if not specified |\n\n| Return Type | Description |\n|:------------:|-------------|\n|    `bool`    | Whether the selection is successful or not |\n\n---\n\n### 📌 `select.cancel_by_index()`\n\nThis method is used to cancel the selection of list items based on their index, starting from `1`. If it is a multiple selection list, you can cancel multiple items.\n\n|   Parameter   |             Type             | Default | Description                                               |\n|:------------:|:---------------------------:|:-------:|-----------------------------------------------------------|\n|    `index`   | `int`<br/>`list`<br/>`tuple` | Required | The index of the item to be canceled, passing in a `list` or `tuple` can select multiple items |\n|   `timeout`  |           `float`           |  `None`  | The timeout duration, `None` by default, uses the page timeout duration if not specified |\n\n| Return Type | Description |\n|:------------:|-------------|\n|    `bool`    | Whether the selection is successful or not |\n\n---\n\n### 📌 `select.cancel_by_locator()`\n\nThis method can be used to cancel the selection of option elements by using locators. If it is a multiple selection list, you can cancel multiple items.\n\n|   Parameter   |             Type             | Default | Description                                         |\n|:------------:|:---------------------------:|:-------:|-----------------------------------------------------|\n|   `locator`  | `str`<br/>`list`<br/>`tuple` | Required | The locator, passing in a `list` or `tuple` can select multiple items |\n|   `timeout`  |           `float`           |  `None`  | The timeout duration, `None` by default, uses the page timeout duration if not specified |\n\n| Return Type | Description |\n|:------------:|-------------|\n|    `bool`    | Whether the selection is successful or not |\n\n---\n\n### 📌 `select.cancel_by_option()`\n\nThis method is used to cancel the selection of single or multiple list items. If it is a multiple selection list, you can select multiple items.\n\n| Parameter Name | Type                                  | Default Value | Description                                                         |\n|:--------------:|:-------------------------------------:|:-------------:|---------------------------------------------------------------------|\n| `option`       | `ChromiumElement`<br/>`List[ChromiumElement]` | Required      | `<option>` element or a list of `<option>` elements                   |\n\n**Returns:** `None`\n\n---\n\n### 📌 `select.all()`\n\nThis method is used to select all items. It only applies to multi-select lists.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `select.clear()`\n\nThis method is used to deselect all items. It only applies to multi-select lists.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `select.invert()`\n\nThis method is used to invert the selection. It only applies to multi-select lists.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `select.is_multi`\n\nThis attribute returns whether the current element is a multi-select list.\n\n**Return Type:** `bool`\n\n---\n\n### 📌 `select.options`\n\nThis attribute returns all option elements of the current list element.\n\n**Return Type:** `ChromiumElement`\n\n---\n\n### 📌 `select.selected_option`\n\nThis attribute returns the selected option of the current element (for single-select lists).\n\n**Return Type:** `bool`\n\n---\n\n### 📌 `select.selected_options`\n\nThis attribute returns all selected options of the current element (for multi-select lists).\n\n**Return Type:** `List[ChromiumElement]`\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/frame_operation.md",
    "content": "🚤 iframe Operation\n---\n\nThe `<iframe>` element is a special element that is both an element and a page, so a separate section is dedicated to its introduction.\n\nUnlike selenium, DrissionPage can handle `<iframe>` elements without switching in and out. Therefore, it can realize operations such as cross-level element search, separate navigation within elements, simultaneous operation of inside and outside elements of `<iframe>`, multi-threaded control of multiple `<iframe>`, etc., with more flexible functions and clearer logic.\n\nWe use the Runoob online editor for demonstration:\n\n[Runoob Online Editor](https://www.runoob.com/try/try.php?filename=tryhtml_iframe)\n\nMake some adjustments to the content in the source code box, and then click \"Run\":\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>Runoob.com</title>\n</head>\n\n<body>\n<iframe id=\"sss\" src=\"https://www.runoob.com\">\n    <p>Your browser does not support iframe tags.</p>\n</iframe>\n</body>\n</html>\n```\n\nPress `F12`, you can see a two-layer `<iframe>` on the right side of the web page. The `<iframe>` with an id of `'iframeResult'` has an `<iframe>` with an id of `'sss'` inside. The innermost `<iframe>` page points to https://www.runoob.com.\n\n---\n\n## ✅️ Get `<iframe>` Object\n\nThere are two methods to get the `<iframe>` object. You can use the same method as getting a normal element, or use the `get_frame()` method. It is recommended to use the `get_frame()` method, because when obtaining it as a normal element, the IDE cannot correctly recognize the `<iframe>` element obtained.\n\n### 📌 `get_frame()`\n\nThis method is used to get a `<frame>` or `<iframe>` object in the page.\n\n| Parameter Name |         Type         | Default | Description                                                 |\n|:--------------:|:--------------------:|:-------:|-------------------------------------------------------------|\n| `loc_ind_ele`  | `str`<br/>`int`<br/>`ChromiumFrame` | Required | Locator<br/>Index of `<iframe>` element (starts from `1`, negative numbers represent reverse index)<br/>`ChromiumFrame object`<br/>`id` attribute content<br/>`name` attribute content |\n|   `timeout`    |       `float`        | `None`  | Timeout, use the page timeout if `None`                                 |\n\n|  Return Type  | Description                                          |\n|:------------:|------------------------------------------------------|\n| `ChromiumFrame` | Object of `<frame>` or `<iframe>` element |\n| `NoneElement` | Returns `NoneElement` when not found         |\n\n:::warning Note\n    It should be noted that if there are nested `<iframe>` in the page, obtaining it by index may be inaccurate.\n    For example, in the website mentioned above, `get_frames()` can obtain 6 elements, but `get_frame(6)` cannot obtain the last one.\n    This is because there are two nested `<iframe>`, which causes the inaccuracy of the obtained result.\n:::\n\n**Example:**\n\n```python\n# Get it using a locator\niframe = page.get_frame('#sss')\n\n# Get the second iframe\niframe = page.get_frame(1)\n```\n\n---\n\n### 📌 `get_frames()`\n\nThis method is used to get multiple `<frame>` or `<iframe>` objects that meet the conditions in the page.\n\n| Parameter Name |         Type         | Default | Description                       |\n|:--------------:|:--------------------:|:-------:|-----------------------------------|\n| `loc_ind_ele`  | `str`<br/>`int`<br/>`ChromiumFrame` | `None`  | Locator, returns all if `None` |\n|   `timeout`    |       `float`        | `None`  | Timeout, use the page timeout if `None` |\n\n|    Return Type     | Description                                              |\n|:-----------------:|----------------------------------------------------------|\n| `List[ChromiumFrame]` | List composed of `<frame>` or `<iframe>` element objects |\n\n---\n\n### 📌 Normal Element Method\n\nYou can also obtain the `<iframe>` object using the same method as obtaining a normal element:\n\n```python\niframe = page('#sss')\nprint(iframe.html)\n```\n\n**Output:**\n\n```shell\n<iframe id=\"sss\" src=\"https://www.runoob.com\"><html><head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Runoob.com - Learning is not just technology, it's dreams!</title>\n\n  <meta name=\"robots\" content=\"max-image-preview:large\">\n\n...truncated...\n```\n\nThis `ChromiumFrame` object is both a page and an element. Since the IDE does not prompt the properties and methods related to the `<iframe>` element object, it is recommended to wrap it with `get_frame()` when using this method to obtain it:\n\n```python\niframe = page('#sss')\niframe = page.get_frame(iframe)\n```\n\n---\n\n## ✅️ Find Elements Inside `<iframe>`\n\nFrom the obtained element object just now, we can see that we do not need to switch to the `<iframe>` with an id of `'iframeResult'` to obtain the elements inside it. Therefore, it is not necessary to first obtain the `ChromiumFrame` object in order to obtain the elements. \n\n### 📌 Find Inside `<iframe>`\n\nWith the element we obtained just now, we can find elements inside it:\n\n```python\nele = iframe('Home')\nprint(ele)\n```\n\n**Output:**\n\n```shell\n<ChromiumElement a href='https://www.runoob.com/' data-id='index' title='菜鸟教程' class='current'>\n```\n\n---\n\n### 📌 Cross Page Search of `<iframe>`\n\nIf the URL of the `<iframe>` element is in the same domain as the main page, we can directly search for elements inside the `<iframe>` using the page object without obtaining a `ChromiumFrame` object first:\n\n```python\nele = page('Homepage')\nprint(ele)\n```\n\n**Output:**\n\n```shell\n<ChromiumElement a href='https://www.runoob.com/' data-id='index' title='菜鸟教程' class='current'>\n```\n\nIf it is in the same domain, we can directly obtain the elements using the page object, regardless of how many levels of `<iframe>` it crosses.\n\n---\n\n### 📌 Comparison with Selenium\n\n`WebPage`:\n\n```python\nfrom DrissionPage import WebPage\n\npage = WebPage()\nele = page('Homepage')\n```\n\n`MixPage` (based on Selenium):\n\n```python\nfrom DrissionPage import MixPage\n\npage = MixPage()\npage.to_frame('#iframeResult')\npage.to_frame('#sss')\nele = page('Homepage')\npage.to_frame.main()\n```\n\nAs you can see, the original logic of switching in and out is more cumbersome.\n\n---\n\n### 📌 Important Note\n\nIf the `<iframe>` is in a different domain from the current tab, you cannot directly search for elements inside it using the page object. Instead, you need to obtain its `ChromiumFrame` element object and then search within that object.\n\n---\n\n## ✅️ Element Properties of `ChromiumFrame`\n\nAs mentioned above, `ChromiumFrame` is both an element and a page. Below are the usage instructions for its element properties.\n\n### 📌 `tag`\n\nThis property returns the name of the element.\n\n**Type:** `str`\n\n---\n\n### 📌 `html`\n\nThis property returns the outerHTML text of the entire `<iframe>` element.\n\n**Type:** `str`\n\n---\n\n### 📌 `inner_html`\n\nThis property returns the innerHTML text.\n\n**Type:** `str`\n\n---\n\n### 📌 `attrs`\n\nThis property returns all attributes of the element as a dictionary.\n\n**Type:** `dict`\n\n---\n\n### 📌 `xpath`\n\nThis property returns the xpath path of the element on its page.\n\n**Type:** `str`\n\n---\n\n### 📌 `css_path`\n\nThis property returns the css selector path of the element on its page.\n\n**Type:** `str`\n\n---\n\n### 📌 `attr()`\n\nThis method is used to retrieve an attribute of the element.\n\n| Parameter | Type  | Default | Description |\n|:---------:|:-----:|:------:|------------|\n|   `attr`  | `str` |   N/A  | Attribute name |\n\n|  Return Type | Description                   |\n|:-----------:|-------------------------------|\n|    `str`    | Attribute value text           |\n|    `None`   | Returns `None` if no attribute exists |\n\n---\n\n### 📌 `set.attr()`\n\nThis method is used to set the attribute of the element.\n\n| Parameter  |  Type  | Default | Description |\n|:----------:|:-----:|:------:|------------|\n|   `attr`   | `str` |   N/A  | Attribute name |\n|  `value`   | `str` |   N/A  | Attribute value |\n\n**Returns:** `None`\n\n---\n\n### 📌 `remove_attr()`\n\nThis method is used to remove an attribute of the element.\n\n| Parameter | Type  | Default | Description |\n|:---------:|:-----:|:------:|------------|\n|   `attr`  | `str` |   N/A  | Attribute name |\n\n**Returns:** `None`\n\n---\n\n### 📌 Relative Positioning\n\nRelative positioning methods are the same as regular elements, please refer to the section about acquiring elements.\n\n- `parent()`: Returns the parent element at a certain level.\n\n- `prev()`: Returns the previous sibling element.\n\n- `next()`: Returns the next sibling element.\n\n- `before()`: Returns the element before the current element.\n\n- `after()`: Returns the element after the current element.\n\n- `prevs()`: Returns a list of all previous sibling elements or nodes.\n\n- `nexts()`: Returns a list of all next sibling elements or nodes.\n\n- `befores()`: Returns a list of all sibling elements or nodes after the current element that meet the conditions.\n\n---\n\n## ✅️ Page Properties of `ChromiumFrame`\n\n### 📌 `url`\n\nThis property returns the current URL of the page.\n\n**Type:** `str`\n\n---\n\n### 📌 `title`\n\nThis property returns the current title text of the page.\n\n**Type:** `str`\n\n---\n\n### 📌 `cookies`\n\nThis property returns the current content of the cookies on the page.\n\n**Type:** `dict`\n\n---\n\n### 📌 `get()`\n\nThis method is used to navigate to another page within the `<iframe>`, and the usage is the same as `ChromiumPage`.\n\n```python\niframe.get('https://www.runoob.com/css3/css3-tutorial.html')\n```\n\n### 📌 `refresh()`\n\nThis method is used to refresh the page.\n\n**Parameters**: None\n\n**Returns**: `None`\n\n```python\niframe.refresh()\n```\n\n---\n\n### 📌 `active_ele`\n\nThis attribute returns the element on which the focus is currently placed in the page.\n\n**Type**: `ChromiumElement`\n\n---\n\n### 📌 `run_js()`\n\nThis method is used to execute JavaScript scripts within the `<iframe>`.\n\n| Parameter Name | Type   | Default Value | Description                                                  |\n| -------------- | ------ | ------------- | ------------------------------------------------------------ |\n| `script`       | `str`  | Required      | The JavaScript script text to be executed                    |\n| `*args`        | -      | N/A           | The parameters to be passed to the script in the order of `arguments[0]`, `arguments[1]`, etc. |\n| `as_expr`      | `bool` | `False`       | Whether to run the script as an expression, if `True`, the `args` parameter is ignored |\n| `timeout`      | `float`| `None`        | The timeout for the script, if `None`, use the page `timeouts.script` setting |\n\n| Return Type | Description                                                 |\n| ----------- | ----------------------------------------------------------- |\n| `Any`       | The result of the script execution                           |\n\n---\n\n### 📌 `scroll`\n\nThe scrolling methods of `ChromiumFrame` are the same as those of the page or element.\n\n**Example**: Scroll the `<iframe>` element down by 300 pixels\n\n```python\niframe.scroll.down(300)\n```\n\n---\n\n### 📌 `get_screenshot()`\n\nThis method is used to take a screenshot of the `<iframe>`. Due to technical limitations, only a viewport screenshot is available.\n\nThe three parameters below are mutually exclusive, with the order of priority: `as_bytes` > `as_base64` > `path`.\n\n| Parameter Name | Type              | Default Value | Description                                                                                                                  |\n| -------------- | ----------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `path`         | `str` <br/>`Path` | `None`        | The path to save the image, if `None`, the image will be saved in the current directory                                 |\n| `name`         | `str`             | `None`        | The complete file name, the suffix can be `'jpg'`, `'jpeg'`, `'png'`, `'webp'`, if `None`, the image will be saved in jpg format |\n| `as_bytes`     | `str` <br/>`True` | `None`        | Whether to return the image as bytes, can be `'jpg'`, `'jpeg'`, `'png'`, `'webp'`, `None`, `True`.<br/>When not `None`, the `path` and `as_base64` parameters are invalid. When `True`, jpg format is used |\n| `as_base64`    | `str` <br/>`True` | `None`        | Whether to return the image as base64, can be `'jpg'`, `'jpeg'`, `'png'`, `'webp'`, `None`, `True`.<br/>When not `None`, the `path` parameter is invalid. When `True`, jpg format is used |\n\n| Return Type | Description                                                           |\n| ----------- | --------------------------------------------------------------------- |\n| `bytes`     | Returns the image bytes when `as_bytes` is in effect                   |\n| `str`       | Returns the complete path of the image when `as_bytes` and `as_base64` are `None` |\n| `str`       | Returns the base64 formatted string when `as_base64` is in effect      |\n\n---\n\n## ✅️ Position and Size\n\n### 📌 `rect.location`\n\nThis attribute returns the coordinates of the top left corner of the iframe element in the page. Format: (x, y), with the top left being (0, 0).\n\n**Return Type**: `Tuple[float, float]`\n\n---\n\n### 📌 `rect.viewport_location`\n\nThis attribute returns the coordinates of the top left corner of the iframe element in the viewport. Format: (x, y), with the top left being (0, 0).\n\n**Return Type**: `Tuple[float, float]`\n\n---\n\n### 📌 `rect.screen_location`\n\nThis attribute returns the coordinates of the top left corner of the iframe element on the screen. Format: (x, y), with the top left being (0, 0).\n\n**Return Type**: `Tuple[float, float]`\n\n---\n\n### 📌 `rect.size`\n\nThis attribute returns the size of the page inside the frame. Format: (width, height).\n\n**Return Type**: `Tuple[float, float]`\n\n---\n\n### 📌 `rect.viewport_size`\n\nThis attribute returns the size of the iframe viewport. Format: (width, height).\n\n**Return Type**: `Tuple[float, float]`\n\n---\n\n### 📌 `rect.corners`\n\nThis attribute returns the coordinates of the four corners of the iframe element in the page. Order: top left, top right, bottom right, bottom left.\n\n**Return Type**: `((float, float), (float, float), (float, float), (float, float),)`\n\n---\n\n### 📌 `rect.viewport_corners`\n\nThis attribute returns the coordinates of the four corners of the iframe element in the viewport. Order: top left, top right, bottom right, bottom left.\n\n**Return Type**: `((float, float), (float, float), (float, float), (float, float),)`\n\n---\n\n## ✅️ Object States\n\n### 📌 `states.is_loading`\n\nThis attribute returns whether the page is in loading state.\n\n**Return Type**: `bool`\n\n---\n\n### 📌 `states.is_alive`\n\nThis attribute returns whether the frame element is available and still contains frames.\n\n**Return Type**: `bool`\n\n---\n\n### 📌 `states.is_displayed`\n\nThis attribute returns whether the iframe is displayed.\n\n**Return Type**: `bool`\n\n---\n\n### 📌 `states.ready_state`\n\nThis attribute returns the loading state, with 4 possible values:\n\n- 'connecting': the web page is connecting\n- 'loading': the document is still loading\n- 'interactive': the DOM has loaded but the resources have not\n- 'complete': all content has finished loading\n\n**Return Type**: `str`\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/get_element_info.md",
    "content": "🚤 Get Element Information\n---\n\nThe objects corresponding to browser elements are `ChromiumElement` and `ShadowRoot`. In this section, we will discuss how to retrieve information about an element after obtaining its object.\n\n`ChromiumElement` inherits all the properties of `SessionElement` and provides additional information specific to the browser. This section focuses on how to retrieve browser-specific element information.\n\n## ✅️️ Common Information with `SessionElement`\n\nHere is a list of attributes and methods that are shared with `SessionElement`. For detailed usage, please refer to the \"Retrieve Element Information\" section of the \"Send and Receive Data Packets\" part.\n\n| Attribute/Method | Description |\n|:-------------:|-------------|\n|    `html`    | This attribute returns the `outerHTML` text of the element |\n| `inner_html` | This attribute returns the `innerHTML` text of the element |\n|    `tag`     | This attribute returns the tag name of the element |\n|    `text`    | This attribute returns a string that represents the combined text of all contents within the element |\n|  `raw_text`  | This attribute returns the raw text within the element |\n|  `texts()`   | This method returns the text of all **direct** child nodes of the element, including elements and text nodes |\n|  `comments`  | This attribute returns a list of comments within the element |\n|   `attrs`    | This attribute returns a dictionary of all attributes and their values for the element |\n|   `attr()`   | This method returns the value of a specific `attribute` of the element |\n|    `link`    | This method returns the `href` or `src` attribute of the element |\n|    `page`    | This attribute returns the page object the element belongs to |\n|   `xpath`    | This attribute returns the absolute xpath of the element within the page |\n|  `css_path`  | This attribute returns the absolute css selector of the element within the page |\n\n---\n\n## ✅️️ Size and Position\n\n### 📌 `rect.size`\n\nThis attribute returns the size of the element as a tuple.\n\n**Type:** `Tuple[float, float]`\n\n```python\nsize = ele.rect.size\n# Returns: (50, 50)\n```\n\n---\n\n### 📌 `rect.location`\n\nThis attribute returns the coordinates of the **top-left corner** of the element on the **entire page** as a tuple.\n\n**Type:** `Tuple[float, float]`\n\n```python\nloc = ele.rect.location\n# Returns: (50, 50)\n```\n\n---\n\n### 📌 `rect.midpoint`\n\nThis attribute returns the coordinates of the **center** of the element on the **entire page** as a tuple.\n\n**Type:** `Tuple[float, float]`\n\n```python\nloc = ele.rect.midpoint\n# Returns: (55, 55)\n```\n\n---\n\n### 📌 `rect.click_point`\n\nThis attribute returns the coordinates of the **clicking point** of the element on the **entire page** as a tuple.\n\nThe clicking point refers to the position where `click()` method would click and is located at the top part of the element.\n\n**Type:** `Tuple[float, float]`\n\n---\n\n### 📌 `rect.viewport_location`\n\nThis attribute returns the coordinates of the **top-left corner** of the element in the **current viewport** as a tuple.\n\n**Type:** `Tuple[float, float]`\n\n---\n\n### 📌 `rect.viewport_midpoint`\n\nThis attribute returns the coordinates of the **center** of the element in the **current viewport** as a tuple.\n\n**Type:** `Tuple[float, float]`\n\n---\n\n### 📌 `rect.viewport_click_point`\n\nThis attribute returns the coordinates of the **clicking point** of the element in the **current viewport** as a tuple.\n\n**Type:** `Tuple[float, float]`\n\n---\n\n### 📌 `rect.screen_location`\n\nThis attribute returns the coordinates of the **top-left corner** of the element on the **screen** as a tuple.\n\n**Type:** `Tuple[float, float]`\n\n---\n\n### 📌 `rect.screen_midpoint`\n\nThis attribute returns the coordinates of the **center** of the element on the **screen** as a tuple.\n\n**Type:** `Tuple[float, float]`\n\n---\n\n### 📌 `rect.screen_click_point`\n\nThis attribute returns the coordinates of the **clicking point** of the element on the **screen** as a tuple.\n\n**Type:** `Tuple[float, float]`\n\n---\n\n### 📌 `rect.corners`\n\nThis attribute returns the coordinates of the four corners of the element within the page as a list. The order of corners is: top-left, top-right, bottom-right, bottom-left.\n\n**Type:** `((float, float), (float, float), (float, float), (float, float),)`\n\n---\n\n### 📌 `rect.viewport_corners`\n\nThis attribute returns the coordinates of the four corners of the element within the viewport as a list. The order of corners is: top-left, top-right, bottom-right, bottom-left.\n\n**Type:** `list[(float, float), (float, float), (float, float), (float, float)]`\n\n---\n\n### 📌 `rect.viewport_rect`\n\nThis attribute returns the coordinates of the four corners of the element within the viewport as a list. The order of corners is: top-left, top-right, bottom-right, bottom-left.\n\n**Type:** `List[(float, float), (float, float), (float, float), (float, float)]`\n\n---\n\n## ✅️️ Attributes and Content\n\n### 📌 `pseudo.before`\n\nThis attribute returns the content of the `::before` pseudo-element of the current element as a text.\n\n**Type:** `str`\n\n```python\nbefore_txt = ele.pseudo.before\n```\n\n---\n\n### 📌 `pseudo.after`\n\nThis attribute returns the content of the `::after` pseudo-element of the current element as a text.\n\n**Type:** `str`\n\n```python\nafter_txt = ele.pseudo.after\n```\n\n---\n\n### 📌 `style()`\n\nThis method returns the value of a CSS style property of the element, including properties of pseudo-elements. It takes two parameters: `style` for the style property name, and `pseudo_ele` for the pseudo-element name. If `pseudo_ele` is omitted, it retrieves the style property of a normal element.\n\n| Parameter Name | Type  | Default Value | Description |\n|:------------:|:-----:|:----:|-----------|\n|   `style`    | `str` | Mandatory  | Style name |\n| `pseudo_element` | `str` | `''` | Pseudo element name (if any) |\n\n| Return Type  | Description    |\n|:-----:|-------|\n| `str` | Style property value |\n\n**Example:**\n\n```python\n# Get the color value of the CSS property\nprop = ele.style('color')\n\n# Get the content of the after pseudo element\nprop = ele.style('content', 'after')\n```\n\n---\n\n### 📌 `prop()`\n\nThis method returns the value of a given `property`. It takes a string parameter and returns the value of that property.\n\n|  Parameter Name  |  Type   | Default Value | Description |\n|:------:|:-----:|:---:|------|\n| `prop` | `str` | Mandatory  | Property name |\n\n| Return Type  | Description  |\n|:-----:|-----|\n| `str` | Property value |\n\n---\n\n### 📌 `shadow_root`\n\nThis attribute returns the shadow-root object inside the element, or `None` if there is none.\n\n**Type:** `ShadowRoot`\n\n---\n\n## ✅️️ State Information\n\nState information is stored in the `states` attribute.\n\n### 📌`states.is_in_viewport`\n\nThis attribute returns a boolean value indicating whether the element is in the viewport, based on the clickability of the element.\n\n**Type:** `bool`\n\n---\n\n### 📌`states.is_whole_in_viewport`\n\nThis attribute returns a boolean value indicating whether the entire element is in the viewport.\n\n**Type:** `bool`\n\n---\n\n### 📌`states.is_whole_in_viewport`\n\nThis attribute returns a boolean value indicating whether the entire element is in the viewport.\n\n**Type:** `bool`\n\n---\n\n### 📌`states.is_alive`\n\nThis attribute returns a boolean value indicating whether the element is still alive. It is used to determine if the element is no longer valid due to a page refresh in D mode.\n\n**Type:** `bool`\n\n---\n\n### 📌 `states.is_checked`\n\nThis attribute returns a boolean value indicating whether a form radio or checkbox element is selected.\n\n**Type:** `bool`\n\n---\n\n### 📌 `states.is_selected`\n\nThis attribute returns a boolean value indicating whether an item in a `<select>` element is selected.\n\n**Type:** `bool`\n\n---\n\n### 📌 `states.is_enabled`\n\nThis attribute returns a boolean value indicating whether the element is enabled.\n\n**Type:** `bool`\n\n---\n\n### 📌 `states.is_displayed`\n\nThis attribute returns a boolean value indicating whether the element is visible.\n\n**Type:** `bool`\n\n---\n\n### 📌 `states.is_covered`\n\nThis attribute returns whether the element is covered by another element. If it is covered, the ID of the covering element is returned. Otherwise, it returns `False`.\n\n|  Return Type   |       Description       |\n|:-------:|:--------------:|\n| `False` | Not covered, returns `False`  |\n|  `int`   | If covered, returns the ID of the covering element |\n\n---\n\n### 📌 `states.has_rect`\n\nThis attribute returns whether the element has size and position information. If it does, it returns a list of coordinates of the four corners of the element on the page. If it doesn't, it returns `False`.\n\n|  Return Type   | Description                                                        |\n|:-------:|-----------------------------------------------------------|\n| `list`  | If size and position information exists, it returns the coordinates of the four corners of the element as [(int, int), ...], in the order: top-left, top-right, bottom-right, bottom-left |\n| `False` | If it doesn't exist, it returns `False`                                             |\n\n---\n\n## ✅️️ Save and Screenshot\n\nThe save feature is a unique feature of this library, which can directly read from the browser cache without relying on another UI library or re-downloading to save page resources.\n\nFor comparison, Selenium cannot save images by itself, often requiring the use of UI tools for assistance, which is not only inefficient and unreliable but also consumes keyboard and mouse resources.\n\n### 📌 `get_src()`\n\nThis method returns the resource used by the `src` attribute of the element. Base64 data can be returned as `bytes`, while other resources are returned as `str`. If there is no resource, it returns `None`.\n\nFor example, you can retrieve the byte data of an image on the page for content recognition or save it to a file. JavaScript text can also be obtained from `<script>` tags.\n\n|     Parameter Name      |      Type       |  Default Value  | Description                                     |\n|:-----------------------:|:---------------:|:--------------:|-------------------------------------------------|\n|   `timeout`             |     `float`     |     `None`     | Timeout for resource loading. If `None`, use the `timeout` attribute of the element's page |\n| `base64_to_bytes`       |     `bool`      |     `True`     | If `True`, convert base64 data to `bytes` format |\n\n|  Return Type  | Description           |\n|:------:|-----------------------|\n| `str`  | Resource string        |\n| `None` | Returns `None` if no resource |\n\n**Example:**\n\n```python\nimg = page('tag:img')\nsrc = img.get_src()\n```\n\n---\n\n### 📌 `save()`\n\nThis method saves the resource obtained from the `get_src()` method to a file.\n\n|     Parameter Name    |         Type         |  Default Value  | Description                                     |\n|:---------------------:|:--------------------:|:--------------:|-------------------------------------------------|\n|  `path`               | `str`<br/>`Path`     |     `None`     | File saving path. If `None`, save to the current folder |\n|  `name`               |         `str`        |     `None`     | File name, including the extension. If `None`, get from the resource URL |\n| `timeout`             |        `float`       |     `None`     | Timeout for resource loading. If `None`, use the `timeout` attribute of the element's page |\n\n|  Return Type  | Description           |\n|:------:|-----------------------|\n| `str`  | Saving path           |\n\n**Example:**\n\n```python\nimg = page('tag:img')\nimg.save('D:\\\\img.png')\n```\n\n---\n\n## ✅️️ `ShadowRoot` property\n\nThis library treats the shadow dom's `root` as an element to process, which can retrieve attributes and perform searches on its descendants. It follows the same logic as `ChromiumElement`, but with fewer properties. The available properties are as follows:\n\n### 📌 `tag`\n\nThis property returns the element's tag name, which is `'shadow-root'`.\n\n**Type:** `str`\n\n---\n\n### 📌 `html`\n\nThis property returns the html text of the `shadow_root`, wrapped in `<shadow_root></shadow_root>` tags.\n\n**Type:** `str`\n\n---\n\n### 📌 `inner_html`\n\nThis property returns the inner html text of the `shadow_root`.\n\n**Type:** `str`\n\n---\n\n### 📌 `page`\n\nThis property returns the page object where the element is located.\n\n**Type:** `ChromiumPage`, `ChromiumTab`, `ChromiumFrame`, `WebPage`\n\n---\n\n### 📌 `parent_ele`\n\nThis property returns the attached regular element object.\n\n**Type:** `ChromiumElement`\n\n---\n\n### 📌 `states.is_enabled`\n\nSame as `ChromiumElement`.\n\n**Type:** `bool`\n\n---\n\n### 📌 `states.is_alive`\n\nSame as `ChromiumElement`.\n\n**Type:** `bool`\n\n---\n\n## ✅️️ Compare Elements\n\nTwo element objects can be compared using `==` to determine if they refer to the same element.\n\n**Example:**\n\n```python\nele1 = page('t:div')\nele2 = page('t:div')\nprint(ele1==ele2)  # Outputs True\n```\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/get_elements.md",
    "content": "🚤 Find Elements\n---\n\nPlease refer to the \"[Find Elements](https://g1879.gitee.io/drissionpagedocs/get_elements/get_ele_intro)\" section.\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/get_page_info.md",
    "content": "🚤 Getting Page Information\n---\n\nAfter successfully accessing the web page, you can use the properties and methods of `ChromiumPage` to obtain page information.\n\nIn addition to `ChromiumPage`, there are two other types of page objects, `ChromiumTab` and `ChromiumFrame`, which correspond to tab and `<iframe>` element objects, respectively. These will be covered in separate sections.\n\n## ✅️️ Page Information\n\n### 📌 `html`\n\nThis property returns the current page's HTML text.\n\n**Return type:** `str`\n\n---\n\n### 📌 `json`\n\nThis property parses the request content into json.\n\nIf you visit a URL that returns JSON data, the browser will display the JSON data. This property can convert the data into a `dict` format.\n\n**Return type:** `dict`\n\n---\n\n### 📌 `title`\n\nThis property returns the current page's `title` text.\n\n**Return type:** `str`\n\n---\n\n### 📌 `user_agent`\n\nThis property returns the current page's user agent information.\n\n**Return type:** `str`\n\n---\n\n### 📌 `save()`\n\nThis method saves the current page as a file and returns the saved content.\n\nIf both the `path` and `name` parameters are `None`, only the content is returned without saving the file.\n\nBoth the Page object and Tab object have this method.\n\n|    Parameter    |       Type        |   Default   | Description                                 |\n|:---------:|:-------------:|:-------:|-------------------------------------------|\n|   `path`  | `str`<br/>`Path` | `None`  | The save path. If `None` and `name` is not `None`, it saves to the current path |\n|   `name`  |      `str`      | `None`  | The name of the saved file. If `None` and `path` is not `None`, the title value is used |\n| `as_pdf` |     `bool`     | `False` | If `True`, save as pdf; otherwise, save as mhtml and ignore `kwargs` parameters |\n| `**kwargs`|    multiple   |   None  | PDF generation parameters                  |\n\nPDF generation parameters include: `landscape`, `displayHeaderFooter`, `printBackground`, `scale`, `paperWidth`, `paperHeight`, `marginTop`, `marginBottom`, `marginLeft`, `marginRight`, `pageRanges`, `headerTemplate`, `footerTemplate`, `preferCSSPageSize`, `generateTaggedPDF`, `generateDocumentOutline`\n\n| Return Type |              Description                |\n|:-----------:|:--------------------------------------:|\n|   `str`     |  Return mhtml text when `as_pdf` is `False` |\n|   `bytes`   |     Return file byte data when `as_pdf` is `True`     |\n\n---\n\n## ✅️️ Running Status Information\n\n### 📌 `url`\n\nThis property returns the current visited URL.\n\n**Return type:** `str`\n\n---\n\n### 📌 `address`\n\nThis property returns the page address and port controlled by the current object.\n\n**Return type:** `str`\n\n```python\nprint(page.address)\n```\n\n**Output:**\n\n```\n127.0.0.1:9222\n```\n\n---\n\n### 📌 `tab_id`\n\n**Return type:** `str`\n\nThis property returns the id of the current tab.\n\n---\n\n### 📌 `process_id`\n\nThis property returns the browser process id.\n\n**Return type:** `int` or `None`\n\n---\n\n### 📌 `states.is_loading`\n\nThis property returns whether the page is currently loading.\n\n**Return type:** `bool`\n\n---\n\n### 📌 `states.is_alive`\n\nThis property returns whether the page is still alive. If the tab has been closed, it returns `False`.\n\n**Return type:** `bool`\n\n---\n\n### 📌 `states.ready_state`\n\nThis property returns the current loading state of the page, which can be one of the following:\n\n- 'connecting': The web page is connecting\n- 'loading': The document is still loading\n- 'interactive': The DOM has loaded, but resources are still loading\n- 'complete': All content has finished loading\n\n**Return type:** `str`\n\n---\n\n### 📌 `url_available`\n\nThis property returns a boolean value indicating whether the current link is available.\n\n**Return type:** `bool`\n\n---\n\n### 📌 `states.has_alert`\n\nThis property returns a boolean value indicating whether an alert is present on the page.\n\n**Return type:** `bool`\n\n---\n\n## ✅️️ Window Information\n\n### 📌 `size` and `rect.page_size`\n\nThese two properties return the page size as a `tuple` in the format (width, height).\n\n**Return type:** `Tuple[int, int]`\n\n---\n\n### 📌 `rect.window_size`\n\nThis property returns the window size as a `tuple` in the format (width, height).\n\n**Return type:** `Tuple[int, int]`\n\n---\n\n### 📌 `rect.window_location`\n\nThis property returns the window's coordinates on the screen as a `tuple`, with the top-left corner as (0, 0).\n\n**Return type:** `Tuple[int, int]`\n\n---\n\n### 📌 `rect.window_state`\n\nThis property returns the current state of the window, which can be one of the following: `'normal'`, `'fullscreen'`, `'maximized'`, `'minimized'`.\n\n**Return type:** `str`\n\n---\n\n### 📌 `rect.viewport_size`\n\nThis property returns the viewport size as a `tuple`, excluding scrollbars, in the format (width, height).\n\n**Return type:** `Tuple[int, int]`\n\n---\n\n### 📌 `rect.viewport_size_with_scrollbar`\n\nThis property returns the browser window size, including scrollbars, as a `tuple`, in the format (width, height).\n\n**Return type:** `Tuple[int, int]`\n\n---\n\n### 📌 `rect.page_location`\n\nThis property returns the screen coordinates of the top-left corner of the page as a `tuple`, with the top-left corner of the screen as (0, 0).\n\n**Return type:** `Tuple[int, int]`\n\n\n\n### 📌 `rect.viewport_location`\n\nThis property returns the coordinate of the viewport in the screen as a tuple, with the top left corner as (0, 0).\n\n**Return Type:** `Tuple[int, int]`\n\n---\n\n## ✅️️ Configuration Parameters\n\n### 📌 `timeout`\n\nThis property sets the overall default timeout time, including timeouts for element finding, clicking, handling prompt boxes, list selection, and other places where timeouts are required. It uses this value as the default.  \nDefault is 10, can be assigned a new value.\n\n**Return Type:** `int`, `float`\n\n```python\n# Specify when creating the page object\npage = ChromiumPage(timeout=5)\n\n# Modify the timeout\npage.timeout = 20\n```\n\n---\n\n### 📌 `timeouts`\n\nThis property returns three types of timeout values as a dictionary.\n\n- `'base'`: Same as the `timeout` property\n- `'page_load'`: Used for waiting for page load\n- `'script'`: Used for waiting for script execution\n\n**Return Type:** `dict`\n\n```python\nprint(page.timeouts)\n```\n\n**Output:**\n\n```\n{'base': 10, 'pageLoad': 30.0, 'script': 30.0}\n```\n\n---\n\n### 📌 `retry_times`\n\nThis property sets the number of retries when a network connection fails. Default is 3, can be assigned a new value.\n\n**Return Type:** `int`\n\n```python\n# Modify the number of retries\npage.retry_times = 5\n```\n\n---\n\n### 📌 `retry_interval`\n\nThis property sets the waiting interval in seconds for retrying when a network connection fails. Default is 2, can be assigned a new value.\n\n**Return Type:** `int`, `float`\n\n```python\n# Modify the waiting interval for retrying\npage.retry_interval = 1.5\n```\n\n---\n\n### 📌 `page_load_strategy`\n\nThis property returns the page load strategy, which has 3 available options:\n\n- `'normal'`: Wait for all resources on the page to finish loading\n- `'eager'`: Stop when the DOM is loaded\n- `'none'`: Stop when the page finishes connecting\n\n**Return Type:** `str`\n\n---\n\n## ✅️️ Cookies and Cache Information\n\n### 📌 `cookies`\n\nThis property returns the cookies used by the current page as a dictionary.\n\nPlease note that if different subdomains use the same `name` attribute, the cookies returned by this property may be incomplete.\n\n**Return Type:** `dict`\n\n---\n\n### 📌 `get_cookies()`\n\nThis method retrieves cookies and returns them as a list of cookie objects.\n\n| Parameter Name | Type   | Default | Description                                                                                   |\n|:-------------:|:------:|:-------:|----------------------------------------------------------------------------------------------|\n| `as_dict`     | `bool` | `False` | Whether to return the results as a dictionary. When `True`, a dictionary of `{name: value}` pairs is returned and the `all_info` parameter is ignored. When `False`, a list of cookie objects is returned. |\n| `all_domains` | `bool` | `False` | Whether to return all cookies. When `False`, only the cookies for the current URL are returned.                                                           |\n| `all_info`    | `bool` | `False` | Whether the returned cookies include all information. When `False`, only `name`, `value`, and `domain` information are included.                                                  |\n\n| Return Type | Description                                        |\n|:------:| -------------------------------------------------- |\n| `dict` | When `as_dict` is `True`, a dictionary of cookies is returned.    |\n| `list` | When `as_dict` is `False`, a list of cookie objects is returned. |\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('http://www.baidu.com')\n\nfor i in page.get_cookies(as_dict=False):\n    print(i)\n```\n\n**Output:**\n\n```\n{'domain': '.baidu.com', 'domain_specified': True, ......}\n......\n```\n\n---\n\n### 📌 `get_session_storage()`\n\nThis method is used to retrieve sessionStorage information, and can retrieve all or a single item.\n\n| Parameter Name | Type   | Default | Description                        |\n|:-------------:|:------:|:-------:|------------------------------------|\n| `item`        | `str`  | `None`  | The item to retrieve. If `None`, all items are returned as a dictionary. |\n\n| Return Type | Description                                 |\n|:------:| --------------------------------------------|\n| `dict` | When `item` is `None`, all items are returned. |\n| `str`  | When `item` is specified, the content of that item is returned.      |\n\n---\n\n### 📌 `get_local_storage()`\n\nThis method is used to retrieve localStorage information, and can retrieve all or a single item.\n\n| Parameter Name | Type   | Default | Description                        |\n|:-------------:|:------:|:-------:|------------------------------------|\n| `item`        | `str`  | `None`  | The item to retrieve. If `None`, all items are returned as a dictionary. |\n\n| Return Type | Description                                 |\n|:------:| --------------------------------------------|\n| `dict` | When `item` is `None`, all items are returned. |\n| `str`  | When `item` is specified, the content of that item is returned.      |\n\n---\n\n## ✅️️ Embeded Objects\n\n### 📌 `driver`\n\nThis property returns the `Driver` object used by the current page object.\n\n**Return Type:** `Driver`\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/introduction.md",
    "content": "🚤 Overview\n---\n\nThe `ChromiumPage` object and the `WebPage` object are d models for manipulating the browser. This chapter introduces `ChromiumPage`.\n\nAs the name suggests, `ChromiumPage` is a page of the Chromium kernel browser, which encapsulates the properties and methods required to manipulate web pages using POM.\n\nUsing it, we can interact with web pages, such as adjusting window size, scrolling pages, and operating pop-up windows, etc.\n\nWith the element objects obtained from it, we can also interact with elements on the page, such as entering text, clicking buttons, selecting drop-down menus, etc.\n\nEven, we can run JavaScript code on the page or elements, modify element properties, add or remove elements, etc.\n\nIt can be said that most operations for manipulating the browser can be completed by `ChromiumPage` and its derivative objects, and their functions are constantly increasing.\n\nIn addition to interacting with pages and elements, `ChromiumPage` also acts as a browser controller. It can be said that a `ChromiumPage` object is a browser.\n\nIt can manage tabs, control download tasks, and generate independent page objects (`ChromiumTab`) for each tab, allowing simultaneous operation of multiple tabs without switching.\n\nWith the release of version 3.0, the author finally can unleash his imagination and add various interesting features to `ChromiumPage`. We will continue to improve it in the future.\n\nLet's take a simple example to understand how `CromiumPage` works.\n\n---\n\nSearch \"Drissionpage\" on Baidu and print the result.\n\n```python\n# Import\nfrom DrissionPage import ChromiumPage\n\n# Create an object\npage = ChromiumPage()\n# Access the web page\npage.get('https://www.baidu.com')\n# Enter text\npage('#kw').input('DrissionPage')\n# Click the button\npage('#su').click()\n# Wait for page redirection\npage.wait.load_start()\n# Get all results\nlinks = page.eles('tag:h3')\n# Traverse and print the results\nfor link in links:\n    print(link.text)\n```\n\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/network_listener.md",
    "content": "🚤 Listening to Network Data\n---\n\nMany web page data comes from interfaces, dynamically loaded during website usage, such as pagination lists that load content using JavaScript.\n\nThese data are usually sent in JSON format, received by the browser, parsed, and then loaded into the corresponding positions in the DOM.\n\nWhen doing data collection, we often retrieve the parsed data from the DOM, which may have issues such as incomplete data, delayed loading response, and difficulty in determining when loading is complete.\n\nWouldn't it be great if we could intercept the data packets sent and received by the browser, determine the next steps based on the packet status, and even directly obtain the data?\n\nThe DrissionPage provides a built-in listener for each page object (including tab and frame objects) specifically designed for capturing browser data packets.\n\nIt can provide the ability to wait for and capture specific data packets, and return them in real-time.\n\n## ✅️ Examples\n\nLet's first look at two examples to understand how the listener works.\n\n:::warning Note\n    Before performing any actions, the listener needs to be started. Data packets before calling `listen.start()` cannot be captured.\n:::\n\n### 📌 Waiting and Capturing\n\nClick on the next page, wait for the data packet, and then click on the next page in a loop.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('https://gitee.com/explore/all')  # Access the website, the data packet generated by this line is not captured\n\npage.listen.start('gitee.com/explore')  # Start the listener, specifying to capture data packets containing this text\nfor _ in range(5):\n    page('@rel=next').click()  # Click on the next page\n    res = page.listen.wait()  # Wait for and capture a data packet\n    print(res.url)  # Print the data packet URL\n```\n\n**Output:**\n\n```shell\nhttps://gitee.com/explore/all?page=2\nhttps://gitee.com/explore/all?page=3\nhttps://gitee.com/explore/all?page=4\nhttps://gitee.com/explore/all?page=5\nhttps://gitee.com/explore/all?page=6\n```\n\n---\n\n### 📌 Real-time Capturing\n\nDo the same thing as the previous example, but in a different way.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.listen.start('gitee.com/explore')  # Start the listener, specifying to capture data packets containing this text\npage.get('https://gitee.com/explore/all')  # Access the website\n\ni = 0\nfor packet in page.listen.steps():\n    print(packet.url)  # Print the data packet URL\n    page('@rel=next').click()  # Click on the next page\n    i += 1\n    if i == 5:\n        break\n```\n\n---\n\n## ✅️ Setting Targets and Starting the Listener\n\n### 📌 `listen.start()`\n\nThis method is used to start the listener and set the targets to capture.\n\nMultiple targets can be set, and data packets that meet the conditions will be captured.\n\nIf this method is called while the listener is still running, the previously captured queue will be cleared.\n\n|   Parameter   |             Type              | Default | Description                                                |\n|:-------------:|:----------------------------:|:-------:|------------------------------------------------------------|\n|   `targets`   | `str`<br/>`list`<br/>`tuple` |  `None` | URL feature to match data packets, can specify multiple using a list, `True` to capture all |\n|  `is_regex`   |            `bool`            |  `None` | Whether the targets are regular expressions, `None` to keep the original setting |\n|   `method`    | `str`<br/>`list`<br/>`tuple` |  `None` | Request types to listen for, can specify multiple, default is `('GET', 'POST')`, `True` to listen for all, `None` to keep the original setting |\n| `res_type`    | `str`<br/>`list`<br/>`tuple` |  `None` | Resource type to listen for, can specify multiple, `True` to listen for all, `None` to keep the original setting |\n\n**Returns:** `None`\n\n---\n\n### 📌 `listen.set_targets()`\n\nThis method can modify the targets to capture during the listening process, or set them before starting the listener.\n\nThe listener will not start if it has not been started yet.\n\n|   Parameter   |             Type              |   Default   | Description                                                |\n|:-------------:|:----------------------------:|:----------:|------------------------------------------------------------|\n|   `targets`   | `str`<br/>`list`<br/>`tuple` |    `True`   | URL feature to match data packets, can specify multiple using a list, `True` to capture all |\n|  `is_regex`   |            `bool`            |   `False`  | Whether the targets are regular expressions                |\n|   `method`    | `str`<br/>`list`<br/>`tuple` | (`'GET', 'POST')` | Request types to listen for, can specify multiple, default is `('GET', 'POST')`, `True` to listen for all |\n| `res_type`    | `str`<br/>`list`<br/>`tuple` |    `True`   | Resource type to listen for, can specify multiple, `True` to listen for all |\n\n**Returns:** `None`\n\n---\n\n## ✅️ Waiting and Capturing Data Packets\n\n### 📌 `listen.wait()`\n\nThis method is used to wait for a specified number of data packets that meet the requirements to arrive.\n\nAll eligible packets will be stored in a queue, and `wait()` actually retrieves results one by one from the queue, so there is no need to worry about packets being lost due to the page being refreshed.\n\n|    Parameter Name    |       Type        | Default | Description                                                                        |\n|:----------------:|:-----------------:|:-------:|------------------------------------------------------------------------------------|\n|      `count`      |       `int`       |   `1`   | Number of packets to capture                                                       |\n|     `timeout`     | `float`<br/>`None` |  `None`  | Timeout duration, set to `None` for unlimited wait                                 |\n|   `fit_count`    |       `bool`       | `True`  | Whether the total count requirement needs to be met. If timeout, returns `False` if `True`, returns the captured packets if `False`. |\n|   `raise_err`    |       `bool`       |  `None`  | Whether to throw an error when timeout occurs. If `None`, it will be based on the `Settings` setting. If not throwing, returns `False` on timeout. |\n\n|      Return Type       | Description                                                                        |\n|:---------------------:|------------------------------------------------------------------------------------|\n|     `DataPacket`      | Returns a data packet object if `count` is `1` and there is no timeout              |\n| `List[DataPacket]` | Returns a list of data packet objects if `count` is greater than `1` and there is no timeout or `fit_count` is `False` |\n|        `False`        | Returns `False` if there is a timeout and `fit_count` is `True`                      |\n\n---\n\n### 📌 `listen.steps()`\n\nThis method returns an iterable object for a `for` loop, where each iteration can get a data packet.\n\nIt allows for real-time retrieval and return of data packets.\n\nIf `timeout` occurs, the loop will be interrupted.\n\n|   Parameter Name    |        Type         | Default | Description                                                                        |\n|:----------------:|:------------------:|:-------:|------------------------------------------------------------------------------------|\n|     `count`      |       `int`        |  `None` | Number of packets to capture. Set to `None` for an unlimited amount.                   |\n|    `timeout`    | `float`<br/>`None` |  `None` | Waiting time for each data packet. Set to `None` for unlimited wait.                  |\n|       `gap`      |       `int`        |   `1`   | How many packets to receive before returning data.                                    |\n\n|      Return Type       | Description                                          |\n|:---------------------:|------------------------------------------------------|\n|     `DataPacket`      | Returns a data packet object when `gap` is `1`         |\n| `List[DataPacket]` | Returns a list of data packet objects when `gap` is greater than `1` |\n\n---\n\n### 📌 `listen.wait_silent()`\n\nThis method is used to wait for all specified requests to complete.\n\n|   Parameter Name    |        Type         |    Default    | Description                                                                        |\n|:----------------:|:------------------:|:------------:|------------------------------------------------------------------------------------|\n|    `timeout`     | `float`<br/>`None` |    `None`    | Waiting time. Set to `None` for unlimited wait.                                     |\n|   `targets_only` |       `bool`       |    `False`   | Whether to wait only for the requests specified in `targets`.                       |\n\n|       Return Type      | Description                                                          |\n|:----------------------:|----------------------------------------------------------------------|\n|         `bool`         | Returns whether the waiting was successful.                          |\n\n---\n\n## ✅️ Pausing and Resuming\n\n### 📌 `listen.pause()`\n\nThis method is used to pause the listener.\n\n| Parameter Name |   Type   | Default | Description                                   |\n| :-----------: | :------: | :-----: | --------------------------------------------- |\n|    `clear`    | `bool` | `True`  | Whether to clear the already acquired queue. |\n\n**Returns:** `None`\n\n---\n\n### 📌 `listen.resume()`\n\nThis method is used to continue the paused listener.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `listen.stop()`\n\nThis method is used to terminate the listener, and it will clear the acquired queue while keeping the targets.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n## ✅️ `DataPacket` Object\n\nThe `DataPacket` object is the result object of the acquired data packet, which contains various information about the data packet.\n\n### 📌 `Object Properties`\n\n|   Property Name   |  Data Type  | Description                    |\n|:--------------:|:----------:|----------------------------|\n|    `tab_id`    |   `str`    | ID of the tab that made the request |\n|   `frameId`    |   `str`    | ID of the frame that made the request |\n|    `target`    |   `str`    | Listening target that made the request |\n|     `url`      |   `str`    | URL of the data packet request |\n|    `method`    |   `str`    | Type of request |\n|  `is_failed`   |   `bool`   | Whether the connection failed |\n| `resourceType` |   `str`    | Type of resource |\n|   `request`    | `Request`  | Object that holds request information |\n|   `response`   | `Response` | Object that holds response information |\n|  `fail_info`   | `FailInfo` | Object that holds connection failure information |\n\n### 📌 `wait_extra_info()`\n\nSome data packets have `extra_info` data, but this data may be loaded later than the data packet. Use this method to wait for the data to be loaded into the data packet object.\n\n|   Parameter Name    |        Type         | Default | Description                    |\n|:---------------:|:-----------------:|:-------:|----------------------------|\n|     `timeout`    | `float`<br/>`None` |  `None` | Timeout duration, set to `None` for unlimited wait. |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `bool` | Whether it waits successfully |\n\n### 📌 `Request` Object\n\nThe `Request` object is an object used to store request information within the `DataPacket` object. It has the following properties:\n\n| Property Name | Data Type | Description |\n| ------------- | --------- | ----------- |\n| `headers` | `CaseInsensitiveDict` | Returns headers data as a case-insensitive dictionary |\n| `postData` | `str`<br/>`dict` | Data submitted for requests of type post |\n\nIn addition to the above properties, the `Request` object can access other fields using a key or specifying a property, including:\n\nurl, method, urlFragment, hasPostData, postDataEntries, mixedContentType, initialPriority, referrerPolicy, isLinkPreload, trustTokenParams, isSameSite\n\n---\n\n### 📌 `Response` Object\n\nThe `Response` object is an object used to store response information within the `DataPacket` object. It has the following properties:\n\n| Property Name | Data Type | Description |\n| ------------- | --------- | ----------- |\n| `headers` | `CaseInsensitiveDict` | Returns headers data as a case-insensitive dictionary |\n| `body` | `str`<br/>`bytes`<br/>`dict` | Automatically converted if it is in json format, converted to base64 if it is an image format, directly returned as text for other formats |\n| `raw_body` | `str` | Unprocessed body text |\n\nIn addition to the above properties, the `Response` object can access other fields using a key or specifying a property, including:\n\nurl, status, statusText, headers, headersText, mimeType, requestHeaders, requestHeadersText, connectionReused, connectionId, remoteIPAddress, remotePort, fromDiskCache, fromServiceWorker, fromPrefetchCache, encodedDataLength, timing, serviceWorkerResponseSource, responseTime, cacheStorageCacheName, protocol, alternateProtocolUsage, securityState, securityDetails\n\n---\n\n### 📌 `FailInfo` Object\n\nThe `FailInfo` object is an object used to store connection failure information within the `DataPacket` object. It has the following properties:\n\n| Property Name | Data Type | Description |\n| ------------- | --------- | ----------- |\n| `errorText` | `str` | Error message text |\n| `canceled` | `bool` | Whether it is canceled |\n| `blockedReason` | `str` | Reasons for being blocked |\n| `corsErrorStatus` | `str` | CORS error status |\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/page_operation.md",
    "content": "🚤 Page Interaction\n---\n\nThis section introduces the browser page interaction feature, while element interaction is covered in the next section.\n\nA Tab object (`ChromiumTab` and `WebPageTab`) controls a browser tab and is the main unit of page control.\n\n`ChromiumPage` and `WebPage` also control a tab, but they provide additional browser-wide control capabilities.\n\nThe features described below can be used by Tab objects except for closing the browser.\n\n## ✅️️ Page Navigation\n\n### 📌 `get()`\n\nThis method is used to navigate to a URL. The program will retry when the connection fails.\n\n|   Parameter Name   |   Type    |  Default Value  | Description                           |\n|:------------------:|:---------:|:--------------:|---------------------------------------|\n|      `url`         |  `str`    |   required     | Target URL                            |\n|  `show_errmsg`     | `bool`    |   `False`      | Whether to display and raise an exception when the connection fails |\n|    `retry`         |  `int`    |     `None`     | Number of retries, uses the page parameter by default when it is `None` |\n|   `interval`       | `float`   |     `None`     | Retry interval (seconds), uses the page parameter by default when it is `None` |\n|   `timeout`        | `float`   |     None`      | Load timeout (seconds)                |\n\n|   Return Type   | Description   |\n|:------------:|--------------|\n|   `bool`     | Whether the connection is successful |\n\n**Example:**\n\n```python\npage.get('https://www.baidu.com')\n```\n\n---\n\n### 📌 `back()`\n\nThis method is used to navigate backward in the browsing history.\n\n|  Parameter Name  |  Type  |  Default Value  | Description |\n|:----------------:|:-----:|:--------------:|--------------|\n|    `steps`      | `int` |     `1`        | Number of steps to go back |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.back(2)  # Go back two web pages\n```\n\n---\n\n### 📌 `forward()`\n\nThis method is used to navigate forward in the browsing history.\n\n|  Parameter Name  |  Type  |  Default Value  | Description |\n|:----------------:|:-----:|:--------------:|--------------|\n|    `steps`      | `int` |     `1`        | Number of steps to go forward |\n\n**Returns:** `None`\n\n```python\npage.forward(2)  # Go forward two steps\n```\n\n---\n\n### 📌 `refresh()`\n\nThis method is used to refresh the current page.\n\n|   Parameter Name   |   Type    |  Default Value  | Description                             |\n|:------------------:|:---------:|:--------------:|-----------------------------------------|\n|  `ignore_cache`    | `bool`    |    `False`     | Whether to ignore cache when refreshing  |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.refresh()  # Refresh the page\n```\n\n---\n\n### 📌 `stop_loading()`\n\nThis method is used to force stop the current page from loading.\n\n**Parameter:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `set.blocked_urls()`\n\nThis method is used to set ignored URLs.\n\n|    Parameter Name  |                  Type                      | Default Value | Description                                                     |\n|:------------------:|:------------------------------------------:|:------------:|-----------------------------------------------------------------|\n|       `urls`       | `str`<br/>`list`<br/>`tuple`<br/>`None`    |   required   | URLs to be ignored, can pass multiple, can use `'*'` as wildcard, clear the set items when `None` is provided |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.set.blocked_urls('*.css*')  # Do not load CSS files\n```\n\n---\n\n## ✅️️ Element Management\n\n### 📌 `remove_ele()`\n\nThis method is used to remove an element from the page.\n\n|    Parameter Name    |                          Type                        | Default Value | Description          |\n|:--------------------:|:---------------------------------------------------:|:------------:|----------------------|\n|    `loc_or_ele`      | `str`<br/>`Tuple[str, str]`<br/>`ChromiumElement`    |   required   | The element to be removed, can be an element or a locator |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\n# Remove an element that has been obtained\nele = page('tag:a')\npage.remove_ele(ele)\n\n# Remove an element found using a locator\npage.remove_ele('tag:a')\n```\n\n---\n\n## ✅️️ Execute Scripts or Commands\n\n### 📌 `run_js()`\n\nThis method is used to execute a JavaScript script.\n\n|   Parameter Name   |   Type    |   Default Value  | Description                                                     |\n|:------------------:|:---------:|:---------------:|-----------------------------------------------------------------|\n|    `script`        |  `str`    |    required     | JavaScript script text                                          |\n|      `*args`       |     -     |       None      | Pass the arguments, which correspond to `arguments[0]`, `arguments[1]`, and so on in the JavaScript script |\n|    `as_expr`       | `bool`    |     `False`     | Whether to run as an expression, `args` parameter is invalid when `True` |\n|    `timetout`      | `float`   |     `None`      | JavaScript timeout, use the page `timeouts.script` setting if `None` |\n\n|   Return Type   | Description   |\n|:------------:|--------------|\n|     `Any`    | Script execution result |\n\n**Example:**\n\n```python\n# Execute the JavaScript script by passing in the arguments, displaying a popup with the message Hello world!\npage.run_js('alert(arguments[0]+arguments[1]);', 'Hello', ' world!')\n```\n\n:::warning Attention\n- If `as_expr` is `True`, the script should return a result and should not have a `return`.\n- If `as_expr` is not `True`, the script should be written as a method as much as possible.\n:::\n\n---\n\n### 📌 `run_js_loaded()`\n\nThis method is used to run JavaScript scripts after the page has finished loading.\n\n|   Parameter   |   Type   |  Default  | Description                                              |\n|:-------------:|:-------:|:--------:|-------------------------------------------------------|\n|    `script`   |  `str`  | Required | JavaScript script text                                   |\n|    `*args`    |    -    |    N/A   | Arguments passed to the script, corresponding to `arguments[0]`, `arguments[1]`, ... in the JavaScript text |\n|  `as_expr`    | `bool`  | `False`  | Whether to run as an expression, when `True` the `args` argument is invalid                   |\n|  `timetout`   | `float` |   `None`  | JavaScript timeout in seconds, if `None` the page's `timeouts.script` setting will be used   |\n\n| Return Type | Description |\n|:-----------:|-------------|\n|    `Any`    | Script execution result |\n\n---\n\n### 📌 `run_async_js()`\n\nThis method is used to execute JavaScript code asynchronously.\n\n**Parameters:**\n\n|   Parameter   |   Type   |  Default  | Description                                              |\n|:-------------:|:-------:|:--------:|-------------------------------------------------------|\n|    `script`   |  `str`  | Required | JavaScript script text                                   |\n|    `*args`    |    -    |    N/A   | Arguments passed to the script, corresponding to `arguments[0]`, `arguments[1]`, ... in the JavaScript text |\n|  `as_expr`    | `bool`  | `False`  | Whether to run as an expression, when `True` the `args` argument is invalid                   |\n\n**Return:** `None`\n\n---\n\n### 📌 `run_cdp()`\n\nThis method is used to execute Chrome DevTools Protocol statements.\n\nFor more information on using cdp, please see [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/).\n\n|  Parameter Name  |  Type  | Default | Description |\n|:----------------:|:-----:|:-------:|-------------|\n|      `cmd`       | `str` | Required | Protocol item |\n| `**cmd_args` |   -   |  N/A  | Project parameters |\n\n| Return Type | Description |\n|:-----------:|-------------|\n|    `dict`    | Execution result returned |\n\n**Example:**\n\n```python\n# Stop loading the page\npage.run_cdp('Page.stopLoading')\n```\n\n---\n\n### 📌 `run_cdp_loaded()`\n\nThis method is used to execute Chrome DevTools Protocol statements after making sure the page has finished loading.\n\n|  Parameter Name  |  Type  | Default | Description |\n|:----------------:|:-----:|:-------:|-------------|\n|      `cmd`       | `str` | Required | Protocol item |\n| `**cmd_args` |   -   |  N/A  | Project parameters |\n\n| Return Type | Description |\n|:-----------:|-------------|\n|    `dict`    | Execution result returned |\n\n---\n\n## ✅️️ Cookies and Cache\n\n### 📌 `set_cookies()`\n\nThis method is used to set cookies.\n\nIt can accept cookies in the formats of `CookieJar`, `list`, `tuple`, `str`, and `dict`. Note that this method does not have the `item` and `value` parameters of the latter two methods.\n\n|  Parameter Name   |                                  Type                                  | Default | Description        |\n|:-----------------:|:---------------------------------------------------------------------:|:-------:|--------------------|\n|     `cookies`     | `RequestsCookieJar`<br/>`list`<br/>`tuple`<br/>`str`<br/>`dict` | Required | Cookies information |\n\n**Return:** `None`\n\n**Example:**\n\n```python\n# It can accept multiple types of parameters\ncookies1 = ['name1=value1', 'name2=value2'],\ncookies2 = ('name1=value1', 'name2=value2', 'secure')\ncookies3 = 'name1=value1; name2=value2; path=/; domain=.example.com; secure'\ncookies4 = {'name1': 'value1', 'name2': 'value2'}\npage.set_cookies(cookies1)\n```\n\n---\n\n### 📌 `clear_cookies()`\n\nThis method is used to clear all cookies.\n\n**Parameters:** None\n\n**Return:** `None`\n\n---\n\n### 📌 `remove_cookies()`\n\nThis method is used to delete a cookie.\n\n| Parameter Name |  Type   |  Default  |      Description      |\n|:--------------:|:-------:|:---------:|-----------------------|\n|     `name`     |  `str`  |  Required | The name field of the cookie |\n|      `url`     |  `str`  |  `None`   | The url field of the cookie |\n|    `domain`    |  `str`  |  `None`   | The domain field of the cookie |\n|     `path`     |  `str`  |  `None`   | The path field of the cookie |\n\n**Return:** `None`\n\n---\n\n### 📌 `set_session_storage()`\n\nThis method is used to set or delete a particular sessionStorage item.\n\n| Parameter Name |         Type         | Default |  Description      |\n|:--------------:|:-------------------:|:-------:|-------------------|\n|     `item`     |      `str`          | Required | The item to be set |\n|    `value`     | `str`<br/>`False` | Required | When `False`, delete the item |\n\n**Return:** `None`\n\n**Example:**\n\n```python\npage.set_session_storage(item='abc', value='123')\n```\n\n---\n\n### 📌 `set_local_storage()`\n\nThis method is used to set or delete a particular localStorage item.\n\n| Parameter Name | Type               | Default Value | Description            |\n|:--------------:|:------------------:|:-------------:|------------------------|\n| `item`         | `str`              | Required      | The item to be set     |\n| `value`        | `str`<br/>`False` | Required      | When `False`, delete the item |\n\n**Returns:** `None`\n\n---\n\n### 📌 `clear_cache()`\n\nThis method is used to clear the cache and can choose the items to clear.\n\n| Parameter Name  | Type    | Default Value | Description               |\n|:--------------:|:------:|:------------:|---------------------------|\n| `session_storage` | `bool` | `True`        | Whether to clear session storage |\n| `local_storage`  | `bool` | `True`        | Whether to clear local storage   |\n| `cache`        | `bool` | `True`        | Whether to clear cache        |\n| `cookies`      | `bool` | `True`        | Whether to clear cookies      |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.clear_cache(cookies=False)  # Clear everything except cookies\n```\n\n---\n\n## ✅️️ Run parameter settings\n\nVarious settings are hidden in the `set` attribute.\n\n### 📌 `set.retry_times()`\n\nThis method is used to set the number of retries when the connection fails.\n\n| Parameter Name | Type   | Default Value | Description |\n|:-------------:|:------:|:-----------:|-------------|\n| `times`       | `int`  | Required    | Number of times |\n\n**Returns:** `None`\n\n### 📌 `set.retry_interval()`\n\nThis method is used to set the interval between retries when the connection fails.\n\n| Parameter Name | Type   | Default Value | Description |\n|:-------------:|:------:|:------------:|-------------|\n| `interval`    | `float` | Required    | Number of seconds |\n\n**Returns:** `None`\n\n### 📌 `set.timeouts()`\n\nThis method is used to set three types of timeout times, in seconds. Can be set individually, `None` means not to change the original settings.\n\n| Parameter Name  | Type   | Default Value | Description     |\n|:--------------:|:------:|:------------:|-----------------|\n| `base`         | `float` | `None`       | Overall timeout |\n| `page_load`    | `float` | `None`       | Page load timeout |\n| `script`       | `float` | `None`       | Script runtime timeout |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.set.timeouts(base=10, page_load=30)\n```\n\n---\n\n### 📌 `set.load_strategy`\n\nThis property is used to set the page loading strategy, call its method to choose a strategy.\n\n| Method Name  | Parameter | Description                                  |\n|:------------:|:--------:|----------------------------------------------|\n| `normal()`   | None     | Wait for the page to be completely loaded, default |\n| `eager()`    | None     | Wait for the document to be loaded and do not wait for resources |\n| `none()`     | None     | Finish loading the page |\n\n**Example:**\n\n```python\npage.set.load_strategy.normal()\npage.set.load_strategy.eager()\npage.set.load_strategy.none()\n```\n\n---\n\n### 📌 `set.user_agent()`\n\nThis method is used to set the user agent for the current browser tab.\n\n| Parameter Name | Type   | Default Value | Description            |\n|:-------------:|:------:|:------------:|------------------------|\n| `ua`          | `str`  | Required    | User agent string      |\n| `platform`    | `str`  | `None`       | Platform type, such as `'android'` |\n\n**Returns:** `None`\n\n---\n\n### 📌 `set.headers()`\n\nThis method is used to set additional parameters to add to the current page request headers.\n\n| Parameter Name | Type   | Default Value | Description            |\n|:-------------:|:------:|:------------:|------------------------|\n| `headers`     | `dict` | Required    | Headers in dict format |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\nh = {'connection': 'keep-alive', 'accept-charset': 'GB2312,utf-8;q=0.7,*;q=0.7'}\npage.set.headers(headers=h)\n```\n\n---\n\n## ✅️️ Window management\n\nWindow management functions are hidden in the `set.window` attribute.\n\n### 📌 `set.window.max()`\n\nThis method is used to maximize the window.\n\n**Parameter:** None\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.set.window.max()\n```\n\n---\n\n### 📌 `set.window.mini()`\n\nThis method is used to minimize the window.\n\n**Parameter:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `set.window.full()`\n\nThis method is used to switch the window to full screen mode.\n\n**Parameter:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `set.window.normal()`\n\nThis method is used to switch the window to normal mode.\n\n**Parameter:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `set.window.size()`\n\nThis method is used to set the window size. When only one parameter is passed, the other parameter will not change.\n\n| Parameter Name | Type   | Default Value | Description |\n|:-------------:|:------:|:------------:|-------------|\n| `width`       | `int`  | `None`       | Window width |\n| `height`      | `int`  | `None`       | Window height |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.set.window.size(500, 500)\n```\n\n---\n\n### 📌 `set.window.location()`\n\nThis method is used to set the position of the window. When only one parameter is passed, the other parameter will not change.\n\n| Parameter Name |   Type  |  Default Value | Description |\n|:--------------:|:-------:|:-------------:|-------------|\n|      `x`       | `int`   |     `None`    |  Distance from the top |\n|      `y`       | `int`   |     `None`    | Distance from the left |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.set.window.location(500, 500)\n```\n\n---\n\n### 📌 `set.window.hide()`\n\nThis method is used to hide the browser window.\n\nUnlike headless mode, this method directly hides the browser process. It will also disappear from the taskbar. It only supports Windows system and requires the installation of the pypiwin32 library to be used.\n\nHowever, after the window is hidden, if a new window appears, the entire browser will become visible again.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.set.window.hide()\n```\n\n:::warning Note\n   - After hiding the browser, it is not closed. The hidden browser will be taken over by the next program run.\n   - After the browser is hidden, if new tabs are opened, they will be displayed automatically.\n:::\n\n---\n\n### 📌 `set.window.show()`\n\nThis method is used to show the current browser window.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n## ✅️️ Page Scrolling\n\nThe functionality of page scrolling is hidden in the `scroll` attribute.\n\n### 📌 `scroll.to_top()`\n\nThis method is used to scroll the page to the top, with the horizontal position unchanged.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.scroll.to_top()\n```\n\n---\n\n### 📌 `scroll.to_bottom()`\n\nThis method is used to scroll the page to the bottom, with the horizontal position unchanged.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `scroll.to_half()`\n\nThis method is used to scroll the page to the middle vertical position, with the horizontal position unchanged.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `scroll.to_rightmost()`\n\nThis method is used to scroll the page to the rightmost position, with the vertical position unchanged.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `scroll.to_leftmost()`\n\nThis method is used to scroll the page to the leftmost position, with the vertical position unchanged.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `scroll.to_location()`\n\nThis method is used to scroll the page to the specified location.\n\n| Method |  Parameter | Description |\n|:------:|:----------:|-------------|\n|   `x`  |  Required  | Horizontal position |\n|   `y`  |  Required  | Vertical position |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.scroll.to_location(300, 50)\n```\n\n---\n\n### 📌 `scroll.up()`\n\nThis method is used to scroll the page up by a certain number of pixels, with the horizontal position unchanged.\n\n|  Method  | Parameter | Description |\n|:-------:|:---------:|-------------|\n| `pixel` |  Required | Scrolling pixels |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.scroll.up(30)\n```\n\n---\n\n### 📌 `scroll.down()`\n\nThis method is used to scroll the page down by a certain number of pixels, with the horizontal position unchanged.\n\n|  Method  | Parameter | Description |\n|:-------:|:---------:|-------------|\n| `pixel` |  Required | Scrolling pixels |\n\n**Returns:** `None`\n\n---\n\n### 📌 `scroll.right()`\n\nThis method is used to scroll the page to the right by a certain number of pixels, with the vertical position unchanged.\n\n|  Method  | Parameter | Description |\n|:-------:|:---------:|-------------|\n| `pixel` |  Required | Scrolling pixels |\n\n**Returns:** `None`\n\n---\n\n### 📌 `scroll.left()`\n\nThis method is used to scroll the page to the left by a certain number of pixels, with the vertical position unchanged.\n\n|  Method  | Parameter | Description |\n|:-------:|:---------:|-------------|\n| `pixel` |  Required | Scrolling pixels |\n\n**Returns:** `None`\n\n### 📌 `scroll.to_see()`\n\nThis method is used to scroll the page until the element is visible.\n\n| Parameter Name | Type                                | Default | Description                                             |\n| -------------- | ----------------------------------- | ------- | ------------------------------------------------------- |\n| `loc_or_ele`   | `str`<br/>`tuple`<br/>`ChromiumElement` | Required  | Location information of the element, can be element or locator |\n| `center`       | `bool`<br/>`None`                    | `None`  | Whether to scroll to the center of the page, if `None`, scroll to the center if blocked |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\n# Scroll to an element already obtained\nele = page.ele('tag:div')\npage.scroll.to_see(ele)\n\n# Scroll to an element found by locator\npage.scroll.to_see('tag:div')\n```\n\n---\n\n## ✅️️ Scrolling Settings\n\nThere are two ways to scroll the page: directly jumping to the target position or smooth scrolling, which takes some time. The latter scrolling time is difficult to determine, which can lead to program instability and inaccurate clicks.\n\nSome websites specify the use of smooth scrolling in the CSS settings, which is not what we want. However, in order to give developers the full right to choose, this library does not force modification, but provides two settings for developers to choose from.\n\n### 📌 `set.scroll.smooth()`\n\nThis method sets whether the website is enabled for smooth scrolling. It is recommended to use this method to disable smooth scrolling for web pages.\n\n| Parameter Name | Type  | Default | Description |\n| -------------- | ----- | ------- | ----------- |\n| `on_off`       | `bool` | `True`  | `bool` indicates on or off |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.set.scroll.smooth(on_off=False)\n```\n\n---\n\n### 📌 `set.scroll.wait_complete()`\n\nThis method is used to set whether to wait for the scroll to complete after scrolling. When you do not want to turn off the smooth scrolling function of the web page, you can enable this setting to ensure that the scroll completes before performing the subsequent steps.\n\n| Parameter Name | Type  | Default | Description |\n| -------------- | ----- | ------- | ----------- |\n| `on_off`       | `bool` | `True`  | `bool` indicates on or off |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.set.scroll.wait_complete(on_off=True)\n```\n\n---\n\n## ✅️️ Popup Message Handling\n\n### 📌 `handle_alert()`\n\nThis method is used to handle alert messages.\nIt can set the waiting time, so that the message box will not be processed until it appears. If the message box is not received within the timeout, it returns `False`.\nIt can also only get the text of the message box without processing the message box.\nIt can also handle the next prompt box that appears, which is very useful when handling pop-ups triggered when leaving the page.\n\n:::warning Note\n    The program cannot take over a browser or tab that has already displayed a prompt box.\n:::\n\n| Parameter Name | Type                 | Default | Description                                                                 |\n| -------------- | -------------------- | ------- | --------------------------------------------------------------------------- |\n| `accept`       | `bool`<br/>`None`    | `True`  | `True` to confirm, `False` to cancel, `None` does not click a button but still returns the text value |\n| `send`         | `str`                | `None`  | When handling the prompt prompt box, you can enter text                      |\n| `timeout`      | `float`              | `None`  | Timeout for waiting for the prompt box to appear, `None` uses the overall timeout of the page |\n| `next_one`     | `bool`               | `False` | Whether to handle the next popup, `timeout` parameter is invalid when `True` |\n\n| Return Type | Description                                       |\n| ----------- | ------------------------------------------------- |\n| `str`       | Text content of the prompt box                     |\n| `False`     | Returns `False` if the prompt box is not received |\n\n**Example:**\n\n```python\n# Confirm the alert and get the text content of the alert\ntxt = page.handle_alert()\n\n# Click cancel\npage.handle_alert(accept=False)\n\n# Enter text for the prompt box and click OK\npage.handle_alert(accept=True, send='some text')\n\n# Do not process the message box, just get the text content of the message box\ntxt = page.handle_alert(accept=None)\n```\n\n---\n\n### 📌 Automatic Processing\n\nYou can use the `set.auto_handle_alert()` method to set automatic processing of the alert box, so that the alert box will not pop up and will be processed directly.\n\n| Parameter Name | Type  | Default | Description                 |\n| -------------- | ----- | ------- | --------------------------- |\n| `on_off`       | `bool` | `True`  | `bool` indicates on or off    |\n| `accept`       | `bool` | `True`  | `bool` indicates confirm or cancel |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\n\np = ChromiumPage()\np.set.auto_handle_alert()  # After this, pop-ups will be automatically confirmed\n```\n\n---\n\n## ✅️️ Closing and Reconnecting\n\n### 📌 `disconnect()`\n\nThis method is used to disconnect the connection between the page object and the page, but does not close the tab. After disconnecting, the object cannot operate on the tab.\n\nPage, Tab, and `ChromiumFrame` objects all have this method.\n\nIt is worth noting that after the Page object breaks the connection with the browser, it can still manage tabs.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `reconnect()`\n\nThis method is used to close the connection to the page and then establish a new connection.\n\nThis is mainly used to deal with long-running processes that result in excessive memory usage. Disconnecting frees up memory, and then reconnecting allows for continued control of the browser.\n\nThe `Page`, `Tab`, and `ChromiumFrame` objects all have this method.\n\n|  Parameter  |   Type    | Default Value | Description |\n|:------:|:-------:|:---:|-------------|\n| `wait` | `float` | `0` | Number of seconds to wait before reconnecting after closing |\n\n**Returns:** `None`\n\n---\n\n### 📌 `quit()`\n\nThis method is used to close the browser.\n\n|   Parameter    |   Type    |   Default Value   | Description                                         |\n|:---------:|:-------:|:-------:|--------------------------------------------|\n| `timeout` | `float` |   `5`   | Timeout for waiting for the browser to close |\n|  `force`  | `bool`  | `False` | Whether to forcibly terminate the process immediately      |\n\n**Returns:** `None`\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/screen_recording.md",
    "content": "🚤 Screenshots and Recordings\n---\n\n## ✅️️ Page Screenshots\n\nUse the `get_screenshot()` method of the page object to take screenshots of the page, including the entire webpage, visible webpage, or a specified area.\n\nTaking screenshots outside the visible range requires browser support of version 90 or above.\n\nChoose one of the following three parameters: `as_bytes`>`as_base64`>`path`.\n\n| Parameter Name | Type   | Default | Description                                               |\n| :------------: | :----: | :-----: | --------------------------------------------------------- |\n|    `path`      | string | `None`  | The path to save the image. Defaults to the current folder.                           |\n|    `name`      | string | `None`  | The complete file name. Optional extensions: `'jpg'`, `'jpeg'`, `'png'`, `'webp'`. Defaults to jpg.                                                  |\n|   `as_bytes`   | string | `True`  | Whether to return the image as bytes. Optional extensions: `'jpg'`, `'jpeg'`, `'png'`, `'webp'`, `None`, `True`. If not `None`, the `path` parameter is ignored. Defaults to jpg.                   |\n|  `as_base64`   | string | `None`  | Whether to return the image as base64 encoded string. Optional extensions: `'jpg'`, `'jpeg'`, `'png'`, `'webp'`, `None`, `True`. If not `None`, the `path` parameter is ignored. Defaults to jpg. |\n|  `full_page`   | boolean| `False` | Whether to take a full-page screenshot. Set to `True` for the entire webpage, `False` for the visible viewport only.                            |\n|   `left_top`   | tuple  | `None`  | The top-left coordinates of the screenshot area.                                         |\n| `right_bottom` | tuple  | `None`  | The bottom-right coordinates of the screenshot area.                                     |\n\n| Return Type | Description                                                           |\n| :--------: | --------------------------------------------------------------------- |\n|   bytes    | Returns the image bytes when `as_bytes` is in effect.                    |\n|   string   | Returns the complete path of the image when `as_bytes` and `as_base64` are `None`. |\n|   string   | Returns the base64 encoded string when `as_base64` is in effect.          |\n\n:::info Information\n    If `path` contains the filename, the `name` parameter is ignored.\n:::\n\n**Example:**\n\n```python\n# Take a full-page screenshot and save it\npage.get_screenshot(path='tmp', name='pic.jpg', full_page=True)\n```\n\n## ️️ ✅️️ Element Screenshots\n\nUse the `get_screenshot()` method of the element object to take screenshots of the element.\n\nIf the element is outside the viewport, it requires browser support of version 90 or above.\n\nChoose one of the following three parameters: `as_bytes`>`as_base64`>`path`.\n\n|  Parameter Name  |    Type    |  Default | Description                                                      |\n| :-------------: | :-------: | :------: | ---------------------------------------------------------------- |\n|     `path`     |  string | `None`  | The path to save the image. Defaults to the current folder.                        |\n|     `name`     |  string | `None`  | The complete file name. Optional extensions: `'jpg'`, `'jpeg'`, `'png'`, `'webp'`. Defaults to jpg.                                   |\n|   `as_bytes`   |  string | `None`  | Whether to return the image as bytes. Optional extensions: `'jpg'`, `'jpeg'`, `'png'`, `'webp'`, `None`, `True`. If not `None`, the `path` and `as_base64` parameters are ignored. Defaults to jpg. |\n|  `as_base64`  |  string | `None`  | Whether to return the image as base64 encoded string. Optional extensions: `'jpg'`, `'jpeg'`, `'png'`, `'webp'`, `None`, `True`. If not `None`, the `path` parameter is ignored. Defaults to jpg.       |\n| `scroll_to_center` | boolean | `True` | Whether to scroll to the center of the viewport before taking the screenshot. |\n\n| Return Type | Description                                                           |\n| :--------: | --------------------------------------------------------------------- |\n|   bytes    | Returns the image bytes when `as_bytes` is in effect.                    |\n|   string   | Returns the complete path of the image when `as_bytes` and `as_base64` are `None`. |\n|   string   | Returns the base64 encoded string when `as_base64` is in effect.          |\n\n:::info Information\n    If `path` contains the filename, the `name` parameter is ignored.\n:::\n\n**Example:**\n\n```python\nimg = page('tag:img')\nimg.get_screenshot()\nbytes_str = img.get_screenshot(as_bytes='png')  # Returns the screenshot as a binary string\n```\n\n---\n\n## ✅️️ Screen Recording\n\nUse the `screencast` functionality of the page object to take screenshots or record screen videos.\n\n### 📌 Set Recording Mode\n\nThere are a total of 5 recording modes, which can be set using the `screencast.set_mode.xxx_mode()` method.\n\n| Mode                   | Description                                             |\n|:----------------------:| ------------------------------------------------------- |\n| `video_mode()`         | Continuously records the page and generates a video without sound when stopped. |\n| `frugal_video_mode()`  | Records the page only when there is a change, and generates a video without sound when stopped. |\n| `js_video_mode()`🧪    | Generates a video with sound, but needs to be manually started. This feature is still in testing phase. |\n| `imgs_mode()`          | Continuously takes screenshots of the page.              |\n| `frugal_imgs_mode()`   | Takes screenshots of the page only when there is a change. |\n\n### 📌 Set Save Path\n\nUse `screencast.set_save_path()` to set the path to save the recording results.\n\n| Parameter     | Type              | Default | Description              |\n|:-------------:|:-----------------:|:-------:| ------------------------ |\n| `save_path`   | `str`<br/>`Path` | `None`  | Path to save images or videos. |\n\n**Returns:** `None`\n\n### 📌 `screencast.start()`\n\nThis method is used to start recording the browser window.\n\n| Parameter     | Type              | Default | Description              |\n|:-------------:|:-----------------:|:-------:| ------------------------ |\n| `save_path`   | `str`<br/>`Path` | `None`  | Path to save images or videos. |\n\n**Returns:** `None`\n\n:::warning Note\n    The save path must be set, regardless of whether it is set using `screencast.set()` or `screencast.start()`.\n:::\n\n### 📌 `screencast.stop()`\n\nThis method is used to stop recording the screen.\n\n| Parameter     | Type    | Default | Description                          |\n|:-------------:|:-------:|:-------:| ----------------------------------- |\n| `video_name`  | `str`   | `None`  | Video file name. If `None`, it is named with the current time. |\n\n| Return Type   | Description                                         |\n|:-------------:| --------------------------------------------------- |\n| `str`         | Returns the path of the video file if saved as a video, otherwise returns the folder path of the saved image. |\n\n### 📌 Note\n\n- When using `video_mode` and `frugal_video_mode`, the save path and file name must be in English.\n\n- When using `video_mode` and `frugal_video_mode`, the opencv library needs to be installed first. `pip install opencv-python`\n\n- When using `js_video_mode`, the target to be recorded needs to be manually selected with the mouse before recording can begin.\n\n- When using `js_video_mode`, if recording of a window is desired, recording needs to be started in another window. Otherwise, if the window jumps, the recording will be invalidated.\n\n### 📌 Example\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.screencast.set_save_path('video')  # Set the video save path\npage.screencast.set_mode.video_mode()  # Set the recording mode\npage.screencast.start()  # Start recording\nsleep(3)\npage.screencast.stop()  # Stop recording\n```\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/tab_operation.md",
    "content": "🚤 Tab Page Operations\n---\n\nThis section introduces the management and usage skills of browser tabs.\n\nA Tab object (`ChromiumTab` and `WebPageTab`) controls a browser tab and is the main unit of page control.\n\nA tab can also be controlled by multiple Tab objects at the same time (singleton needs to be disabled).\n\n`ChromiumPage` and `WebPage` are tab managers that also control a tab, but they add some overall browser control functions.\n\n:::info Explanation\n    `ChromiumPage` and `WebPage` have all the functionality of tab control.\n    `ChromiumTab` and `WebPageTab`, on the other hand, only have the ability to close and activate themselves.\n:::\n\n## ✅️️ Multi-Tab Usage\n\nSelenium does not have a tab object, and the driver can only manipulate one tab at a time. When using multiple tabs, you need to switch back and forth between different tabs, and when switching, you will lose the elements that you have previously acquired, making it inefficient and inconvenient to use.\n\nDrissionPage supports the coexistence of multiple tab objects, which do not affect each other, and tabs can be operated without being activated. In addition, the focus switching handles the complexity and stability issues maintained by the proxy.\n\nTherefore, DrissionPage does not provide the ability to switch in and out of tabs. Instead, you can use the `get_tab()` or `new_tab()` methods to get the specified tab object for operation.\n\nThe logic of operation is the same as that of the Page object.\n\n**Example 1: Create a new tab**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\ntab = page.new_tab()  # Create a new tab and get the tab object\ntab.get('https://www.baidu.com')  # Operate the tab using the tab object\n```\n\n**Example 2: Get a specific tab**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.ele('Some link').click()  # Click on a link to create a new tab\npage.wait.new_tab()  # Wait for the new tab to appear\ntab = page.get_tab(page.latest_tab)  # Get the specified tab object\ntab.get('https://www.baidu.com')  # Operate the tab using the tab object\n```\n\n---\n\n## ✅️️ Tab Overview\n\n### 📌 `tabs_count`\n\nThis property returns the number of tabs.\n\n**Type:** `int`\n\n```python\nprint(page.tabs_count)\n```\n\n**Output:**\n\n```shell\n2\n```\n\n---\n\n### 📌 `tabs`\n\nThis property returns all tab IDs as a list.\n\n**Type:** `List[str]`\n\n```python\nprint(page.tabs)\n```\n\n**Output:**\n\n```shell\n['0B300BEA6F1F1F4D5DE406872B79B1AD', 'B838E91F38121B32940B47E8AC59D015']\n```\n\n---\n\n## ✅️️ Create a New Tab\n\n### 📌 `new_tab()`\n\nThis method is used to create a new tab at the end.\n\nOnly the Page object has this method.\n\n|    Parameter    |        Type        | Default | Description                                                                   |\n|:--------------:|:-----------------:|:-------:|-------------------------------------------------------------------------------|\n|     `url`      | `str`<br/>`None` | `None`  | The URL to be visited by the new tab, if not passed in, an empty tab is created |\n|  `new_window`  |       `bool`      | `False` | Whether to open the tab in a new window                                       |\n|  `background`  |       `bool`      | `False` | Whether to not activate the new tab, invalid if `new_window` is `True`         |\n| `new_context`  |       `bool`      | `False` | Whether to create a new context, if `True`, opens a new window in incognito mode, and the new window does not share cookies with other windows |\n\n|   Return Type    | Description                                                   |\n|:---------------:|--------------------------------------------------------------|\n| `ChromiumTab`  | `ChromiumPage`'s `new_tab()` returns a `ChromiumTab` object      |\n| `WebPageTab` | `WebPage`'s `new_tab()` returns a `WebPageTab` object |\n\n**Example:**\n\n```python\npage.new_tab(url='https://www.baidu.com')\n```\n\n---\n\n### 📌 `wait.new_tab()`\n\nThis method is used to wait for a new tab to appear.\n\nOnly the Page object has this method.\n\n| Parameter |  Type  |  Default  | Description                                         |\n|:---------:|:------:|:---------:|-----------------------------------------------------|\n| `timeout` | `float` |   `None`  | Timeout in seconds, if `None`, uses the page's `timeout` setting |\n| `raise_err` | `bool` |   `None`  | Whether to raise an error if waiting fails, if `None`, follows the `Settings` setting |\n\n|  Return Type  | Description                 |\n|:------------:|-----------------------------|\n|    `str`     | Returns the ID of the new tab if successful |\n|    `False`   | Returns `False` if waiting fails |\n\n---\n\n## ✅️️ Get Tab Object\n\n### 📌 `get_tab()`\n\nThis method is used to get a tab object. You can specify the index or ID of the tab.\n\nOnly the Page object has this method.\n\n|   Parameter   |           Type          |  Default | Description                                              |\n|:------------:|:----------------------:|:--------:|----------------------------------------------------------|\n| `id_or_num` | `str`<br/>`int`<br/>`None` | `None` | The ID or index (starting from `1` and negative numbers indicating reverse) of the tab to get, default is `None` to get the current tab |\n\n|   Return Type    |                            Description                            |\n|:---------------:|-------------------------------------------------------------------|\n|  `ChromiumTab`  | The tab object                                               |\n\n:::warning Warning\n    If a sequence number is passed, the sequence number may not correspond to the visual order of the tabs, but will be arranged based on the activation order.\n:::\n\n**Example:**\n\n```python\ntab = page.get_tab()  # Get the current tab object\ntab = page.get_tab(1)  # Get the object of the second tab in the list\ntab = page.get_tab('5399F4ADFE3A27503FFAA56390344EE5')  # Get the object of the specified id tab in the list\n```\n\n---\n\n### 📌 `find_tabs()`\n\nThis method is used to find tabs that meet the specified conditions. Only Page objects have this method.\n\nThe `title`, `url`, and `tab_type` parameters are three search conditions, and they are connected with the \"and\" relationship.\n\nThe parameters `title`, `url`, and `tab_type` are connected with the relationship \"and\".\n\n| Parameter Name |        Type       | Default | Description                                            |\n|:--------------:|:-----------------:|:-------:|--------------------------------------------------------|\n|     `title`    |       `str`       | `None`  | Match tabs that contain this text in the title, match all when `None` |\n|      `url`     |       `str`       | `None`  | Match tabs that contain this text in the URL, match all when `None` |\n|  `tab_type`    | `str`, `list`, `tuple`, `set` | `None` | Match tabs of this type, you can enter multiple types, match all when `None` |\n|    `single`    |       `bool`      |  `True` | If `True`, return the id of the first result; if `False`, return all information |\n\n|   Return Type  | Description                                            |\n|:--------------:|--------------------------------------------------------|\n|     `str`      | When `single` is `True`, return the tab id               |\n| `List[dict]`   | When `single` is `False`, return all tab information     |\n\n**Example:**\n\nFind tabs that contain `'baidu.com'` in the URL and create an object:\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('https://www.baidu.com')\n\ntab_id = page.find_tabs(url='baidu.com')\nprint(tab_id)\n```\n\n**Output:**\n\n```shell\n'8460E5D55BCA5798AF83BC4D243652A9'\n```\n\nGet all tab information:\n\n```python\ntabs = page.find_tabs(single=False)\nprint(tab)\n```\n\n**Output:**\n\n```shell\n[{'description': '',\n  'devtoolsFrontendUrl': '/devtools/inspector.html?ws=127.0.0.1:9222/devtools/page/8460E5D55BCA5798AF83BC4D243652A9',\n  'faviconUrl': 'https://www.baidu.com/img/baidu_85beaf5496f291521eb75ba38eacbd87.svg',\n  'id': '8460E5D55BCA5798AF83BC4D243652A9',\n  'title': '百度一下，你就知道',\n  'type': 'page',\n  'url': 'https://www.baidu.com/',\n  'webSocketDebuggerUrl': 'ws://127.0.0.1:9222/devtools/page/8460E5D55BCA5798AF83BC4D243652A9'}]\n```\n\n---\n\n### 📌 `latest_tab`\n\nThis property returns the id of the last activated tab, which refers to the most recently appeared or newly activated tab.\n\nOnly Page objects have this property.\n\n**Type:** `str`\n\n**Example:**\n\n```python\n# Open a tab\nele.click()\n# Get the object of the latest tab\ntab = page.get_tab(page.latest_tab)  # Equivalent to page.get_tab(0)\n```\n\n---\n\n## ✅️️ Using Multiple Instances\n\nBy default, a Tab object is a singleton, which means that each tab has only one object, even if `get_tab()` is used repeatedly, the same object will be obtained.\n\nThis is mainly to prevent beginners from misunderstanding the mechanism and creating multiple connections repeatedly, which may cause resource waste.\n\nMultiple Tab objects are allowed to be operated on the same tab at the same time, each responsible for different tasks. For example, one Tab object executes the main logic flow, and the other monitors the page and handles various pop-ups.\n\nTo allow multiple instances, use `Settings` to set:\n\n```python\nfrom DrissionPage.common import Settings\n\nSettings.singleton_tab_obj = False\n```\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom DrissionPage.common import Settings\n\npage = ChromiumPage()\npage.new_tab()\npage.new_tab()\n\n# Without using multiple instances:\ntab1 = page.get_tab(1)\ntab2 = page.get_tab(1)\nprint(id(tab1), id(tab2))\n\n# Using multiple instances:\nSettings.singleton_tab_obj = False\ntab1 = page.get_tab(1)\ntab2 = page.get_tab(1)\nprint(id(tab1), id(tab2))\n```\n\n**Output:**\n\nThe first output shows that the two Tab objects are the same, while the second output shows that they are independent.\n\n```shell\n2347582903056 2347582903056\n2347588741840 2347588877712\n```\n\n---\n\n## ✅️️ Closing and Reconnecting\n\n### 📌 `close()`\n\nThis method is used to close the tab.\n\nThis method can be called on both Page objects and Tab objects.\n\nIt should be noted that the Page object can still manage other tabs after closing a tab.\n\n**Parameters:** None\n\n**Returns:** None\n\n---\n\n### 📌 `disconnect()`\n\nThis method is used to disconnect the page object from the browser, but does not close the tab. After disconnecting, the object cannot perform operations on the tab.\n\nBoth the Page object and Tab object have this method.\n\nIt should be noted that the Page object can still manage tabs after disconnecting from the browser.\n\n**Parameters:** None\n\n**Returns:** None\n\n---\n\n### 📌 `reconnect()`\n\nThis method is used to close the connection to the page and then rebuild a new connection.\n\nThis is mainly used to cope with long-term running causing excessive memory usage. Disconnecting can release memory, and then reconnect to continue controlling the browser.\n\nBoth the Page, Tab, and `ChromiumFrame` objects have this method.\n\n| Parameter Name |  Type  | Default Value | Description |\n|:-------------:|:-----:|:-------------:|----------------------|\n|     `wait`    |  float  |       0       |  How many seconds to wait before reconnecting after closing |\n\n**Returns:** None\n\n---\n\n### 📌 `close_tabs()`\n\nThis method is used to close the specified tabs, and multiple tabs can be closed. The current tab is closed by default.\n\nIf the closed tabs include the current tab, it will switch to the first remaining tab, but not necessarily the visually first one.\n\nOnly the Page object has this method.\n\n|  Parameter Name   |                                                                                                                                              Type                                                                                                                                               | Default Value |                                  Description                                  |\n|:----------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-------------:|-------------------------------------------------------------------------------|\n|  `tabs_or_ids`   | `str`, `None`, `ChromiumTab`, `List[str, ChromiumTab]`, `Tuple[str, ChromiumTab]`                                                                                                                                                                                                            |     None      | The tab objects or IDs to be processed, can be passed in as a list or tuple, `None` means process the current tab                            |\n|     `others`     |                                                                                                                                               bool                                                                                                                                               |     True      | Whether to close tabs other than the specified ones                                                                                                                                                  |\n\n**Returns:** None\n\n**Example:**\n\n```python\n# Close the current tab\npage.close_tabs()\n\n# Close the 1st and 3rd tabs\ntabs = page.tabs\npage.close_tabs(tabs_or_ids=(tabs[0], tabs[2]))\n```\n\n---\n\n### 📌 `close_other_tabs()`\n\nThis method is used to close tabs other than the ones passed in, and the current tab is kept by default. Multiple tabs can be passed in.\n\nIf the closed tabs include the current tab, it will switch to the first remaining tab, but not necessarily the visually first one.\n\nOnly the Page object has this method.\n\n|  Parameter Name   |                                                                                                                                              Type                                                                                                                                               | Default Value |                                  Description                                  |\n|:----------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-------------:|-------------------------------------------------------------------------------|\n|  `tabs_or_ids`   | `str`, `None`, `ChromiumTab`, `List[str, ChromiumTab]`, `Tuple[str, ChromiumTab]`                                                                                                                                                                                                            |     None      | The tab objects or IDs to be processed, can be passed in as a list or tuple, `None` means process the current tab                            |\n\n**Returns:** None\n\n**Example:**\n\n```python\n# Close all tabs except the current tab\npage.close_other_tabs()\n\n# Close all tabs except the first tab\npage.close_other_tabs(page.tab[0])\n\n# Close tabs except for specified IDs\nreserve_list = ('0B300BEA6F...', 'B838E91...')\npage.close_other_tabs(reserve_list)\n```\n\n---\n\n## ✅️ Activate Tab\n\n### 📌 `set.tab_to_front()`\n\nThis method is used to activate a tab and bring it to the front. However, it does not shift the focus to the tab.\n\nOnly the Page object has this method.\n\n|  Parameter Name |         Type         | Default Value |                               Description                               |\n|:--------------:|:--------------------:|:-------------:|:----------------------------------------------------------------------:|\n|  `tab_or_id`   | `str`, `ChromiumTab`, `None` |     None      | The tab object or ID, the default is `None` which means the current tab |\n\n**Returns:** None\n\n---\n\n### 📌 `set.activate()`\n\nThis method is used to activate the Tab object or Page object.\n\n**Parameters:** None\n\n**Returns:** None\n\n---\n\n## ✅️️ Multiple Tab Collaboration\n\nWhen doing automation, we often encounter a scenario where we have a list page and need to open the links in the list one by one to get the content of the new page. Each link will open a new page.\n\nIf we use Selenium, after clicking on a link, we must switch the focus to the new tab, collect the information, and then go back to the original page and click on the next link. However, due to the focus switch, the information of the original elements has been lost, and we can only re-get all the links and click on the next link using counting, which is very ungraceful.\n\nWith `ChromiumPage`, there is no need to move the focus after opening a tab. You can directly generate a new page object for a new tab and collect the information on the new page, while the original list page object can continue to operate on the next link. You can even control multiple tabs using multithreading to achieve various black technologies.\n\nLet's demonstrate this using the Gitee recommended projects page: [Latest recommended projects - Gitee.com](https://gitee.com/explore/all)\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('https://gitee.com/explore/all')\n\nlinks = page.eles('t:h3')\nfor link in links[:-1]:\n    # Click the link\n    link.click()\n    # Wait for the new tab to appear\n    page.wait.new_tab()\n    # Get the new tab object\n    new_tab = page.get_tab(0)\n    # Wait for the new tab to load\n    new_tab.wait.load_start()\n    # Print the title of the tab\n    print(new_tab.title)\n    # Close all tabs except the list page\n    page.close_other_tabs()\n```\n\n\n\n**Output:**\n\n```shell\nwx-calendar: Native Mini Program Calendar Component (Scrollable, Markable, Disableable)\nthingspanel-go: Open Source Plugin-based IoT Platform developed in Go. Supports MQTT, Modbus multi-protocol, multi-type device access and visualization, automation, alarm, rule engine and other functions. QQ Group: 371794256.\nAPITable: Community Edition of vika.cn, an open source low-code, multi-dimensional table tool. An open and free alternative to Airtable.\nideaseg: Chinese word segmentation plug-in based on NLP technology, with much higher accuracy than commonly used segmenters, also provides ElasticSearch and OpenSearch plug-ins.\nvue-plugin-hiprint: hiprint for Vue2/Vue3 ⚡Printing, print design, visual designer, report design, element editing, visual print editing.\nExDUIR.NET: Lightweight DirectUI framework for Windows platform\n\nThe rest is omitted...\n```\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/upload_files.md",
    "content": "🚤 File Upload\n---\n\nThere are two ways to upload a file:\n\n- Find the `<input>` element and insert the file path.\n\n- Intercept the file input box and automatically fill in the path.\n\n## ✅️️ Traditional Method\n\nThe first method is the traditional method, where developers need to find the file upload control in the DOM and use the `input()` method of the element object to insert the path.\n\nThe file upload control is an `<input>` element with the `type` attribute set to `'file'`, and the file path can be entered into the element. Its usage is the same as entering text.\n\nThe only difference is that\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/visit_web_page.md",
    "content": "🚤 Visit Web Page\n---\n\nBoth `ChromiumPage` and `WebPage` objects can control browser access to web pages. Here we will only explain `ChromiumPage`, and `WebPage` will be introduced separately in later chapters.\n\n## ✅️️ `get()`\n\nThis method is used to navigate to a URL. When the connection fails, the program will retry.\n\n| Parameter Name | Type    | Default | Description          |\n| :------------: | :-----: | :-----: | -------------------- |\n| `url`          | `str`   | Required| Target URL           |\n| `show_errmsg`  | `bool`  | `False` | Whether to display and raise exceptions when a connection error occurs |\n| `retry`        | `int`   | `None`  | Number of retries, defaults to 3 |\n| `interval`     | `float` | `None`  | Retry interval (seconds), defaults to 2 |\n| `timeout`      | `float` | `None`  | Timeout for loading (seconds) |\n\n| Return Type | Description |\n| :---------: | ----------- |\n| `bool`      | Whether the connection is successful |\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('https://www.baidu.com')\n```\n\n---\n\n## ✅️️ Setting Timeout and Retry\n\nWhen the network is unstable, accessing a web page may not always be successful. The `get()` method has built-in timeout and retry functions. They can be set using the `retry`, `interval`, and `timeout` parameters.  \nIf the `timeout` parameter is not specified, it will use the value in the `page_load` parameter of `ChromiumPage`'s `timeouts` attribute.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('https://g1879.gitee.io/drissionpagedocs', retry=1, interval=1, timeout=1.5)\n```\n\n---\n\n## ✅️️ Load Strategies\n\n### 📌 Overview\n\nThe load strategy refers to the behavior pattern of the program during the page loading phase. There are three types:\n\n- `normal()`: Normal mode, will wait for the page to finish loading and automatically retry or stop if timeout (default mode)\n- `eager()`: Load DOM or stop loading immediately if timeout, without loading page resources\n- `none()`: Will not automatically stop even if timeout, will trigger load complete event\n\nIn the first two modes, the page loading process will block the program and only perform subsequent operations when loading is complete.\n\nIn the `none()` mode, the program is only blocked during the connection phase, and the loading phase can be manually stopped by calling `stop_loading()` according to the situation.\n\nThis provides users with great flexibility, allowing them to stop page loading actively when key data packets or elements appear, greatly improving execution efficiency.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.set.load_strategy.eager()\npage.get('https://g1879.gitee.io/drissionpagedocs')\n```\n\n---\n\n### 📌 Setting Strategies\n\nLoad strategies can be set using an ini file, `ChromiumOptions` object, or the `set.load_mode.xxxx()` methods of the page object.\n\nThey can also be dynamically set during runtime.\n\n**Setting in Configuration Object**\n\n```python\nfrom DrissionPage import ChromiumOptions, ChromiumPage\n\nco = ChromiumOptions().set_load_mode('none')\npage = ChromiumPage(co)\n```\n\n**Setting During Runtime**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.set.load_mode.none()\n```\n\n---\n\n### 📌 `none` Mode Tips\n\n**Example 1, with a Listener**\n\nWhen used with a listener, it can actively stop loading when the desired data packet is obtained.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.set.load_mode.none()  # Set the load mode to none\n\npage.listen.start('api/getkeydata')  # Specify the target to listen and start listening\npage.get('http://www.hao123.com/')  # Access the website\npacket = page.listen.wait()  # Wait for the data packet\npage.stop_loading()  # Actively stop loading\nprint(packet.response.body)  # Print the body of the data packet\n```\n\n**Example 2, with Element Retrieval**\n\nWhen used with element retrieval, it can actively stop loading when a specific element is obtained.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.set.load_mode.none()  # Set the load mode to none\n\npage.get('http://www.hao123.com/')  # Access the website\nele = page.ele('中国日报')  # Retrieve the element with text containing \"中国日报\"\npage.stop_loading()  # Actively stop loading\nprint(ele.text)  # Print element text\n```\n\n**Example 2, with Page Features**\n\nIt can actively stop loading when the page reaches a certain state. For example, when performing multi-level login, it can wait for the title to change to the final target URL and then stop.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.set.load_mode.none()  # Set the load mode to none\n\npage.get('http://www.hao123.com/')  # Access the website\npage.wait.title_change('hao123')  # Wait for the title to change to the target text\npage.stop_loading()  # Actively stop loading\n```\n\n"
  },
  {
    "path": "docs_en/ChromiumPage/waiting.md",
    "content": "🚤 Automatic Waiting\n---\n\nIn unstable network environments, it is difficult to determine the execution time of JavaScript on a webpage. When automating processes, it is often necessary to wait for certain conditions to be met.\n\nUsing `sleep()` all the time is not elegant and can waste time. Not waiting enough can lead to errors.\n\nTherefore, it is important for a program to be able to wait smartly. `DrissionPage` provides built-in waiting methods that can improve program stability and efficiency.\n\nThese methods are accessible through the `wait` attribute of the page and element objects.\n\nAll waiting methods have a `timeout` parameter that can be set to a specific timeout duration. The parameter can also be set to determine whether the method should return `False` or raise an exception upon timeout.\n\n## ✅️️ Waiting Methods for Page Objects\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('http://g1879.gitee.io/drissionpagedocs/')\npage.wait.ele_displayed('tag:div')\n```\n\n### 📌 `wait.load_start()`\n\nThis method is used to wait for the page to enter the loading state.\n\nOften, we navigate to another webpage by clicking an element and immediately want to access elements on the new page. However, if the previous page contains elements with the same locators as the elements on the new page, we may inadvertently access elements that become invalid after the navigation. By using this method, the program will be blocked, and execution will be delayed until the page starts loading, avoiding the aforementioned issue.\n\nUsually, we only need to wait for the page to start loading, and the program will automatically wait for the loading to finish.\n\n| Parameter | Type                | Default | Description                                                                                         |\n|:---------:|:-------------------:|:-------:| --------------------------------------------------------------------------------------------------- |\n| `timeout` | `float`<br/>`None`<br/>`True` | `None`   | The timeout duration. If `None` or `True`, the page's `timeout` setting will be used.<br/>If it's a number, the program will wait for the specified duration. |\n| `raise_err` | `bool`          | `None`   | Determines whether an error should be raised upon timeout. If `None`, the behavior is determined by `Settings`.               |\n\n| Return Type | Description                                                                      |\n|:-----------:| -------------------------------------------------------------------------------- |\n| `bool`      | Whether entering the loading state was completed when the waiting ended. |\n\n**Example:**\n\n```python\nele.click()  # Click a certain element\npage.wait.load_start()  # Wait for the page to start loading\n# Perform actions on the new page\nprint(page.title)\n```\n\n---\n\n### 📌 `wait.doc_loaded()`\n\nThis method is used to wait for the page document to finish loading.\n\nIn general, developers don't need to use this method explicitly, as most actions in the program will automatically wait for the document to finish loading before execution.\n\n:::warning Note\n    - This feature is only used to wait for the main document of the page to load and cannot be used to wait for changes loaded by JavaScript.\n    - The `get()` method already contains this functionality, so it is unnecessary to append this wait afterwards.\n:::\n\n| Parameter | Type                | Default | Description                                                                                         |\n|:---------:|:-------------------:|:-------:| --------------------------------------------------------------------------------------------------- |\n| `timeout` | `float`<br/>`None`<br/>`True` | `None`   | The timeout duration. If `None` or `True`, the page's `timeout` setting will be used.<br/>If it's a number, the program will wait for the specified duration. |\n| `raise_err` | `bool`          | `None`   | Determines whether an error should be raised upon timeout. If `None`, the behavior is determined by `Settings`.               |\n\n| Return Type | Description                                                                                      |\n|:-----------:| ----------------------------------------------------------------------------------------------- |\n| `bool`      | Whether the loading was completed when the waiting ended. |\n\n---\n\n### 📌 `wait.ele_loaded()`\n\nThis method is used to wait for an element to be loaded in the DOM.\n\nSometimes, the appearance of an element is a prerequisite for the next step of an action. Using this method can prevent inadvertent actions caused by some elements loading slower than the program executes.\n\n| Parameter    | Type                                         | Default | Description                                                |\n|:------------:|:--------------------------------------------:|:-------:| ---------------------------------------------------------- |\n| `locator`    | `str`<br/>`Tuple[str, str]`                   | Required| The element to wait for, specified by locator.              |\n| `timeout`    | `float`                                      | `None`  | The timeout duration. If `None`, the page's `timeout` setting will be used. |\n| `raise_err`  | `bool`         | `None`    | Determines whether an error should be raised upon timeout. If `None`, the behavior is determined by `Settings`.          |\n\n| Return Type        | Description                                                |\n|:------------------:| ---------------------------------------------------------- |\n| `ChromiumElement`  | The element object if waiting is successful.               |\n| `False`            | If waiting fails.                                          |\n\n**Example:**\n\n```python\nele1.click()  # Click a certain element\npage.wait.ele_loaded('#div1')  # Wait for the element with id 'div1' to load\nele2.click()  # Proceed with the next step after the 'div1' element has finished loading\n```\n\n---\n\n### 📌 `wait.ele_displayed()`\n\nThis method is used to wait for an element to become displayed. If the specified element cannot be found in the DOM, it will automatically wait for the element to be loaded and displayed.\n\nAn element is considered hidden when it is present in the DOM but in a hidden state (even if it's within the viewport and not obscured). If a parent element is hidden, its child elements will also be considered hidden.\n\n| Parameter         | Type                                                      | Default | Description                                                |\n|:-----------------:|:---------------------------------------------------------:|:-------:| ---------------------------------------------------------|\n| `loc_or_ele`      | `str`<br/>`Tuple[str, str]`<br/>`ChromiumElement`          |Required | The element to wait for. It can be an element or a locator. |\n| `timeout`         | `float`                                                   | `None`  | The timeout duration. If `None`, the page's `timeout` setting will be used. |\n| `raise_err`  | `bool`         | `None`    | Determines whether an error should be raised upon timeout. If `None`, the behavior is determined by `Settings`.          |\n\n| Return Type | Description     |\n|:-----------:|-----------------|\n| `bool`      | Whether to wait successfully |\n\n**Example：**\n\n```python\n# Wait for the element with id \"div1\" to be displayed, using the page settings for timeout\npage.wait.ele_displayed('#div1')\n\n# Wait for the element with id \"div1\" to be displayed, set timeout to 3 seconds\npage.wait.ele_displayed('#div1', timeout=3)\n\n# Wait for the already obtained element to be displayed\nele = page.ele('#div1')\npage.wait.ele_displayed(ele)\n```\n\n---\n\n### 📌 `wait.ele_hidden()`\n\nThis method is used to wait for an element to become hidden. If the specified element cannot be found in the current DOM, it will automatically wait for the element to be loaded and then wait for it to be hidden.\n\nElement hiding means that the element is in the DOM but in a hidden state (even if it is in the viewport and not occluded). Child elements are also hidden when the parent element is hidden.\n\n| Parameter    | Type                                              | Default | Description                  |\n|:------------:|:-------------------------------------------------:|:-------:|------------------------------|\n| `loc_or_ele` | `str`<br/>`Tuple[str, str]`<br/>`ChromiumElement` | Required | The element to wait for, can be an element or a locator |\n| `timeout`    | `float`                                           | `None`  | Timeout, if `None`, use the page `timeout` setting |\n| `raise_err`  | `bool`                                            | `None`  | Whether to raise an error when waiting fails, if `None`, based on the `Settings` |\n\n| Return Type | Description     |\n|:-----------:|-----------------|\n| `bool`      | Whether to wait successfully |\n\n---\n\n### 📌 `wait.ele_deleted()`\n\nThis method is used to wait for an element to be deleted from the DOM.\n\n| Parameter    | Type                                              | Default | Description                  |\n|:------------:|:-------------------------------------------------:|:-------:|------------------------------|\n| `loc_or_ele` | `str`<br/>`Tuple[str, str]`<br/>`ChromiumElement` | Required | The element to wait for, can be an element or a locator |\n| `timeout`    | `float`                                           | `None`  | Timeout, if `None`, use the page `timeout` setting |\n| `raise_err`  | `bool`                                            | `None`  | Whether to raise an error when waiting fails, if `None`, based on the `Settings` |\n\n| Return Type | Description     |\n|:-----------:|-----------------|\n| `bool`      | Whether to wait successfully |\n\n---\n\n### 📌 `wait.download_begin()`\n\nThis method is used to wait for the start of a download, see the download section for details.\n\n| Parameter | Type    | Default | Description                                          |\n|:---------:|---------|---------|------------------------------------------------------|\n| `timeout` | `float` | `None`  | Timeout, if `None`, use the page `timeout` setting |\n| `raise_err`  | `bool`                                            | `None`  | Whether to raise an error when waiting fails, if `None`, based on the `Settings` |\n\n| Return Type | Description     |\n|:-----------:|-----------------|\n| `bool`      | Whether to wait successfully |\n\n**Example：**\n\n```python\npage('#download_btn').click()  # Click the button to trigger the download\npage.wait.download_begin()  # Wait for the download to start\n```\n\n---\n\n### 📌 `wait.upload_paths_inputted()`\n\nThis method is used to wait for automatically filling in the file upload path. See the file upload section for details.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n**Example：**\n\n```python\n# Set the file path to be uploaded\npage.set.upload_files('demo.txt')\n# Click the button to trigger the file selection dialog\nbtn_ele.click()\n# Wait for the path to be filled in\npage.wait.upload_paths_inputted()\n```\n\n---\n\n### 📌 `wait.new_tab()`\n\nThis method is used to wait for a new tab to appear.\n\n| Parameter | Type    | Default | Description                                          |\n|:---------:|---------|---------|------------------------------------------------------|\n| `timeout` | `float` | `None`  | Timeout, if `None`, use the page `timeout` setting |\n| `raise_err`  | `bool`                                            | `None`  | Whether to raise an error when waiting fails, if `None`, based on the `Settings` |\n\n| Return Type | Description                     |\n|:-----------:|---------------------------------|\n| `str`       | The id of the new tab being waited for |\n| `False`     | `False` if waiting fails       |\n\n---\n\n### 📌 `wait.title_change()`\n\nThis method is used to wait for the title to contain or not contain the specified text.\n\n| Parameter   | Type    | Default | Description                                                  |\n|:------------:|---------|---------|--------------------------------------------------------------|\n| `text`     | `str`   | Required| The text used for identification                              |\n| `exclude`  | `bool`  | `False` | Whether to exclude, if `True`, `True` is returned when the title does not contain the specified `text` |\n| `timeout`  | `bool`  | `float` | Timeout                                                      |\n| `raise_err` | `bool`  | `None`  | Whether to raise an error when waiting fails, if `None`, based on the `Settings` |\n\n| Return Type | Description     |\n|:-----------:|-----------------|\n| `bool`      | Whether to wait successfully |\n\n---\n\n### 📌 `wait.url_change()`\n\nThis method is used to wait for the url to contain or not contain the specified text.\n\nFor example, some websites will undergo multiple redirections during login, and the url will change multiple times. This function can be used to wait for the final page needed.\n\n| Parameter Name | Type    | Default Value | Description                                                                                |\n| -------------- | ------- | ------------- | ------------------------------------------------------------------------------------------ |\n| `text`         | `str`   | Required -   | Text for identification                                                                     |\n| `exclude`      | `bool`  | `False`       | When `True`, returns `True` if the URL does not contain the specified `text`               |\n| `timeout`      | `float` | Required -   | Timeout                                                                                     |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails. Use `Settings` if `None` is specified         |\n\n| Return Type | Description   |\n| ----------- | ------------- |\n| `bool`      | Whether to wait for success   |\n\n**Example:**\n\n```python\n# Access the website\npage.get('https://www.*****.cn/login/')  # Access the login page\npage.ele('#username').input('***')  # Execute login logic\npage.ele('#password').input('***\\n')\n\npage.wait.url_change('https://www.*****.cn/center/')  # Wait for the URL to change to the background URL\n```\n\n---\n\n### 📌 `wait()`\n\nThis method is used to wait for a certain number of seconds, and there is no difference with `sleep()`, but the author is too lazy to write another import statement.\n\n| Parameter Name | Type    | Default Value | Description                                      |\n| -------------- | ------- | ------------- | ------------------------------------------------ |\n| `second`       | `float` | Required -   | Wait for a certain number of seconds               |\n\n**Return:** `None`\n\n**Example:**\n\n```python\npage.wait(1)  # Wait for 1 second forcefully\n\nimport time\ntime.sleep(1)  # No difference with this line\n```\n\n---\n\n## ✅️️ Waiting Methods of Element Objects\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('http://g1879.gitee.io/drissionpagedocs/')\nele = page('tag:div')\nele.wait.covered()\n```\n\n### 📌 `wait.displayed()`\n\nThis method is used to wait for an element to change from a hidden state to a displayed state.\n\nAn element is hidden when it is in the DOM but in a hidden state (even if it is in the viewport and not covered). The child elements are also hidden when the parent elements are hidden.\n\n| Parameter Name | Type    | Default Value | Description                                      |\n| -------------- | ------- | ------------- | ------------------------------------------------ |\n| `timeout`      | `float` | `None`        | Timeout. Use the timeout of the element's page if `None` is specified                        |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails. Use `Settings` if `None` is specified         |\n\n| Return Type | Description   |\n| ----------- | ------------- |\n| `bool`      | Whether waiting is successful   |\n\n**Example:**\n\n```python\n# Wait for the element to be displayed. Use the timeout of the ele's page\nele.wait.displayed()\n```\n\n---\n\n### 📌 `wait.hidden()`\n\nThis method is used to wait for an element to change from a displayed state to a hidden state.\n\nAn element is hidden when it is in the DOM but in a hidden state (even if it is in the viewport and not covered). The child elements are also hidden when the parent elements are hidden.\n\n| Parameter Name | Type    | Default Value | Description                                      |\n| -------------- | ------- | ------------- | ------------------------------------------------ |\n| `timeout`      | `float` | `None`        | Timeout. Use the timeout of the element's page if `None` is specified                        |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails. Use `Settings` if `None` is specified         |\n\n| Return Type | Description   |\n| ----------- | ------------- |\n| `bool`      | Whether waiting is successful   |\n\n**Example:**\n\n```python\n# Wait for the element to be hidden for 3 seconds\nele.wait.hidden(timeout=3)\n```\n\n---\n\n### 📌 `wait.deleted()`\n\nThis method is used to wait for an element to be deleted from the DOM.\n\n| Parameter Name | Type    | Default Value | Description                                      |\n| -------------- | ------- | ------------- | ------------------------------------------------ |\n| `timeout`      | `float` | `None`        | Timeout. Use the timeout of the element's page if `None` is specified                        |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails. Use `Settings` if `None` is specified         |\n\n| Return Type | Description   |\n| ----------- | ------------- |\n| `bool`      | Whether waiting is successful   |\n\n**Example:**\n\n```python\n# Wait for the element to be displayed. Use the timeout of the ele's page\nele.wait.deleted()\n```\n\n---\n\n### 📌 `wait.covered()`\n\nThis method is used to wait for an element to be covered by other elements.\n\n| Parameter Name | Type    | Default Value | Description                                      |\n| -------------- | ------- | ------------- | ------------------------------------------------ |\n| `timeout`      | `float` | `None`        | Timeout. Use the timeout of the element's page if `None` is specified                        |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails. Use `Settings` if `None` is specified         |\n\n| Return Type | Description   |\n| ----------- | ------------- |\n| `bool`      | Whether waiting is successful   |\n\n---\n\n### 📌 `wait.not_covered()`\n\nThis method is used to wait for an element not to be covered by other elements.\n\nIt can be used to wait for the \"loading\" mask that covers the element being operated on to disappear.\n\n| Parameter Name | Type   | Default Value | Description                                          |\n|----------------|--------|---------------|------------------------------------------------------|\n| `timeout`      | `float` | `None`        | Waiting timeout duration, if `None` then use the timeout duration of the element on the page |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails, if `None` then based on the `Settings` configuration |\n\n| Return Type | Description           |\n|-------------|-----------------------|\n| `bool`      | Whether the wait succeeded |\n\n---\n\n### 📌 `wait.enabled()`\n\nThis method is used to wait for an element to become enabled.\n\nAn element in an disabled state is still in the DOM with a `disabled` attribute set to `False`.\n\n| Parameter Name | Type   | Default Value | Description                                          |\n|----------------|--------|---------------|------------------------------------------------------|\n| `timeout`      | `float` | `None`        | Waiting timeout duration, if `None` then use the timeout duration of the element on the page |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails, if `None` then based on the `Settings` configuration |\n\n| Return Type | Description           |\n|-------------|-----------------------|\n| `bool`      | Whether the wait succeeded |\n\n---\n\n### 📌 `wait.disabled()`\n\nThis method is used to wait for an element to become disabled.\n\nAn element in an disabled state is still in the DOM with a `disabled` attribute set to `True`.\n\n| Parameter Name | Type   | Default Value | Description                                          |\n|----------------|--------|---------------|------------------------------------------------------|\n| `timeout`      | `float` | `None`        | Waiting timeout duration, if `None` then use the timeout duration of the element on the page |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails, if `None` then based on the `Settings` configuration |\n\n| Return Type | Description           |\n|-------------|-----------------------|\n| `bool`      | Whether the wait succeeded |\n\n---\n\n### 📌 `wait.stop_moving()`\n\nThis method is used to wait for an element to stop moving. If the element does not have size and position information, a `NoRectError` exception will be thrown when the timeout is reached.\n\n| Parameter Name | Type   | Default Value | Description                                                  |\n|----------------|--------|---------------|--------------------------------------------------------------|\n| `gap`          | `float` | `0.1`         | Time interval to check for movement                            |\n| `timeout`      | `float` | `None`        | Waiting timeout duration, if `None` then use the timeout duration of the element on the page |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails, if `None` then based on the `Settings` configuration |\n\n| Return Type | Description           |\n|-------------|-----------------------|\n| `bool`      | Whether the wait succeeded |\n\n```python\n# Wait for element to stabilize\npage.ele('#button1').wait.stop_moving()\n# Click on the element\npage.ele('#button1').click()\n```\n\n---\n\n### 📌 `wait.disable_or_deleted()`\n\nThis method is used to wait for an element to become disabled or deleted.\n\n| Parameter Name | Type   | Default Value | Description                                          |\n|----------------|--------|---------------|------------------------------------------------------|\n| `timeout`      | `float` | `None`        | Waiting timeout duration, if `None` then use the timeout duration of the element on the page |\n| `raise_err`    | `bool`  | `None`        | Whether to raise an error when waiting fails, if `None` then based on the `Settings` configuration |\n\n| Return Type | Description           |\n|-------------|-----------------------|\n| `bool`      | Whether the wait succeeded |\n\n---\n\n### 📌 `wait()`\n\nThis method is used to wait for a certain number of seconds, same as `sleep()`.\n\n| Parameter Name | Type   | Default Value | Description     |\n|----------------|--------|---------------|-----------------|\n| `second`       | `float` | Required      | Number of seconds to wait |\n\n**Returns:** `None`\n\n"
  },
  {
    "path": "docs_en/MixPage/introduction.md",
    "content": "🛠 Old Version (MixPage)\n---\n\nThe versions of this repository prior to 3.0 were implemented by re-encapsulating selenium.\n\nThe page objects for this version are `MixPage` and `DriverPage`, corresponding to `WebPage` and `ChromiumPage` of DrissionPage. The usage is basically the same as the new version.\n\nAfter years of use, the old version has become quite stable. However, due to reliance on selenium, the development of functions has been greatly restricted. Moreover, with the iteration of versions, the new version has surpassed the old version comprehensively, and it is time for the old version to retire.\n\nTherefore, starting from version 3.0, the old version code has been separated from this repository and developed into an independent library.\n\nThis is to commemorate the achievements it has made.\n\nCurrently, the development of the old version has been frozen. Except for bug fixes, there will be no more functional modifications for the old version.\n\nInterested readers can take a look.\n\n---\n\nProject address: [MixPage](https://gitee.com/g1879/MixPage)\n\nDocumentation: [MixPage User Manual](http://g1879.gitee.io/mixpage)\n\n---\n\nThe structure of `MixPage` is as follows:\n\n![](../imgs/mixpage.jpg)\n\n"
  },
  {
    "path": "docs_en/Q&A.md",
    "content": "# ❓ Q&A'\nhide:\n  - navigation\n---\n\nThis page collects some frequently asked questions from users during the usage process.\n\nDevelopers are welcome to contribute by submitting issues, PRs, or writing blog articles and sending the links to the author of this repository.\n\n## ❓ How to use on headless Linux?\n\n**Answer:**\n\nFor CentOS, please refer to this article: [Linux deployment instructions](https://blog.csdn.net/sinat_39327967/article/details/132181129?spm=1001.2014.3001.5501)\n\nFor Ubuntu, please refer to this article: [The usage of DrissionPage on Ubuntu Linux](https://zhuanlan.zhihu.com/p/674687748)\n\n---\n\n## ❓ Why can't the browser exit headless mode?\n\n> Why does the browser still enter headless mode even if `headless()` is not set in the next run after setting headless before?\n\n**Answer:**\n\nThis is because the previously opened browser has not been closed and it is just not visible due to headless mode. The program continues to control it.\n\nTo close the browser, you can use the `page.quit()` statement at the end of the program.\n\nYou can also set `co.headless(False)`, and the program will automatically close the previous headless browser and start a new one.\n\nAlso note that the `page.close()` function closes the current tab, not the browser, unless the browser has only one tab.\n\n---\n\n## ❓ How to disable the popup prompts from the browser, such as whether to save passwords or restore the page?\n\n**Answer:**\n\nWhen the browser popup prompts appear, you can close them manually. Not closing them will not affect automatic operations. It is also possible to prevent them from being displayed in the code.\nAdd some browser configuration code to disable the corresponding prompts. You need to add code like this:\n\n```python\nco = ChromiumOptions()\n\n# Disable the prompt bubble for \"Save Password\"\nco.set_pref('credentials_enable_service', False)\n\n# Disable the prompt bubble for \"Do you want to restore this page? Chrome didn't shut down correctly.\"\nco.set_argument('--hide-crash-restore-bubble')\n\npage = ChromiumPage(co)\n```\n\n---\n\n## ❓ When using `.click()`, it reports an error \"The element has no position and size\". How to solve it?\n\n**Answer:**\n\nIt is normal for an element to have no position and size, as many elements do not have them.\n\nAt this time, you need to check if there are elements with the same name in the page, and whether the locator is accurate and retrieves another element.\n\nIf the element you want to click really has no position, you can force click by using JavaScript, the usage is `.click(by_js=True)`, which can be simplified as `.click('js')`.\n\n---\n\n## ❓ Other automation tools seem to be able to use advanced features of the browser (launch options, user preferences, experimental flags). How can I use them in DrissionPage?\n\n**Answer:**\n\n### Launch Options (arguments)\n- Usage reference: [https://g1879.gitee.io/drissionpagedocs/ChromiumPage/browser_opt#-set_argument](https://g1879.gitee.io/drissionpagedocs/ChromiumPage/browser_opt#-set_argument)\n- Parameter details: [https://peter.sh/experiments/chromium-command-line-switches/](https://peter.sh/experiments/chromium-command-line-switches/)\n\n### User Preferences (prefs)\n- Usage reference: [https://g1879.gitee.io/drissionpagedocs/ChromiumPage/browser_opt#-set_pref](https://g1879.gitee.io/drissionpagedocs/ChromiumPage/browser_opt#-set_pref)\n- Parameter details: [https://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/pref_names.cc](https://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/pref_names.cc)\n\n### Experimental Flags (flags)\n- Usage reference: [https://g1879.gitee.io/drissionpagedocs/ChromiumPage/browser_opt#-set_flag](https://g1879.gitee.io/drissionpagedocs/ChromiumPage/browser_opt#-set_flag)\n- Parameter details: [chrome://flags](chrome://flags)\n\n:::warning Note\n    External links are for reference only. Please use any advanced features with caution and only when you have full control, as using these features may result in loss of browser data or compromise security and privacy.\n\n"
  },
  {
    "path": "docs_en/README_en.md",
    "content": "# ✨️ Overview\n\nDrissionPage is a web automation tool based on Python.\n\nIt can control browsers, send and receive data packets, and combine the two.\n\nIt combines the ease of browser automation with the efficiency of requests.\n\nIt is powerful, with numerous user-friendly designs and convenient features.\n\nIts syntax is simple and elegant, with less code and beginner-friendly.\n\n---\n\n<a href='https://gitee.com/g1879/DrissionPage/stargazers'><img src='https://gitee.com/g1879/DrissionPage/badge/star.svg?theme=dark' alt='star'></img></a> <a href='https://gitee.com/g1879/DrissionPage/members'><img src='https://gitee.com/g1879/DrissionPage/badge/fork.svg?theme=dark' alt='fork'></img></a>\n\nProject links: [gitee](https://gitee.com/g1879/DrissionPage)    |    [github](https://github.com/g1879/DrissionPage) \n\nYour stars are the greatest support to me 💖\n\n---\n\nSupported systems: Windows, Linux, Mac\n\nPython version: 3.6 and above\n\nSupported browsers: Chromium-based browsers (such as Chrome and Edge), electron applications\n\n---\n\n**📖 Chinese documentation：**  [Click to view](http://g1879.gitee.io/drissionpagedocs)\n\n**QQ group for communication：**  897838127[full]、558778073\n\n**📖 Documentation：**  [Click to view](https://github.com/y0un9kane/DrissionPage/tree/master/docs_en)\n\n**Telegram group：**   [@DrissionPage](https://t.me/DrissionPage)\n\n---\n\n# 🔥 Upcoming version preview\n\nCheck the next development plan: [Upcoming version preview](http://g1879.gitee.io/drissionpagedocs/whatsnew/3_3/)\n\n---\n\n# 📕 Background\n\nWhen using requests for data collection and facing websites that require logging in, analyzing data packets and JS source code, constructing complex requests, and dealing with anti-crawling methods such as captchas, JS obfuscation, and signature parameters, the threshold is high and the development efficiency is not high. \nUsing a browser can bypass many of these obstacles, but the efficiency of browser operation is not high.\n\nTherefore, the original intention of this library is to merge them and achieve both \"write fast\" and \"run fast\". It can switch to the corresponding mode when needed, and provide a user-friendly usage method to improve development and running efficiency. \nIn addition to merging the two, this library encapsulates common functions on a webpage basis, providing very convenient operations and statements, allowing users to reduce consideration of details and focus on functionality implementation. By implementing powerful functions in a simple way, the code becomes more elegant.\n\nPrevious versions were implemented by re-encapsulating selenium. From version 3.0 onwards, the author started from scratch, redeveloped the underlying framework, broke free from the dependence on selenium, enhanced functionality, and improved runtime efficiency.\n\n---\n\n# 💡 Philosophy\n\nSimplicity! Ease of use! Convenience!\n\n---\n\n# ☀️ Features and Highlights\n\nAfter long-term practice and countless trials, the author has summarized the experience and written them all into this library.\n\n## 🎇 Powerful self-developed engine\n\nThis library adopts a self-developed engine, with built-in numerous practical functions, and has the following advantages compared to selenium by integrating and optimizing common functions:\n\n- No webdriver characteristics\n\n- No need to download different drivers for different versions of browsers\n\n- Faster runtime speed\n\n- Can search for elements across `<iframe>` without having to switch in and out\n\n- Treats `<iframe>` as regular elements, allowing direct element search within them for clearer logic\n\n- Can operate on multiple tabs in the browser simultaneously, even if the tabs are inactive, without the need to switch\n\n- Can directly read browser cache to save images without needing to click \"Save As\" using GUI\n\n- Can take screenshots of the entire webpage, including portions outside the viewport (supported by versions 90 and above)\n\n- Can handle non-`open` status shadow-root\n\n## 🎇 Highlighted features\n\nIn addition to the above advantages, this library also includes numerous user-friendly designs.\n\n- Minimalistic syntax rules. Integrates many common functions, leading to more elegant code\n\n- Easier element locating with more powerful and stable functionality\n\n- Ubiquitous waiting and automatic retry functions. Makes unstable networks easier to control, providing program stability and peace of mind in writing code\n\n- Provides powerful download tools. Allows for quick and reliable downloads when operating on the browser\n\n- Allows reuse of already open browsers. No need to start the browser from scratch every time, making debugging extremely convenient\n\n- Saves common configurations in ini files and automatically calls them, providing convenient settings and avoiding complex configuration settings\n\n- Uses built-in lxml as the parsing engine, significantly improving parsing speed\n\n- Wrapped in POM (Page Object Model) pattern, can be directly used for testing and easy to extend\n\n- Highly integrated convenient functionalities, demonstrating excellence in every detail.\n\n- There are many details that are not listed here, welcome to experience them in actual use:）\n\n---\n\n# 🛠 User Manual\n\n[Click here to jump to the user manual](http://g1879.gitee.io/drissionpage)\n\n---\n\n# 🔖 Version History\n\n[Click here to view the version history](http://g1879.gitee.io/drissionpagedocs/history/3.x/)\n\n---\n\n# 🖐🏻 Disclaimer\n\nPlease do not use DrissionPage in any work that may violate laws and moral constraints. Please use DrissionPage in a friendly manner and comply with the spider agreement. Do not use DrissionPage for any illegal purposes. By choosing to use DrissionPage, you agree to this agreement, and the author is not responsible for any legal risks and losses resulting from your violation of this agreement. You are responsible for all consequences.\n\n---\n\n\n# ☕ Buy Me a Coffee\n\nIf this project has been helpful to you, you may consider buying me a cup of coffee:）\n\n![](http://g1879.gitee.io/drissionpagedocs/imgs/code.jpg)\n\n\n\n\n\n\n### Table of contents\n* [Getting started guide](#section1)\n    + [Installation](https://github.com/y0un9kane/DrissionPage/blob/master/docs_en/get_start/installation.md)\n    + [Import](#section3)\n    + [before start](#section4)\n    + [examples](#section5)\n* [SessionPage](#SessionPage)\n    + [intro](#intro)\n    + [create page obj](#create page obj)\n    + [open web](#open web)\n    + [get page info](#get page info)\n    + [get element info](#get element info)\n    + [page settings](#gpage settings)\n    + [startup configuration](#startup configuration)\n* [ChromiumPage](#ChromiumPage)\n    + [intro](#intro)\n    + [create page obj](#create page obj)\n    + [open web](#open web)\n    + [page operation](#page operation)\n    + [get element info](#get element info)\n    + [element operation](#element operation)\n    + [Auto waiting](#Auto waiting)\n    + [File upload](#File upload)\n    + [tab operation](#tab operation)\n    + [iframe operation](#iframe operation)\n    + [listen in network data](#listen in network data)\n    + [Action chains](#Action chains)\n    + [Screenshot and recording](#Screenshot and recording)\n    + [Browser startup settings](#Browser startup settings)\n* [WebPage](#WebPage)\n    + [intro](#intro)\n    + [create page obj](#create page obj)\n    + [Mode switching](#Mode switching)\n    + [Exclusive features](#Exclusive features)\n* [Find element](#Find element)\n    + [intro](#intro)\n    + [Basic Usage](#Basic Usage)\n    + [More Usages](#More Usages)\n    + [Simplified](#Simplified)\n    + [When Element Not Found](#When Element Not Found)\n    + [Syntax Quick Reference Table](#Syntax Quick Reference Table)\n* [Download file](#Download file)\n    + [intro](#intro)\n    + [DownloadKit](#DownloadKit)\n    + [downloads](#downloads)\n* [Advanced usage](#Advanced usage)\n    + [Usage of Configuration Files](#Usage of Configuration Files)\n    + [Global Settings](#Global Settings)\n    + [CMD Usage](#CMD Usage)\n    + [Usage Exceptions](#Usage Exceptions)\n    + [Accelerated Data Reading](#Accelerated Data Reading)\n    + [Packaging the Program](#Packaging the Program)\n    + [Small Tools](#Small Tools)\n"
  },
  {
    "path": "docs_en/SessionPage/create_page_object.md",
    "content": "🚄 Creating Page Objects\n---\n\nBoth the `SessionPage` and `WebPage` objects can send and receive packets. In this section, we will only focus on creating the `SessionPage` object. The `WebPage` object will be introduced in the chapter about web pages.\n\n## ✅️️ Initialization Parameters for `SessionPage`\n\nThe `SessionPage` object is the simplest among the three page objects.\n\n| Initialization Parameter | Type                                     | Default Value | Description                                                                 |\n|-------------------------|------------------------------------------|---------------|-----------------------------------------------------------------------------|\n| `session_or_options`     | `Session`<br/>`SessionOptions`              | `None`        | If a `Session` object is provided, it will be used to send and receive packets. If a `SessionOptions` object is provided, it will be used to create a `Session` object with the specified configuration. |\n| `timeout`                | `float`                                    | `None`        | The connection timeout. If `None`, it will be read from the configuration file. |\n\n---\n\n## ✅️️ Create Directly\n\nThis method is the most concise, as the program will automatically generate the page object by reading the configuration from the configuration file.\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\n```\n\n`SessionPage` does not require controlling the browser or any additional configuration.\n\n:::warning Warning\n    Programs created using this method cannot be directly packaged, as they rely on an ini file. Please refer to the \"Packaging Programs\" section for a solution.\n:::\n\n---\n\n## ✅️️ Create Using Configuration Information\n\nIf you need to configure the page object before using it, you can use `SessionOptions`. It is specifically designed for setting the initial state of a `Session` object and comes with built-in common configurations. For detailed usage instructions, please refer to the \"Startup Configuration\" section.\n\n### 📌 Usage\n\nWhen creating a `SessionPage`, pass the already created and configured `SessionOptions` object as a parameter.\n\n| Initialization Parameter | Type    | Default Value | Description                          |\n|-------------------------|---------|---------------|--------------------------------------|\n| `read_file`             | `bool`  | `True`        | Whether to read the configuration information from an ini file. |\n| `ini_path`              | `str`   | `None`        | The file path. If `None`, it will read from the default ini file. |\n\n:::warning Warning\n    Modifying this configuration after creating the `Session` object will have no effect.\n:::\n\n```python\n# Import SessionOptions\nfrom DrissionPage import SessionPage, SessionOptions\n\n# Create a configuration object and set the proxy information\nso = SessionOptions().set_proxies(http='127.0.0.1:1080')\n# Create the page object using this configuration\npage = SessionPage(session_or_options=so)\n```\n\n:::tip Tips\n    You can save the configuration to a configuration file for automatic reading in the future. Please refer to the \"Startup Configuration\" chapter for more information.\n:::\n\n---\n\n### 📌 Create from a Specified ini File\n\nThe above methods use the configuration information saved in the default ini file to create the object. However, you can save an ini file somewhere else and specify its location when creating the object.\n\n```python\nfrom DrissionPage import SessionPage, SessionOptions\n\n# Specify the ini file path when creating the configuration object\nso = SessionOptions(ini_path=r'./config1.ini')\n# Create the page object using this configuration object\npage = SessionPage(session_or_options=so)\n```\n\n---\n\n### 📌 Do Not Use an ini File\n\nBy default, an ini file is used. However, for convenience, you can specify the configuration in the code instead of an ini file.\n\n```python\nfrom DrissionPage import SessionPage, SessionOptions\n\nso = SessionOptions(read_file=False)  # Set `read_file` to False\nso.set_retry(5)\npage = SessionPage(so)\n```\n\n---\n\n## ✅️️ Passing Control\n\nWhen multiple page objects need to work together to operate on a single page, you can pass a `Session` object from one page object to another to allow them to share the same `Session` object.\n\n```python\n# Create a page object\npage1 = SessionPage()\n# Get the built-in Session object from the page object\nsession = page1.session\n# Pass the session object when initializing the second page object\npage2 = SessionPage(session_or_options=session)\n```\n\n"
  },
  {
    "path": "docs_en/SessionPage/get_element_info.md",
    "content": "🚄 Get Element Information\n---\n\nThe `SessionPage` object and `WebPage` object retrieve elements in `SessionElement` mode. This section introduces its attributes.\n\nAssume `ele` is the object of the following `div` element, this section uses this element for examples:\n\n```html\n<div id=\"div1\" class=\"divs\">Hello World!\n    <p>行元素</p>\n    <!--这是注释-->\n</div>\n```\n\n## ✅️️ `html`\n\nThis attribute returns the outerHTML text of the element.\n\n**Return Type:** `str`\n\n```python\nprint(ele.html)\n```\n\n**Output:**\n\n```shell\n<div id=\"div1\" class=\"divs\">Hello World!\n    <p>行元素</p>\n    <!--这是注释-->\n</div>\n```\n\n---\n\n## ✅️️ `inner_html`\n\nThis attribute returns the innerHTML text of the element.\n\n**Return Type:** `str`\n\n```python\nprint(ele.inner_html)\n```\n\n**Output:**\n\n```shell\nHello World!\n    <p>行元素</p>\n    <!--这是注释-->\n```\n\n---\n\n## ✅️️ `tag`\n\nThis attribute returns the tag name of the element.\n\n**Return Type:** `str`\n\n```python\nprint(ele.tag)\n```\n\n**Output:**\n\n```shell\ndiv\n```\n\n---\n\n## ✅️️ `text`\n\nThis attribute returns a string that is the combination of all the texts inside the element.  \nThe string is formatted, i.e., encoded and removed extra line breaks, for a better readability.\n\n**Return Type:** `str`\n\n```python\nprint(ele.text)\n```\n\n**Output:**\n\n```shell\nHello World!\n行元素\n```\n\n---\n\n## ✅️️ `raw_text`\n\nThis attribute returns the raw text inside the element.\n\n**Return Type:** `str`\n\n```python\nprint(ele.raw_text)\n```\n\nOutput (note that the space and line breaks between elements are preserved):\n\n```shell\nHello World!\n    行元素\n    \n```\n\n---\n\n## ✅️️ `texts()`\n\nThis method returns the texts of all the **direct** child nodes inside the element, including element and text nodes. It has a parameter `text_node_only`, which controls whether only text nodes that are not wrapped by any element should be returned. This method is useful for cases where text nodes and element nodes are mixed.\n\n| Parameter Name      | Type     | Default Value | Description                                 |\n|:-------------------:|:--------:|:-------------:| ----------------------------------------- |\n| `text_node_only`    | `bool`   | `False`       |  Whether to only return text nodes              |\n\n| Return Type    | Description                          |\n|:--------------:| ------------------------------------ |\n| `List[str]`    | List of texts                         |\n\n**Example:**\n\n```python\nprint(e.texts())  \nprint(e.texts(text_node_only=True))  \n```\n\n**Output:**\n\n```shell\n['Hello World!', '行元素']\n['Hello World!']\n```\n\n---\n\n## ✅️️ `comments`\n\nThis attribute returns a list of comments inside the element.\n\n**Return Type:** `List[str]`\n\n```python\nprint(ele.comments)\n```\n\n**Output:**\n\n```shell\n[<!--这是注释-->]\n```\n\n---\n\n## ✅️️ `attrs`\n\nThis attribute returns a dictionary of all attributes and their values of the element.\n\n**Return Type:** `dict`\n\n```python\nprint(ele.attrs)\n```\n\n**Output:**\n\n```shell\n{'id': 'div1', 'class': 'divs'}\n```\n\n---\n\n## ✅️️ `attr()`\n\nThis method returns the value of a specific `attribute` of the element. It takes a string parameter `attr` and returns the value of that attribute as a string. If the attribute does not exist, `None` is returned.  \nThe returned `src` and `href` attributes are the complete paths. The `text` attribute is the formatted text.\n\n**Return Type:** `str` or `None`\n\n| Parameter Name   | Type     | Default Value | Description                           |\n|:-------------:|:------:|:-------------:| --------------------------------- |\n| `attr`        | `str`  | Required      |  Attribute name                      |\n\n| Return Type   | Description                          |\n|:-------------:| ------------------------------------ |\n| `str`         | Attribute value text                  |\n| `None`        | None is returned if the attribute does not exist |\n\n**Example:**\n\n```python\nprint(ele.attr('id'))\n```\n\n**Output:**\n\n```shell\ndiv1\n```\n\n---\n\n## ✅️️ `link`\n\nThis method returns the `href` attribute or `src` attribute of the element. If neither of these two attributes exists, `None` is returned.\n\n**Return Type:** `str`\n\n```html\n<a href='http://www.baidu.com'>百度</a>\n```\n\nAssume `a_ele` is the object of the above element:\n\n```python\nprint(a_ele.link)\n```\n\n**Output:**\n\n```shell\nhttp://www.baidu.com\n```\n\n---\n\n## ✅️️ `page`\n\nThis attribute returns the page object where the element is located. For `SessionElement` generated directly from html text, its `page` attribute is `None`.\n\n**Return Type:** `SessionPage` or `WebPage`\n\n```python\npage = ele.page\n```\n\n---\n\n## ✅️️ `xpath`\n\nThis attribute returns the absolute xpath of the element in the page.\n\n**Return Type:** `str`\n\n```python\nprint(ele.xpath)\n```\n\n**Output:**\n\n```shell\n/html/body/div\n```\n\n---\n\n## ✅️️ `css_path`\n\nThis attribute returns the absolute css selector path of the element in the page.\n\n**Return Type:** `str`\n\n```python\nprint(ele.css_path)\n```\n\n**Output:**\n\n```shell\n:nth-child(1)>:nth-child(1)>:nth-child(1)\n```\n\n---\n\n## ✅️️ Example\n\nThe following example can be directly run to view the results:\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\npage.get('https://gitee.com/explore')\n\n## 翻译 private_upload\\default_user\\2024-01-24-16-48-58\\get_element_info.md.part-1.md\n\n# Get all `a` elements under the recommended directory\nli_eles = page('tag:ul@@text():Recommended projects').eles('t:a')\n\n# Iterate through the list\nfor i in li_eles:  \n    # Get and print the tag name, text, and href attribute\n    print(i.tag, i.text, i.attr('href'))\n```\n\n**Output:**\n\n```shell\na Recommended projects https://gitee.com/explore/all\na Cutting-edge technologies https://gitee.com/explore/new-tech\na Smart hardware https://gitee.com/explore/hardware\na IoT/Internet of Things/Edge computing https://gitee.com/explore/iot\na Vehicle applications https://gitee.com/explore/vehicle\n...the rest is omitted\n\n\n```"
  },
  {
    "path": "docs_en/SessionPage/get_elements.md",
    "content": "🚄 Search for Elements\n---\n\nPlease refer to the \"Search for Elements\" section.\n\n"
  },
  {
    "path": "docs_en/SessionPage/get_page_info.md",
    "content": "🚄 Get Page Info\n---\n\nAfter successfully accessing the webpage, you can use the attributes and methods of `SessionPage` to get page information.\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\npage.get('http://www.baidu.com')\n# Get page title\nprint(page.title)\n# Get page html\nprint(page.html)\n```\n\n**Output:**\n\n```shell\n百度一下，你就知道\n<!DOCTYPE html>\n<!--STATUS OK--><html> <head><meta http-equi...\n```\n\n---\n\n## ✅️️ Page Information\n\n### 📌 `url`\n\nThis property returns the current URL being accessed.\n\n**Type:** `str`\n\n---\n\n### 📌 `url_available`\n\nThis property returns a boolean value indicating whether the current link is available.\n\n**Type:** `bool`\n\n---\n\n### 📌 `title`\n\nThis property returns the text of the current page's `title`.\n\n**Type:** `str`\n\n---\n\n### 📌 `raw_data`\n\nThis property returns the accessed element data, which is the `content` attribute of the `Response` object.\n\n**Type:** `bytes`\n\n---\n\n### 📌 `html`\n\nThis property returns the HTML text of the current page.\n\n**Type:** `str`\n\n---\n\n### 📌 `json`\n\nThis property parses the returned content into JSON format.  \nFor example, when requesting an API, if the returned content is in JSON format, using the `html` property to retrieve it will return a string. Use this property to parse it into a `dict`.\n\n**Type:** `dict`\n\n---\n\n### 📌 `user_agent`\n\nThis property returns the user_agent information of the current page.  \n\n**Type:** `str`\n\n---\n\n## ✅️️ Runtime Parameter Information\n\n### 📌 `timeout`\n\nThis property returns the network request timeout time. The default is 10 and can be set by assigning a value.\n\n**Type:** `int`, `float`\n\n```python\n# Specify the timeout when creating the page object\npage = SessionPage(timeout=5)\n\n# Modify the timeout\npage.timeout = 20\n```\n\n---\n\n### 📌 `retry_times`\n\nThis property is the number of retries when a network connection fails. The default is 3 and can be set by assigning a value.\n\n**Type:** `int`\n\n```python\n# Modify the number of retries\npage.retry_times = 5\n```\n\n---\n\n### 📌 `retry_interval`\n\nThis property is the waiting interval in seconds between retries when a network connection fails. The default is 2 and can be set by assigning a value.\n\n**Type:** `int`, `float`\n\n```python\n# Modify the waiting interval for retries\npage.retry_interval = 1.5\n```\n\n---\n\n### 📌 `encoding`\n\nThis property returns the encoding format set by the user.\n\n---\n\n## ✅️️ Cookies Information\n\n### 📌 `cookies`\n\nThis property returns the cookies used by the current page as a dictionary.\n\nNote that if different subdomains use the same `name` attribute, the cookies returned by this property may be missing.\n\n**Type:** `dict`\n\n---\n\n### 📌 `get_cookies()`\n\nThis method retrieves the cookies and returns them in the form of a list of cookies.\n\n**Type:** `dict`, `list`\n\n| Parameter Name | Type   | Default Value | Description                                                                               |\n|:-------------:|:------:|:-------------:|-------------------------------------------------------------------------------------------|\n| `as_dict`     | `bool` | `False`       | Whether to return the result in dictionary format. When `True`, it returns a `dict` consisting of `{name: value}` key-value pairs, and the `all_info` parameter is invalid. When `False`, it returns a list of cookies. |\n| `all_domains` | `bool` | `False`       | Whether to return cookies of all domains. If `False`, it only returns the cookies of the current domain.                                                       |\n| `all_info`    | `bool` | `False`       | Whether the returned cookies include all information. When `False`, it only includes the `name`, `value`, and `domain` information.                      |\n\n| Return Type | Description                                    |\n|:------:| -------------------------------------------- |\n| `dict` | When `as_dict` is `True`, returns cookies in dictionary format |\n| `list` | When `as_dict` is `False`, returns a list of cookies            |\n\n**Example:**\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\npage.get('http://www.baidu.com')\npage.get('http://gitee.com')\n\nfor i in page.get_cookies(as_dict=False, all_domains=True):\n    print(i)\n```\n\n**Output:**\n\n```\n{'domain': '.baidu.com', 'domain_specified': True, ......}\n......\n{'domain': 'gitee.com', 'domain_specified': False, ......}\n......\n```\n\n---\n\n## ✅️️ Embedded Objects\n\n### 📌 `session`\n\nThis property returns the `Session` object used by the current page object.\n\n**Type:** `Session`\n\n---\n\n### 📌 `response`\n\nThis property is the `Response` object generated after requesting the webpage. For features not implemented in this library, you can directly access this property to use the native features of the requests library.\n\n**Type:** `Response`\n\n```python\n# Print connection status\nr = page.response\nprint(r.status_code)\n```\n\n"
  },
  {
    "path": "docs_en/SessionPage/introduction.md",
    "content": "🚄 Overview\n---\n\nThe `SessionPage` object and the `WebPage` object's s pattern allow you to access web pages in the form of sending and receiving data packets.\n\nThis chapter introduces `SessionPage`.\n\nAs the name suggests, `SessionPage` is a page that uses the `Session` (requests library) object. It encapsulates the functions of network connection and HTML parsing using the Page Object Model (POM) pattern, making it convenient to send and receive data packets just like operating a page.\n\nMoreover, the library has also introduced its original methods for element lookup, making data collection much more convenient compared to combinations such as requests + beautifulsoup.\n\n`SessionPage` is the simplest among the various page objects in this library, so let's start with it.\n\nLet's take a simple example to understand how `SessionPage` works.\n\n---\n\nGet all the recommended projects on gitee's first page.\n\n```python\n# Import\nfrom DrissionPage import SessionPage\n# Create a page object\npage = SessionPage()\n# Access the webpage\npage.get('https://gitee.com/explore/all')\n# Find elements on the page\nitems = page.eles('t:h3')\n# Iterate through the elements\nfor item in items[:-1]:\n    # Get the <a> element under the current <h3> element\n    lnk = item('tag:a')\n    # Print the text and href attribute of the <a> element\n    print(lnk.text, lnk.link)\n```\n\n**Output:**\n\n```shell\n七年觐汐/wx-calendar https://gitee.com/qq_connect-EC6BCC0B556624342/wx-calendar\nThingsPanel/thingspanel-go https://gitee.com/ThingsPanel/thingspanel-go\nAPITable/APITable https://gitee.com/apitable/APITable\nIndexea/ideaseg https://gitee.com/indexea/ideaseg\nCcSimple/vue-plugin-hiprint https://gitee.com/CcSimple/vue-plugin-hiprint\nwilliam_lzw/ExDUIR.NET https://gitee.com/william_lzw/ExDUIR.NET\nanolis/ancert https://gitee.com/anolis/ancert\ncozodb/cozo https://gitee.com/cozodb/cozo\n... (omitted)\n```\n\n\n"
  },
  {
    "path": "docs_en/SessionPage/session_options.md",
    "content": "🚄 Session Options\n---\n\nThis section introduces the startup configuration of the `SessionPage`.\n\nWe manage the initial configuration of the `SessionPage` object using the `SessionOptions` object.\n\n:::warning Note\n    `SessionOptions` is only used to manage the startup configuration and cannot be modified after the program starts.\n:::\n\n## ✅️️ Creating an Object\n\n### 📌 Import\n\n```python\nfrom DrissionPage import SessionOptions\n```\n\n---\n\n### 📌 `SessionOptions`\n\nThe `SessionOptions` object is used to manage the initialization configuration of the `Session` object. Configuration can be read from a configuration file for initialization.\n\n| Initialization Parameter | Type              | Default Value | Description                                    |\n|:------------------------:|:-----------------:|:-------------:| ---------------------------------------------- |\n| `read_file`              | `bool`            | `True`        | Whether to read the configuration from an ini file. If `False`, the default configuration will be used. |\n| `ini_path`               | `Path`<br/>`str`  | `None`        | The path to the ini file. If `None`, the built-in ini file will be read.    |\n\nCreate a configuration object:\n\n```python\nfrom DrissionPage import SessionOptions\n\nso = SessionOptions()\n```\n\nBy default, the `SessionOptions` object will read the configuration from an ini file. If the `read_file` parameter is specified as `False`, it will be created with the default configuration.\n\n---\n\n## ✅️️ Usage\n\nAfter creating the configuration object, you can adjust the configuration content and then pass the configuration object as a parameter when creating the page object.\n\n```python\nfrom DrissionPage import SessionPage, SessionOptions\n\n# Create a configuration object (read configuration from ini file by default)\nso = SessionOptions()\n# Set the proxy\nso.set_proxies('http://localhost:1080')\n# Set cookies\ncookies = ['key1=val1; domain=xxxx', 'key2=val2; domain=xxxx']\nso.set_cookies(cookies)\n\n# Create the page object with this configuration\npage = SessionPage(session_or_options=so)\n```\n\n---\n\n## ✅️️ Methods for Setting Configuration\n\n### 📌 `set_headers()`\n\nThis method is used to set the entire `headers` parameter, and the value passed in will override the original `headers`.\n\n| Parameter Name  | Type     | Default Value | Description                  |\n|:---------------:|:--------:|:-------------:| ---------------------------- |\n| `headers`       | `dict`   | Required      | Complete headers dictionary  |\n\n| Return Type              | Description                                |\n|--------------------------|--------------------------------------------|\n| `SessionOptions`         | The configuration object itself             |\n\n**Example:**\n\n```python\nso.set_headers = {'user-agent': 'Mozilla/5.0 (Macint...', 'connection': 'keep-alive' ...}\n```\n\n---\n\n### 📌 `set_a_header()`\n\nThis method is used to set an item in the `headers`.\n\n| Parameter Name  | Type     | Default Value | Description |\n|:---------------:|:--------:|:-------------:| ----------- |\n| `attr`          | `str`    | Required      | Name        |\n| `value`         | `str`    | Required      | Value       |\n\n| Return Type              | Description                                |\n|--------------------------|--------------------------------------------|\n| `SessionOptions`         | The configuration object itself             |\n\n**Example:**\n\n```python\nso.set_a_header('accept', 'text/html')\nso.set_a_header('Accept-Charset', 'GB2312')\n```\n\n**Output:**\n\n```\n{'accept': 'text/html', 'accept-charset': 'GB2312'}\n```\n\n---\n\n### 📌 `remove_a_header()`\n\nThis method is used to remove a setting from the `headers`.\n\n| Parameter Name  | Type     | Default Value | Description |\n|:---------------:|:--------:|:-------------:| ----------- |\n| `attr`          | `str`    | Required      | Setting to remove |\n\n| Return Type              | Description                                |\n|--------------------------|--------------------------------------------|\n| `SessionOptions`         | The configuration object itself             |\n\n**Example:**\n\n```python\nso.remove_a_header('accept')\n```\n\n---\n\n### 📌 `set_cookies()`\n\nThis method is used to set cookie information. Each setting will overwrite all previously set cookie information.\n\n| Parameter Name   | Type                                                          | Default Value | Description |\n|:----------------:|:-------------------------------------------------------------:|:-------------:| ----------- |\n| `cookies`        | `RequestsCookieJar`<br/>`list`<br/>`tuple`<br/>`str`<br/>`dict` | Required      | Cookies     |\n\n| Return Type              | Description                                |\n|--------------------------|--------------------------------------------|\n| `SessionOptions`         | The configuration object itself             |\n\n**Example:**\n\n```python\ncookies = ['key1=val1; domain=xxxx', 'key2=val2; domain=xxxx']\nso.set_cookies(cookies)\n```\n\n---\n\n### 📌 `set_timeout()`\n\nThis method is used to set the connection timeout attribute.\n\n| Parameter Name  | Type     | Default Value | Description     |\n|:---------------:|:--------:|:-------------:| ----------------|\n| `second`        | `float`  | Required      | Connection waiting time in seconds |\n\n| Return Type              | Description                                |\n|--------------------------|--------------------------------------------|\n| `SessionOptions`         | The configuration object itself             |\n\n---\n\n### 📌 `set_retry()`\n\nThis method is used to set the number of retries and the interval when the connection times out.\n\n|    Parameter Name    |   Type    |  Default Value | Description                   |\n|:--------------------:|:---------:|:-------------:| ------------------------------|\n| `times`              |  `int`    | `None`        | Number of retries when the connection fails    |\n| `interval`           | `float`   | `None`        | The interval between failed connection attempts (in seconds)  |\n\n| Return Type              | Description                                |\n|--------------------------|--------------------------------------------|\n| `ChromiumOptions`         | The configuration object itself             |\n\n---\n\n### 📌 `retry_times`\n\nThis property returns the number of retries when the connection fails.\n\n**Type:** `int`\n\n---\n\n### 📌 `retry_interval`\n\nThis property returns the interval between failed connection attempts (in seconds).\n\n**Type:** `float`\n\n---\n\n### 📌 `set_proxies()`\n\nThis method is used to set proxy information.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `http` | `str` | required | http proxy address |\n| `https` | `str` | `None` | https proxy address, use the value of `http` parameter when `None` |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n**Example:**\n\n```python\nso.set_proxies('http://127.0.0.1:1080')\n```\n\n---\n\n### 📌 `set_download_path()`\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `path` | `str`<br/>`Path` | required  | Default download save path |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n### 📌 `set_auth()`\n\nThis method is used to set authentication tuple information.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `auth` | `tuple`<br/>`HTTPBasicAuth` | required  | Authentication tuple or object |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n### 📌 `set_hooks()`\n\nThis method is used to set callback methods.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `hooks` | `dict` | required  | Callback method |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n### 📌 `set_params()`\n\nThis method is used to set query parameters.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `params` | `dict` | required  | Query parameter dictionary |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n### 📌 `set_cert()`\n\nThis method is used to set the path of the SSL client certificate file (.pem format) or ('cert', 'key') tuple.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `cert` | `str`<br/>`tuple` | required  | Certificate path or tuple |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n### 📌 `set_verify()`\n\nThis method is used to set whether to verify SSL certificate.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `on_off` | `bool` | required  | `bool` indicates on or off |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n### 📌 `add_adapter()`\n\nThis method is used to add an adapter.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `url` | `str` | required  | Adapter corresponding url |\n| `adapter` | `HTTPAdapter` | required  | Adapter object |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n### 📌 `set_stream()`\n\nThis method is used to set whether to use streaming response content.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `on_off` | `bool` | required  | `bool` indicates on or off |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n### 📌 `set_trust_env()`\n\nThis method is used to set whether to trust the environment.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `on_off` | `bool` | required  | `bool` indicates on or off |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n### 📌 `set_max_redirects()`\n\nThis method is used to set the maximum number of redirects.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `times` | `int` | required  | Maximum number of redirects |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `SessionOptions` | The configuration object itself |\n\n---\n\n## ✅️️ Save settings to file\n\nYou can save different configurations to their own ini files to adapt to different scenarios.\n\n:::warning Note\n    `hooks` and `adapters` configurations will not be saved in the file.\n:::\n\n### 📌 `save()`\n\nThis method is used to save configuration items to an ini file.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:| ----------- |\n| `path` | `str`<br/>`Path` | `None` | The path of the ini file, pass `None` to save to the currently read configuration file |\n\n| Return Type | Description |\n| ----------- | ----------- |\n| `str` | Absolute path of the saved ini file |\n\n**Example:**\n\n```python\n# Save the currently read ini file\nso.save()\n\n# Save current configuration to a specified path\nso.save(path=r'D:\\tmp\\settings.ini')\n```\n\n---\n\n### 📌 `save_to_default()`\n\nThis method is used to save configuration items to a fixed default ini file. The default ini file refers to the one built-in with DrissionPage.\n\n**Parameter:** None\n\n| Return Type | Description                     |\n| ----------- | ------------------------------- |\n| `str`       | Absolute path of the saved ini file |\n\n**Example:**\n\n```python\nso.save_to_default()\n```\n\n---\n\n## ✅️️ `SessionOptions` Properties\n\n### 📌 `headers`\n\nThis property returns the headers configuration.\n\n**Type:** `dict`\n\n---\n\n### 📌 `cookies`\n\nThis property returns the cookies configuration in a `list` format.\n\n**Type:** `list`\n\n---\n\n### 📌 `proxies`\n\nThis property returns the proxy configuration.\n\n**Type:** `dict`\n**Format:** `{'http': 'http://xx.xx.xx.xx:xxxx', 'https': 'http://xx.xx.xx.xx:xxxx'}`\n\n---\n\n### 📌 `auth`\n\nThis property returns the authentication configuration.\n\n**Type:** `tuple`, `HTTPBasicAuth`\n\n---\n\n### 📌 `hooks`\n\nThis property returns the callback methods configuration.\n\n**Type:** `dict`\n\n---\n\n### 📌 `params`\n\nThis property returns the query parameters configuration.\n\n**Type:** `dict`\n\n---\n\n### 📌 `verify`\n\nThis property returns the SSL certificate verification configuration.\n\n**Type:** `bool`\n\n---\n\n### 📌 `cert`\n\nThis property returns the SSL certificate configuration.\n\n**Type:** `str`, `tuple`\n\n---\n\n### 📌 `adapters`\n\nThis property returns the adapter configuration.\n\n**Type:** `List[HTTPAdapter]`\n\n---\n\n### 📌 `stream`\n\nThis property returns the streaming response configuration.\n\n**Type:** `bool`\n\n---\n\n### 📌 `trust_env`\n\nThis property returns the trust environment configuration.\n\n**Type:** `bool`\n\n---\n\n### 📌 `max_redirects`\n\nThis property returns the `max_redirects` configuration.\n\n**Type:** `int`\n\n---\n\n### 📌 `timeout`\n\nThis property returns the connection timeout configuration.\n\n**Type:** `int`, `float`\n\n---\n\n### 📌 `download_path`\n\nThis property returns the default download path configuration.\n\n**Type:** `str`\n\n"
  },
  {
    "path": "docs_en/SessionPage/set_session.md",
    "content": "🚄 Page Settings\n---\n\nThis section introduces the settings of `SessionPage`.\n\nThese settings are global parameters, and they will be used for each request after being set.\n\n**Example:**\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\npage.set.cookies([{'name': 'a', 'value': '1'}, {'name': 'b', 'value': '2'}])\n```\n\n## ✅️️ `set.retry_times()`\n\nThis method is used to set the number of retries when the connection fails.\n\n| Parameter Name | Type | Default Value | Explanation |\n| -------------- | ---- | ------------- | ----------- |\n| `times` | `int` | Required | Number of times |\n\n**Returns:** `None`\n\n## ✅️️ `set.retry_interval()`\n\nThis method is used to set the interval between retries when the connection fails.\n\n| Parameter Name | Type   | Default Value | Explanation |\n| -------------- | ------ | ------------- | ----------- |\n| `interval`     | `float` | Required      | Number of seconds |\n\n**Returns:** `None`\n\n## ✅️️ `set.timeout()`\n\nThis method is used to set the connection timeout.\n\n| Parameter Name | Type   | Default Value | Explanation |\n| -------------- | ------ | ------------- | ----------- |\n| `second`       | `float` | Required      | Number of seconds |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage.set.timeout(20)\n```\n\n---\n\n## ✅️️ `set.encoding()`\n\nThis method is used to set the webpage encoding.\n\nBy default, the program will automatically retrieve the encoding from headers and the webpage. However, some webpages may have inaccurate encoding. In this case, you can set the encoding manually.\n\nYou can set it for a retrieved `Response` object or set it globally for all subsequent connections.\n\n| Parameter Name | Type   | Default Value | Explanation |\n| -------------- | ------ | ------------- | ----------- |\n| `encoding`     | `str`  | Required      | The name of the encoding. To cancel the previous setting, pass `None`. |\n| `set_all`      | `bool` | `True`        | Whether to set the object parameter. If `False`, only the current `Response` object will be set. |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.cookies()`\n\nThis method is used to set one or more cookies.\n\n| Parameter Name | Type                                                          | Default Value | Explanation |\n| -------------- | ------------------------------------------------------------- | ------------- | ----------- |\n| `cookies`      | `RequestsCookieJar`<br/>`list`<br/>`tuple`<br/>`str`<br/>`dict` | Required      | Accepts cookies in various formats |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.cookies.clear()`\n\nThis method is used to clear all cookies.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.cookies.remove()`\n\nThis method is used to remove a cookie.\n\n| Parameter Name | Type   | Default Value | Explanation       |\n| -------------- | ------ | ------------- | ----------------- |\n| `name`         | `str`  | Required      | The name of the cookie |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.headers()`\n\nThis method is used to set headers, which will replace existing headers.\n\n| Parameter Name | Type   | Default Value | Explanation      |\n| -------------- | ------ | ------------- | ---------------- |\n| `headers`      | `dict` | Required      | Universal headers |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.header()`\n\nThis method is used to set an item in the headers.\n\n| Parameter Name | Type   | Default Value | Explanation |\n| -------------- | ------ | ------------- | ----------- |\n| `attr`         | `str`  | Required      | The name of the setting |\n| `value`        | `str`  | Required      | The value of the setting |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.user_agent()`\n\nThis method is used to set the user_agent.\n\n| Parameter Name | Type   | Default Value | Explanation     |\n| -------------- | ------ | ------------- | --------------- |\n| `ua`           | `str`  | Required      | User_agent info |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.proxies()`\n\nThis method is used to set the proxy IP.\n\n| Parameter Name | Type   | Default Value | Explanation                                          |\n| -------------- | ------ | ------------- | ---------------------------------------------------- |\n| `http`         | `str`  | Required      | The http proxy address                               |\n| `https`        | `str`  | `None`        | The https proxy address. If `None`, the value of `http` will be used. |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.auth()`\n\nThis method is used to set the authentication tuple or object.\n\n| Parameter Name | Type                                   | Default Value | Explanation           |\n| -------------- | -------------------------------------- | ------------- | --------------------- |\n| `auth`         | `Tuple[str, str]`<br/>`HTTPBasicAuth` | Required      | The authentication tuple or object |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.hooks()`\n\nThis method is used to set callback functions.\n\n| Parameter Name | Type   | Default Value | Explanation |\n| -------------- | ------ | ------------- | ----------- |\n| `hooks`        | `dict` | Required      | Callback functions |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.params()`\n\nThis method is used to set query parameter dictionary.\n\n| Parameter Name | Type   | Default Value | Explanation      |\n| -------------- | ------ | ------------- | ---------------- |\n| `params`       | `dict` | Required      | Query parameter dictionary |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.verify()`\n\nThis method is used to set whether to verify the SSL certificate.\n\n| Parameter Name | Type   | Default Value | Explanation                         |\n| -------------- | ------ | ------------- | ----------------------------------- |\n| `on_off`       | `bool` | Required      | `bool` indicates on or off |\n\n**Returns:** `None`\n\n## ✅️️ `set.cert()`\n\nThis method is used to set the SSL client certificate.\n\n| Parameter   | Type                         | Default | Description                                   |\n|:------:|:--------------------------:|:---:| ---------------------------------------- |\n| `cert` | `str`<br/>`Tuple[str, str]` | Required  | Path to the SSL client certificate file (.pem format), or a tuple of ('cert', 'key') |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.stream()`\n\nThis method is used to set whether to use streaming response content.\n\n| Parameter     | Type     | Default | Description          |\n|:--------:|:------:|:---:| ----------- |\n| `on_off` | `bool` | Required  | `bool` to represent on or off |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.trust_env()`\n\nThis method is used to set whether to trust the environment.\n\n| Parameter     | Type     | Default | Description          |\n|:--------:|:------:|:---:| ----------- |\n| `on_off` | `bool` | Required  | `bool` to represent on or off |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.max_redirects()`\n\nThis method is used to set the maximum number of redirects.\n\n| Parameter    | Type    | Default | Description      |\n|:-------:|:-----:|:---:| ------- |\n| ``times | `int` | Required  | Maximum number of redirects |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `set.add_adapter()`\n\nThis method is used to add an adapter.\n\n| Parameter      | Type            | Default | Description       |\n|:---------:|:-------------:|:---:| -------- |\n| `url`     | `str`         | Required  | URL corresponding to the adapter |\n| `adapter` | `HTTPAdapter` | Required  | Adapter object    |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ `close()`\n\nThis method is used to close the connection.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n"
  },
  {
    "path": "docs_en/SessionPage/visit_web_page.md",
    "content": "🚄 Visit Web Page\n---\n\nThe s mode of `SessionPage` and `WebPage` is based on the requests library for network connections, so all request methods built into requests can be used, including `get()`, `post()`, `head()`, `options()`, `put()`, `patch()`, and `delete()`. However, this library currently only encapsulates and optimizes `get()` and `post()` methods, and other methods can be used by calling the builtin `Session` object of the page. This document only explains `SessionPage`, and `WebPage` will be introduced in separate sections.\n\n## ✅️️ `get()`\n\n### 📌 Visit Online Web Pages\n\nThe syntax of the `get()` method is the same as the `get()` method of requests, with the addition of retrying connection failures. Unlike requests, it does not return a `Response` object.\n\n| Parameter Name | Type   | Default | Description                          |\n| -------------- | ------ | ------- | ------------------------------------ |\n| `url`          | `str`  | required | Target URL                            |\n| `show_errmsg`  | `bool` | `False` | Whether to display and raise an exception when a connection error occurs |\n| `retry`        | `int`  | `None`  | Number of retries, if `None`, use the page parameter, default is 3 |\n| `interval`     | `float`| `None`  | Retry interval (in seconds), if `None`, use the page parameter, default is 2 |\n| `timeout`      | `float`| `None`  | Loading timeout (in seconds)                  |\n| `**kwargs`     | -      | `None`  | Other parameters required for connection, see the requests documentation for details |\n\n| Return Type | Description     |\n| ----------- | --------------- |\n| `bool`      | Whether the connection is successful |\n\nThe `**kwargs` parameter has the same usage as the corresponding parameter in requests. However, if a certain item is set in this parameter (e.g., `headers`), each item in that item will override the corresponding item read from the configuration instead of completely replacing it. \nThat means if you want to continue using the `headers` information in the configuration and only want to modify one item, you only need to pass the value of that item. This simplifies the code logic.\n\nPractical Features:\n\n- The program automatically adds the `Host` and `Referer` items to `headers` based on the website to be accessed\n- The program automatically determines the encoding from the returned content, so manual setting is generally not required\n\nSimple access to web pages:\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\npage.get('http://g1879.gitee.io/drissionpage')\n```\n\nAccess web pages with connection parameters:\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\n\nurl = 'https://www.baidu.com'\nheaders = {'referer': 'gitee.com'}\ncookies = {'name': 'value'}\nproxies = {'http': '127.0.0.1:1080', 'https': '127.0.0.1:1080'}\npage.get(url, headers=headers, cookies=cookies, proxies=proxies)\n```\n\n---\n\n### 📌 Read Local Files\n\nThe `url` parameter of `get()` can point to a local file to implement local HTML parsing.\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\npage.get(r'D:\\demo.html')\n```\n\n---\n\n## ✅️️ `post()`\n\nThis method requests a page using the POST method. It can be used in the same way as `get()`.\n\n| Parameter Name | Type   | Default | Description                          |\n| -------------- | ------ | ------- | ------------------------------------ |\n| `url`          | `str`  | required | Target URL                            |\n| `show_errmsg`  | `bool` | `False` | Whether to display and raise an exception when a connection error occurs |\n| `retry`        | `int`  | `None`  | Number of retries, if `None`, use the page parameter, default is 3 |\n| `interval`     | `float`| `None`  | Retry interval (in seconds), if `None`, use the page parameter, default is 2 |\n| `**kwargs`     | -      | `None`  | Other parameters required for connection, see the requests documentation for details |\n\n| Return Type | Description     |\n| ----------- | --------------- |\n| `bool`      | Whether the connection is successful |\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\ndata = {'username': 'xxxxx', 'pwd': 'xxxxx'}\n\npage.post('http://example.com', data=data)\n# Or\npage.post('http://example.com', json=data)\n```\n\nBoth the `data` parameter and `json` parameter can accept `str` and `dict` format data, which means there are 4 ways to pass data:\n\n```python\n# Pass a string to the data parameter\npage.post(url, data='abc=123')\n\n# Pass a dictionary to the data parameter\npage.post(url, data={'abc': '123'})\n\n# Pass a string to the json parameter\npage.post(url, json='abc=123')\n\n# Pass a dictionary to the json parameter\npage.post(url, json={'abc': '123'})\n```\n\nWhich one to use depends on the server requirements.\n\n---\n\n## ✅️️ Other Request Methods\n\nThis library only optimizes the commonly used get and post methods, but other request methods can also be executed in the native requests code manner by extracting the `Session` object inside the page object.\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\n# Get the builtin session object\nsession = page.session\n# Send a request using the head method\nresponse = session.head('https://www.baidu.com')\nprint(response.headers)\n```\n\n**Output:**\n\n```shell\n{'Accept-Ranges': 'bytes', 'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Connection': 'keep-alive', 'Content-Length': '277', 'Content-Type': 'text/html', 'Date': 'Tue, 04 Jan 2022 06:49:18 GMT', 'Etag': '\"575e1f72-115\"', 'Last-Modified': 'Mon, 13 Jun 2016 02:50:26 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18'}\n```\n\n"
  },
  {
    "path": "docs_en/WebPage/create_page_object.md",
    "content": "🛸 Create Page Object\n---\n\nThis section introduces the creation of the `WebPage` object.\n\nThe `WebPage` object has two modes: \"d\" mode for manipulating the browser and \"s\" mode for sending and receiving packets.\n\n## ✅️️ `WebPage` Initialization Parameters\n\n| Initialization Parameter  | Type                                              | Default | Description                                                                                                                                 |\n|:------------------------:|:-------------------------------------------------:|:-------:| ------------------------------------------------------------------------------------------------------------------------------------------- |\n| `mode`                   | `str`                                             | `'d'`   | Can only receive `'d'` or `'s'`, indicating initial selection for manipulating the browser or sending and receiving packets                                          |\n| `timeout`                | `float`                                           | `None`  | Overall timeout, if `None`, read from the configuration file, default is 10                                                                 |\n| `chromium_options`       | `ChromiumOptions`<br/>`False`                     | `None`  | Default is `None`, indicating reading the configuration from the ini file<br/>When receive `ChromiumOptions`, use this configuration to start or take over the browser<br/>If not using d mode, receive `False` to avoid packaging errors       |\n| `session_or_options`     | `Session`<br/>`SessionOptions`<br/>`False`        | `None`  | Default is `None`, indicating reading the configuration from the ini file<br/>When receive `Session`, directly use an already created `Session` object<br/>When receive `SessionOptions`, use this configuration to create a `Session` object<br/>If not using s mode, receive `False` to avoid packaging errors |\n\n---\n\n## ✅️️ Creating Directly\n\nThis method has the simplest code and the program will read the configuration from the default ini file and generate the page object automatically.\n\nWhen creating, you can specify the initial mode.\n\n```python\nfrom DrissionPage import WebPage\n\n# Create an object in d mode by default\npage = WebPage()\n\n# Create an object in s mode\npage = WebPage('s')\n```\n\nCreating `WebPage` object in d mode will start the browser on the specified port or take over the existing browser on that port.\n\nBy default, the program uses port 9222 and the executable file path of the browser is `'chrome'`. If the browser executable file is not found in the path, the program will search for the path in the registry on Windows system. If it is still not found, the next method needs to be configured manually.\n\n:::warning Note\n    Programs created using this method cannot be packaged directly because they use the ini file. Refer to the methods in the \"Packaging Programs\" section.\n:::\n\n:::tip Tips\n    You can modify the configuration in the configuration file to start as you need for all programs. Refer to the \"Startup Configuration\" section for details.\n:::\n\n---\n\n## ✅️️ Creating with Configuration Information\n\nIf you need to start the browser in a specified way, you can use `ChromiumOptions` and `SessionOptions`. Their usage has been introduced in their respective sections, and here we only demonstrate how to use them when creating `WebPage`.\n\n### 📌 Usage\n\nCreate two configuration objects and pass them to the initialization method of `WebPage`.\n\n```python\nfrom DrissionPage import WebPage, ChromiumOptions, SessionOptions\n\nco = ChromiumOptions()\nso = SessionOptions()\n\npage = WebPage(chromium_options=co, session_or_options=so)\n```\n\nIf you only need to modify the configuration for one mode and use the configuration from the ini file for the other mode, you can only pass one type of configuration object.\n\n```python\nfrom DrissionPage import WebPage, ChromiumOptions\n\nco = ChromiumOptions()\npage = WebPage(chromium_options=co)\n```\n\n:::info Note\n    When both `ChromiumOptions` and `SessionOptions` are passed, the attributes both have will refer to the `ChromiumOptions`. For example, `timeout` and `download_path`.\n:::\n\n---\n\n### 📌 Creating with a Specified ini File\n\nThe above methods create objects using the configuration information saved in the default ini file. You can save an ini file to another location and specify to use it when creating objects.\n\n```python\nfrom DrissionPage import WebPage, ChromiumOptions, SessionOptions\n\nco = ChromiumOptions(ini_path=r'./config1.ini')\nso = SessionOptions(ini_path=r'./config1.ini')\n\npage = ChromiumPage(addr_or_opts=co, session_or_options=so)\n```\n\n---\n\n"
  },
  {
    "path": "docs_en/WebPage/introduction.md",
    "content": "🛸 Overview\n---\n\nThe `WebPage` object integrates `SessionPage` and `ChromiumPage`, enabling communication between the two.\n\nIt can control the browser and send/receive data packets, and synchronizes login information between the two.\n\nIt has two modes: d and s, corresponding to controlling the browser and sending/receiving data packets, respectively.\n\n`WebPage` can flexibly switch between these two modes, allowing for interesting use cases.\n\nFor example, if the website login code is very complex and using data packets is too complicated, we can use the browser to handle the login and then switch to the data packet mode to collect data.\n\nThe logic for using both modes is the same and there is no difference compared to `ChromiumPage`, making it easy to get started.\n\nDiagram of the `WebPage` structure:\n\n![](../imgs/webpage.jpg)\n\n"
  },
  {
    "path": "docs_en/WebPage/mode_switch.md",
    "content": "🛸 Mode Change\n---\n\nThis section introduces the mode change function of `WebPage`.\n\nThe d mode of `WebPage` behaves the same as `ChromiumPage`, while the s mode behaves the same as `SessionPage`.\n\nUse the `change_mode()` method to switch modes. When switching modes, login information will be synchronized.\n\n## ✅️️ `mode`\n\n**Type:** `str`\n\nThis property returns the current mode of `WebPage`.\n\n**Example:**\n\n```python\nfrom DrissionPage import WebPage\n\npage = WebPage()\nprint(page.mode)\n```\n\n**Output:**\n\n```shell\nd\n```\n---\n\n## ✅️️ `change_mode()`\n\nThis method is used to switch the running mode of `WebPage`.\n\n| Parameter        | Type             | Default Value | Description                                                |\n|:----------------:|:----------------:|:-------------:| ---------------------------------------------------------- |\n| `mode`           | `str`<br/>`None` | `None`        | Accepts 's' or 'd' to switch to the specified mode.<br/>Accepts `None` to switch to the other mode relative to the current one |\n| `go`             | `bool`           | `True`        | Whether the target mode should navigate to the url of the original mode |\n| `copy_cookies`   | `bool`           | `True`        | Whether to copy cookies to the target mode when switching  |\n\n**Returns:** `None`\n\n---\n\n## ✅️️ Cross-mode Functionality\n\nSome functionalities are exclusive to the d mode, such as `click()`, while others are exclusive to the s mode, such as `post()`.\n\nIn fact, regardless of the mode, the connection of the other mode still exists. Therefore, it is perfectly fine to call browser element clicks in the s mode, and they do not conflict with each other.\n\nThis design allows for great flexibility. For example, to synchronize login status, you only need to switch modes or pass cookies.\n\n### 📌 `cookies_to_session()`\n\nThis method is used to copy the cookies of the current browser page to the `Session` object.\n\n| Parameter               | Type      | Default Value | Description             |\n|:-----------------------:|:---------:|:-------------:| ----------------------- |\n| `copy_user_agent`       | `bool`    | `True`        | Whether to copy the user agent information |\n\n**Returns:** `None`\n\n### 📌 `cookies_to_browser()`\n\nThis method is used to copy the cookies of the `Session` object to the browser.\n\n---\n\n### 📌 Explanation of `post()` Return Value\n\nThe `post()` method of `SessionPage` returns whether the webpage is accessible, while the content is obtained using `page.html` or `page.json`.\n\nIn the s mode of `WebPage`, the usage of `post()` is the same.\n\nHowever, in the d mode, since `post()` is a function of the s mode and conflicts with the `html` parameter of the d mode, `post()` in the d mode returns the obtained `Response` object, which is consistent with the usage of `requests`.\n\n---\n\n## ✅️️ Example\n\n### 📌 Switching Modes\n\n```python\nfrom DrissionPage import WebPage\n\npage = WebPage()\npage.get('https://www.baidu.com')\nprint(page.mode)\npage.change_mode()\nprint(page.mode)\nprint(page.title)\n```\n\n**Output:**\n\n```shell\nd\ns\n百度一下，你就知道\n```\n\nIn this example, the following operations are performed:\n\n- Initially, access Baidu in d mode.\n\n- Switch to s mode, which will synchronize the login information to the s mode and access Baidu in the s mode.\n\n- Print the page title accessed in the s mode.\n\n"
  },
  {
    "path": "docs_en/WebPage/webpage_function.md",
    "content": "🛸 Unique Features\n---\n\nThis section introduces the unique features of `WebPage`.\n\n`WebPage` is integrated with `ChromiumPage` and `SessionPage`, and therefore has all the functionalities of both. For more details on these functionalities, refer to the relevant chapters. Here, we only introduce the unique features of `WebPage`.\n\n## ✅️️ Cookie Handling\n\n### 📌 `cookies_to_session()`\n\nThis method copies the cookies from the browser to the `session` object.\n\n|  Parameter Name   |  Type  | Default Value | Description                            |\n|:-----------------:|:------:|:-------------:|----------------------------------------|\n|  `copy_user_agent` | `bool` |    `True`     | Specifies whether to copy user agent information |\n\n**Returns:** `None`\n\n---\n\n### 📌 `cookies_to_browser()`\n\nThis method copies the cookies from the `session` object to the browser.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n## ✅️️ Property Settings\n\nThe values set by the `set_cookies()`, `set_headers()`, and `set_user_agent()` methods are only valid for the current mode. This means that when calling these methods in d mode, the browser will be set, but not the Session object, and vice versa.\n\n## ✅️️ Tab\n\nThe `get_tab()` method of `WebPage` returns a Tab object, which is a WebPageTab that can also switch states. Except for not being able to control the download functionality of the tab and browser, all other functionalities are the same as those of `WebPage`.\n\nWhen a `WebPageTab` is newly created, it is in d mode.\n\n**Example:**\n\n```python\nfrom DrissionPage import WebPage\n\npage = WebPage()\npage.get('https://www.baidu.com')\ntab = page.get_tab()\ntab.change_mode()\ntab.get('https://gitee.com')\nprint(tab.title)\n```\n\n## ✅️️ Closing Objects\n\n### 📌 `close_driver()`\n\nThis method closes the built-in `Driver` object and the browser, and switches to s mode.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `close_session()`\n\nThis method closes the built-in `Session` object and the browser, and switches to d mode.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `close()`\n\nThis method is used to close the current tab and Session.\n\n**Parameters:** None\n\n**Returns:** `None`\n\n---\n\n### 📌 `quit()`\n\nThis method completely closes the built-in `Session` object and `Driver` object, and closes the browser (if open).\n\n|  Parameter Name  |   Type   | Default Value | Description                                      |\n|:----------------:|:--------:|:-------------:|--------------------------------------------------|\n|    `timeout`     | `float`  |      `5`      | Timeout for waiting for the browser to close      |\n|     `force`      | `bool`   |    `False`    | Specifies whether to forcefully terminate process |\n\n**Returns:** `None`\n\n"
  },
  {
    "path": "docs_en/advance/accelerate_reading.md",
    "content": "⚙️ Speed Up Data Retrieval\n---\n\nThis section demonstrates a black technology that can greatly accelerate data collection on web pages.\n\n## ✅️️ Example\n\nLet's take a relatively large webpage as an example, such as the homepage of [https://www.163.com](https://www.163.com).\n\nLet's count the number of `<a>` elements within this webpage:\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('https://www.163.com')\nprint(len(page('t:body').eles('t:a')))\n```\n\n**Output:**\n\n```shell\n1613\n```\n\nHmm, that's quite a number, indicating an obvious effect.\n\nSuppose our task is now to print the text of all the links. The common approach is to iterate through all the elements and print them.\n\nHere we introduce a timing tool written by the library author, which can measure the execution time of a code segment. You can also use other methods to measure time.\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom TimePinner import Pinner  # Import the timing tool\n\npinner = Pinner()  # Create a timer object\npage = ChromiumPage()\npage.get('https://www.163.com')\n\npinner.pin()  # Mark the start of recording\n\n# Get all link objects and iterate through them\nlinks = page('t:body').eles('t:a')\nfor lnk in links:\n    print(lnk.text)\n\npinner.pin('Time Elapsed')  # Record and print the time elapsed\n```\n\n**Output:**\n\n```shell\n0.0\n\n网络大过年_网易政务_网易网\n网易首页\n...middle part omitted...\n不良信息举报 Complaint Center\n廉正举报\nTime Elapsed: 4.057772700001806\n```\n\nIt took 4 seconds.\n\nNow, let's make a small modification.\n\nChange `page('t:body').eles('t:a')` to `page('t:body').s_eles('t:a')` and execute it again.\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom TimePinner import Pinner  # Import the timing tool\n\npinner = Pinner()  # Create a timer object\npage = ChromiumPage()\npage.get('https://www.163.com')\n\npinner.pin()  # Mark the start of recording\n\n# Get all link objects and iterate through them\nlinks = page('t:body').s_eles('t:a')\nfor lnk in links:\n    print(lnk.text)\n\npinner.pin('Time Elapsed')  # Record and print the time elapsed\n```\n\n**Output:**\n\n```shell\n0.0\n\n网络大过年_网易政务_网易网\n网易首页\n...middle part omitted...\n不良信息举报 Complaint Center\n廉正举报\nTime Elapsed: 0.2797656000002462\n```\n\nIsn't it magical? The data collection time that used to be 4 seconds now only takes 0.28 seconds.\n\n---\n\n## ✅️️ Interpretation\n\nThe difference between `s_eles()` and `eles()` is that the former converts the entire page or dynamic element into a static element, and then retrieves sub-elements or information within it. Because static elements are plain text and do not have attributes, interactions, or other resource-consuming components, their execution speed is very fast.\n\nThe author once collected a very complex webpage, which took 30 seconds using dynamic elements, but it only took 0.X seconds after converting them into static elements, demonstrating a significant acceleration effect.\n\nWe can obtain content containers within the page (such as `<body>` in the example) and convert them into static elements to retrieve information within them.\n\nOf course, static elements do not have interactive functions. They are only copies and do not affect the original dynamic elements.\n\n"
  },
  {
    "path": "docs_en/advance/commands.md",
    "content": "⚙️ Using Command Line\n---\n\nDrissionPage provides some convenient command line commands for basic settings, instead of using temporary configuration files.\n\nThe main command for the command line is `dp`, and the format is:\n\n```shell\ndp full command name or abbreviation <parameters>\n```\n\n## ✅️️ Set Browser Path\n\n| Full Name                | Abbreviation  | Parameters  | Description            |\n|:-----------------------:|:-------------:|:-----------:|:---------------------:|\n| --set-browser-path | -p                      | browser path | Set the browser path in the configuration file |\n\n**Example:**\n\n```shell\n# Full format\ndp --set-browser-path D:\\chrome\\Chrome.exe\n\n# Abbreviated format\ndp -p D:\\chrome\\Chrome.exe\n```\n\n## ✅️️ Set User Data Path\n\n| Full Name                | Abbreviation  | Parameters    | Description                |\n|:-----------------------:|:-------------:|:-------------:|:-------------------------:|\n| --set-user-path    | -u                      | user data folder path | Set the user data path in the configuration file |\n\n**Example:**\n\n```shell\n# Full format\ndp --set-user-path D:\\chrome\\user_data\n\n# Abbreviated format\ndp -u D:\\chrome\\user_data\n```\n\n## ✅️️ Copy Default INI File to Current Path\n\n| Full Name                    | Abbreviation  | Parameters  | Description                |\n|:---------------------------:|:-------------:|:-----------:|:-------------------------:|\n| --configs-to-here        | -c                      | None           | Copy the default configuration file to the current path |\n\n**Example:**\n\n```shell\n# Full format\ndp --configs-to-here\n\n# Abbreviated format\ndp -c\n```\n\n## ✅️️ Launch Browser\n\nThis command is used to launch the browser and wait for the program to take over.\n\n| Full Name                       | Abbreviation  | Parameters  | Description                |\n|:------------------------------:|:-------------:|:-----------:|:-------------------------:|\n| --launch-browser           | -l                      | port number  | Launch the browser, pass in the port number, where 0 indicates using the value in the configuration file |\n\n**Example:**\n\n```shell\n# Full format\ndp --launch-browser 9333\n\n# Abbreviated format\ndp -l 0\n```\n\n"
  },
  {
    "path": "docs_en/advance/errors.md",
    "content": "⚙️ Usage of Exceptions\n---\n\nThis section introduces custom exceptions in DrissionPage.\n\n## ✅️️ Import\n\nVarious exceptions are located in the `DrissionPage.errors` path.\n\n```python\nfrom DrissionPage.errors import *\n```\n\n## ✅️️ Exception Introduction\n\n### 📌 `ElementNotFoundError`\n\nRaised when an element cannot be found.\n\n---\n\n### 📌 `AlertExistsError`\n\nThrown when executing JS or calling functions implemented through JS, if there is an unhandled alert present.\n\n---\n\n### 📌 `ContextLostError`\n\nRaised when elements are called after the page has been refreshed.\n\n---\n\n### 📌 `ElementLostError`\n\nThrown when an element becomes invalid due to page or self-refresh, but is still called.\n\n---\n\n### 📌 `CDPError`\n\nThrown when an exception occurs while invoking cdp methods.\n\n---\n\n### 📌 `PageDisconnectedError`\n\nRaised when the page is closed or the connection is disconnected, but its functions are still called.\n\n---\n\n### 📌 `JavaScriptError`\n\nThrown when there is a JavaScript runtime error.\n\n---\n\n### 📌 `NoRectError`\n\nRaised when attempting to retrieve size and position information for an element that doesn't have any.\n\n---\n\n### 📌 `BrowserConnectError`\n\nThrown when there is an error connecting to the browser.\n\n---\n\n### 📌 `NoResourceError`\n\nThrown when accessing resources fails for the browser element's `get_src()` and `save()` methods.\n\n---\n\n### 📌 `CanNotClickError`\n\nThrown when clicking on an element that is not clickable and is set to allow the exception to be thrown.\n\n### 📌 `GetDocumentError`\n\nThrown when getting the page document fails.\n\n---\n\nThrown when getting the page document fails.\n\n### 📌 `WaitTimeoutError`\n\nThrown when automatic waiting fails and is set to allow the exception to be thrown.\n\n---\n\n### 📌 `WrongURLError`\n\nThrown when accessing a url with an incorrect format.\n\n---\n\n### 📌 `StorageError`\n\nThrown when there is a prohibition on operating data, such as a website prohibiting the operation.\n\n---\n\n### 📌 `CookieFormatError`\n\nThrown when importing a cookie with an incorrect format.\n\n"
  },
  {
    "path": "docs_en/advance/ini_file.md",
    "content": "⚙️ Using Configuration Files\n---\n\nThis library uses ini files to record the startup configuration of browsers or `Session` objects. It facilitates configuration reuse and avoids adding cumbersome configuration information in the code.  \nBy default, the configuration information in the file is automatically loaded when the page object is started.  \nYou can also modify the default configuration and save it to the ini file using a simple method.  \nMultiple ini files can be saved and called according to different project needs.\n\n:::warning Note\n    - Ini files are only used to manage startup configurations. Modifying the ini file after creating the page object has no effect.\n    - These settings also have no effect if you take over an already opened browser.\n    - Every time this library is upgraded, the ini file will be reset. You can save it to another path to avoid resetting.\n:::\n\n## ✅️️ Content of ini Files\n\nThe initial content of ini files is as follows.\n\n```ini\n[paths]\ndownload_path = \ntmp_path = \n\n[chromium_options]\naddress = 127.0.0.1:9222\nbrowser_path = chrome\narguments = ['--no-default-browser-check', '--disable-suggestions-ui', '--no-first-run', '--disable-infobars', '--disable-popup-blocking', '--disable-popup-blocking']\nextensions = []\nprefs = {'profile.default_content_settings.popups': 0, 'profile.default_content_setting_values': {'notifications': 2}}\nflags = {}\nload_mode = normal\nuser = Default\nauto_port = False\nsystem_user_path = False\nexisting_only = False\n\n[session_options]\nheaders = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'connection': 'keep-alive', 'accept-charset': 'GB2312,utf-8;q=0.7,*;q=0.7'}\n\n[timeouts]\nbase = 10\npage_load = 30\nscript = 30\n\n[proxies]\nhttp =\nhttps = \n\n[others]\nretry_times = 3\nretry_interval = 2\n```\n\n---\n\n## ✅️️ File Location\n\nThe default configuration file is stored in the _configs folder of the DrissionPage library, with the file name configs.ini.  \nUsers can save other configuration files or read configurations from saved files, but the location and name of the default file will not change.\n\n---\n\n## ✅️️ Starting with the Default Configuration File\n\n### 📌 Automatically Loading with Page Object\n\nThis is the default startup method.\n\n```python\nfrom DrissionPage import WebPage\n\npage = WebPage()\n```\n\n---\n\n### 📌 Loading with Configuration Object\n\nThis method is generally used when the configuration needs to be further modified after loading.\n\n```python\nfrom DrissionPage import ChromiumOptions, SessionOptions, WebPage\n\nco = ChromiumOptions(ini_path=r'D:\\setting.ini')\nso = SessionOptions(ini_path=r'D:\\setting.ini')\n\npage = WebPage(chromium_options=co, session_or_options=so)\n```\n\n---\n\n## ✅️️ Saving/Creating a New ini File\n\n```python\nfrom DrissionPage import ChromiumOptions\n\nco = ChromiumOptions()\n\n# Modify some settings\nco.no_imgs()\n\n# Save to the currently opened ini file\nco.save()\n# Save to a specified location for the configuration file\nco.save(r'D:\\config1.ini')\n# Save to the default configuration file\nco.save_to_default()\n```\n\n---\n\n## ✅️️ Using ini Files in Project Paths\n\nThe default ini file is stored in the DrissionPage installation directory, and modifications need to be made through code, which is inconvenient for debugging.\n\nTherefore, a method is provided to conveniently copy the default ini file to the current project folder, and the program will prioritize using the ini file in the project folder for initialization configuration.\n\nIn this way, developers can easily manually change the configuration. Project packaging can also be done directly without causing any file not found issues.\n\nThe ini file copied to the project is named `'dp_configs.ini'`, and the program will read the configuration of this file by default.\n\n### 📌 `configs_to_here()`\n\nThis method is located in the `DrissionPage.common` path and is used to copy the default ini file to the current path and rename it to `'dp_configs.ini'`.\n\n| Parameter    | Type   | Default Value | Description                                                     |\n| ------------ | ------ | ------------- | --------------------------------------------------------------- |\n| `save_name`  | `str`  | `None`        | Specifies the file name. If `None`, it will be named `'dp_configs.ini'`. |\n\n**Returns:** `None`\n\n**Example:**\n\nCreate a new .py file in the project and enter the following content, then run it\n\n```python\nfrom DrissionPage.common import configs_to_here\n\nconfigs_to_here()\n```\n\nAfterwards, the project folder will have a new `'dp_configs.ini'` file. This file will be prioritized when initializing the page object.\n\n### 📌 Copying Using Command Line\n\nIn addition to using the `configs_to_here()` method to copy the ini file to the project folder, you can also use the command line to copy.\n\nRun the following command in the project folder:\n\n```shell\ndp --configs-to-here\n```\n\nThe effect is the same as `configs_to_here()`, but you cannot specify the file name.\n\n"
  },
  {
    "path": "docs_en/advance/packaging.md",
    "content": "⚙️ Packaging Program\n---\n\nThis section describes things to consider when packaging a program.\n\n## ✅️️ Use a new virtual environment!\n\nDevelop the good habit of using a newly created virtual environment and only install necessary libraries for packaging, which can reduce the size of the packaged exe file. The size of the program packaged in an environment with only DrissionPage installed is approximately 14M.\n\nIf the size of the program you packaged is huge, please try this method.\n\n---\n\n## ✅️️ Solve the problem of missing ini file error\n\nBecause the program uses an ini file, and the ini file is not automatically included when packaging, directly packaging will cause runtime errors.\n\nSolution:\n\n- Manually include the ini file and specify the path in the program\n- Write the configuration information in the program without using the ini file\n\n### 📌 Include the ini file\n\nSpecify the ini file using relative path in the program and copy the ini file to the program folder.\n\n```python\nfrom DrissionPage import WebPage, ChromiumOptions, SessionOptions\n\nco = ChromiumOptions(ini_path=r'.\\configs.ini')\nso = SessionOptions(ini_path=r'.\\configs.ini')\npage = WebPage(chromium_options=co, session_or_options=so)\n```\n\nYou can use the `configs_to_here()` method to automatically copy the ini file.\n\nCreate a new py file in the project, enter the following content and run it.\n\n```python\nfrom DrissionPage.common import configs_to_here\n\nconfigs_to_here()\n```\n\nAfter that, the project folder will have an additional `'dp_configs.ini'` file. The page object will prioritize reading this file during initialization.\n\nJust put it together with the packaged executable file.\n\n---\n\n### 📌 Do not use ini\n\nSpecify not to use the ini file in the program to avoid errors. In this method, all configuration information needs to be written in the code.\n\nTake `WebPage` as an example, the usage of `ChromiumPage` and `SessionPage` is the same.\n\n```python\nfrom DrissionPage import WebPage, ChromiumOptions, SessionOptions\n\nco = ChromiumOptions(read_file=False)  # Create a new configuration object without reading the file\nco.set_browser_path(r'.\\chrome.exe')  # Enter the configuration information\nso = SessionOptions(read_file=False)\n\npage = WebPage(chromium_options=co, session_or_options=so)\n```\n\nNote that both the driver and session parameters need to be entered when using this method. If one of them does not need to be set, you can enter `False`:\n\n```python\npage = WebPage(chromium_options=co, session_or_options=False)\n```\n\n---\n\n## ✅️️ Practical examples\n\nUsually, I will put a portable browser and the packaged exe file together, and use relative paths in the program to point to the browser. This way, the program can be used normally on other computers as well.\n\n```python\nfrom DrissionPage import WebPage, ChromiumOptions\n\nco = ChromiumOptions(read_file=False).set_paths(local_port='9888',\n                                                browser_path=r'.\\Chrome\\chrome.exe',\n                                                user_data_path=r'.\\Chrome\\userData')\npage = WebPage(chromium_options=co, session_or_options=False)\n# Note: session_or_options=False\n\npage.get('https://www.baidu.com')\n```\n\nNote the following two points and the program will skip reading the ini file:\n\n- Set `read_file=False` in `ChromiumOptions()`\n- If you do not pass the configuration for a certain mode (in the example, it is the s mode), set the corresponding parameter to `False` when initializing the page object.\n\n"
  },
  {
    "path": "docs_en/advance/settings.md",
    "content": "⚙️ Global Settings\n---\n\nThere are some global settings at runtime that can control certain behaviors of the program.\n\n## ✅️️ Usage\n\nGlobal settings are located in the `DrissionPage.common` path.\n\nUse assignment to modify the properties of the `Settings` object.\n\nUsage:\n\n```python\nfrom DrissionPage.common import Settings\n\nSettings.raise_when_wait_failed = True\n```\n\n---\n\n## ✅️️ Settings Options\n\n### 📌 `raise_when_ele_not_found`\n\nSets whether or not to raise an exception when an element is not found. Default is `False`.\n\n---\n\n### 📌 `raise_when_click_failed`\n\nSets whether or not to raise an exception when clicking fails. Default is `False`.\n\n---\n\n### 📌 `raise_when_wait_failed`\n\nSets whether or not to raise an exception when waiting fails. Default is `False`.\n\n---\n\n### 📌 `singleton_tab_obj`\n\nSets whether or not the Tab object should use the singleton pattern. Default is `True`.\n\n---\n\n## ✅️️ Examples\n\nThis example sets to immediately raise an exception when an element is not found (instead of returning `NoneElement`).\n\nYou can execute it directly to see the effect.\n\n```python\nfrom DrissionPage import SessionPage\nfrom DrissionPage.common import Settings\n\nSettings.raise_when_ele_not_found = True\n\npage = SessionPage()\npage.get('https://www.baidu.com')\nele = page('#abcd')\n```\n\n**Output:**\n\n```shell\n...omitted...\nDrissionPage.errors.ElementNotFoundError: \nElement not found.\nmethod: ele()\nargs: {'locator': '#abcd'}\n```\n\n"
  },
  {
    "path": "docs_en/advance/tools.md",
    "content": "⚙️ Tools\n---\n\nThe `DrissionPage.common` module provides several small tools.\n\n## ✅️️ `make_session_ele()`\n\nThis method is used to convert a page object, element object, or HTML text into a `SessionElement` object, or to search for elements based on it and get their static versions.\n\n|  Parameter Name |                                                                      Type                                                                      | Default | Description |\n|:--------------:|:--------------------------------------------------------------------------------------------------------------------------------------------:|:-------:|-------------|\n| `html_or_ele` | `str`<br/>`ChromiumElement`<br/>`ChromiumPage`<br/>`ChromiumTab`<br/>`WebPage`<br/>`WebPageTab`<br/>`ChromiumFrame`<br/>`ShdownRoot` | Required | HTML text, element, or page object |\n|      `loc`     |                                                     `str`<br/>`Tuple[str, str]`                                                      |  None   | Location tuple or string. When it is `None`, no search in sub-elements and returns the root element |\n|      `index`    |                                                                `int`                                                                 |   `1`   | Get the nth element, starting from `1`. A negative number can be passed to get the nth element from the end. `None` returns all elements |\n\n|       Return Type       |                   Description                   |\n|:----------------------:|:-----------------------------------------------:|\n|    `SessionElement`    |  return a static element object when the index is a number   |\n| `List[SessionElement]` | return a list of static element objects when the index is `None` |\n\n**Example:**\n\n```python\nfrom DrissionPage.common import make_session_ele\n\nhtml = '''\n<html><body><div>abc</div></body></html>\n'''\nele = make_session_ele(html)\nprint(ele.text)\n```\n\n**Output:**\n\n```shell\nabc\n```\n\n---\n\n## ✅️️ `get_blob()`\n\nThis method is used to get the content of the specified blob resource.\n\n:::warning Note\n    - If the resource is inside an iframe element from another domain, you must get the iframe element object and pass it in to retrieve it\n    - This method can only be used to get static resources and cannot be used for streaming media\n:::\n\n|  Parameter Name  |                                         Type                                          | Default | Description |\n|:----------------:|:------------------------------------------------------------------------------------:|:-------:|-------------|\n|      `page`     | `ChromiumPage`<br/>`ChromiumTab`<br/>`WebPage`<br/>`WebPageTab`<br/>`ChromiumFrame` | Required | The page object where the resource is located |\n|     `url`      |                                        `str`                                        | Required | Blob resource url |\n| `as_bytes` |                                       `bool`                                        | `True` | Whether to return as `bytes` type |\n\n|   Return Type    |                         Description                          |\n|:----------------:|:-----------------------------------------------------------:|\n|      `str`       |    return as base64 format when the `as_bytes` is `False`     |\n|     `bytes`     |              return as byte data when the `as_bytes` is `True`            |\n\n---\n\n## ✅️️ `configs_to_here()`\n\nThis method is used to copy the ini file to the current directory.\n\n|  Parameter Name  |  Type  | Default | Description |\n|:----------------:|:-----:|:-------:|-------------|\n|      `save_name`     | `str` |  `None`   | Specify the file name. If it is `None`, it will be named as `'dp_configs.ini'` |\n\n**Return:** `None`\n\n---\n\n## ✅️️ `wait_until()`\n\nThis method is used to wait until the return value of the passed method is not falsy. It will raise `TimeoutError` if timed out.\n\n|  Parameter Name  |       Type       | Default | Description |\n|:----------------:|:----------------:|:-------:|-------------|\n|   `function`       | `callable` | Required | The method to be executed |\n|    `kwargs`      |   `dict`   |  `None`  | Method parameters |\n|    `timeout`    |   `float`   |   `10`   | Timeout in seconds |\n\n**Return:** `Any`\n\n"
  },
  {
    "path": "docs_en/cooperation.md",
    "content": "---\nid: cooper\ntitle: Business cooperation\n---\n\nUndertake automated orders, contact QQ:178691442"
  },
  {
    "path": "docs_en/demos/douban_book_pics.md",
    "content": "🌠 Download Douban Book Covers\n---\n\nThe example from Starbucks uses the `download()` method to download images. This example demonstrates how to directly save images in a browser.\n\nThis feature is a highlight of this library. It does not require any UI operations or re-downloading of images. Instead, it directly reads and saves images from the cache, making it very convenient to use.\n\n## ✅️️ Page Analysis\n\nTarget URL: [https://book.douban.com/tag/小说](https://book.douban.com/tag/%E5%B0%8F%E8%AF%B4)\n\nBy pressing `F12`, you can see that each book is contained in an element with the `class` attribute set to `subject-item`. You can retrieve them in batches and then retrieve the `<img>` element to save the image.\n\n---\n\n## ✅️️ Coding Approach\n\nIn order to demonstrate the `save()` method of the element object, we will use browser operations to save the image files to the local `imgs` folder.\n\n---\n\n## ✅️️ Example Code\n\nThe following code can be run directly.\n\n```python\nfrom DrissionPage import ChromiumPage\n\n# Create a page object\npage = ChromiumPage()\n# Visit the target webpage\npage.get('https://book.douban.com/tag/小说?start=0&type=T')\n\n# Scrape 4 pages\nfor _ in range(4):\n    # Iterate through all the books on a single page\n    for book in page.eles('.subject-item'):\n        # Get the cover image object\n        img = book('t:img')\n        # Save the image\n        img.save(r'.\\imgs')\n\n    # Click the next page\n    page('后页>').click()\n    page.wait.load_start()\n```\n\n---\n\n## ✅️️ Result\n\n![](../imgs/20230105105418.png)\n\n"
  },
  {
    "path": "docs_en/demos/login_gitee.md",
    "content": "🌠 Gitee Auto Login\n---\n\nThis example demonstrates how to automatically login to the Gitee website by controlling the browser.\n\n## ✅️️ Web Analysis\n\nURL: https://gitee.com/login\n\n![](../imgs/login_gitee1.jpg)\n\nPress `F12` to view the code, and you can see that both input boxes can be located using the `id` attribute, as shown in the image.\n\n![](../imgs/login_gitee2.jpg)\n\n---\n\n## ✅️️ Coding Idea\n\nElements with the `id` attribute are easy to locate. Both input boxes can be directly located using the `id` attribute.  \nThe login button does not have an `id` attribute, but it can be observed that it is the first element with the `value` attribute set to `'登 录'`, so it can also be located using the Chinese text for better code readability.\n\nSince we are using a browser for logging in, we will use `ChromiumPage` to control the browser.\n\n---\n\n## ✅️️ Sample Code\n\n```python\nfrom DrissionPage import ChromiumPage\n\n# Create a page object in 'd' mode (default mode)\npage = ChromiumPage()\n# Navigate to the login page\npage.get('https://gitee.com/login')\n\n# Locate the account input box and enter the account\npage.ele('#user_login').input('Your account')\n# Locate the password input box and enter the password\npage.ele('#user_password').input('Your password')\n\n# Click the login button\npage.ele('@value=登 录').click()\n```\n\n---\n\n## ✅️️ Result\n\nLogin successful.\n\n![](../imgs/login_gitee3.jpg)\n\n"
  },
  {
    "path": "docs_en/demos/maoyan_TOP100.md",
    "content": "🌠 Collecting Maoyan Movie Rankings\n---\n\nThis example demonstrates data collection using a web browser.\n\n## ✅️️ Collection Target\n\nTarget URL: [https://www.maoyan.com/board/4](https://www.maoyan.com/board/4)\n\nCollection target: Rankings, Movie Title, Actors, Release Date, Score\n\n---\n\n## ✅️️ Encoding Strategy\n\nBy pressing `F12`, you can see that each movie's information is contained within the `<dd>` element. Therefore, you can retrieve all `<dd>` elements in batches, iterating through them and obtaining the information for each movie.\n\n---\n\n## ✅️️ Example Code\n\nThe following code can be directly executed.\n\nNote that a recorder object is used here. For more details, refer to [DataRecorder](http://g1879.gitee.io/datarecorder).\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom DataRecorder import Recorder\n\n# Create page object\npage = ChromiumPage()\n# Create recorder object\nrecorder = Recorder('data.csv')\n# Access web page\npage.get('https://www.maoyan.com/board/4')\n\nwhile True:\n    # Iterate through all dd elements on the page\n    for mov in page.eles('t:dd'):\n        # Get the required information\n        num = mov('t:i').text\n        score = mov('.score').text\n        title = mov('@data-act=boarditem-click').attr('title')\n        star = mov('.star').text\n        time = mov('.releasetime').text\n        # Write to the recorder\n        recorder.add_data((num, title, star, time, score))\n\n    # Get the next page button, if it exists, click it\n    btn = page('下一页', timeout=2)\n    if btn:\n        btn.click()\n        page.wait.load_start()\n    # Exit the program if it doesn't exist\n    else:\n        break\n\nrecorder.record()\n```\n\n---\n\n## ✅️️ Result\n\nThe program generates a result file data.csv with the following contents:\n\n```csv\n1,I Am Not Madame Bovary,\"Starring: Fan Bingbing, Guo Tao, Zhang Jiayi\",Release Date: 2016-11-18,9.0\n2,Uncle Drew,\"Starring: Kyrie Irving, Lil Rel Howery, Shaquille O'Neal\",Release Date: 2018-06-29,8.6\n3,Escape Plan 2: Hades,\"Starring: Sylvester Stallone, Dave Bautista, Xiaoming Huang\",Release Date: 2018-06-29,6.5\n4,Pacific Rim: Uprising,\"Starring: John Boyega, Scott Eastwood, Cailee Spaeny\",Release Date: 2018-03-23,7.2\n5,Big Fish & Begonia,\"Starring: Ji Guanlin, Pan Shulan, Su Shangqing\",Release Date: 2016-07-08,8.3\n\nFollowing contents are omitted...\n```\n\n\n"
  },
  {
    "path": "docs_en/demos/multithreading_with_tabs.md",
    "content": "🌠 Multi-threaded Operation of Tabs\n---\n\nThis example demonstrates how to use multiple threads to simultaneously control multiple tabs of a browser for web scraping.\n\n## ✅️️ Page Analysis\n\nTarget URLs:\n\n- https://gitee.com/explore/ai\n\n- https://gitee.com/explore/machine-learning\n\nBy pressing `F12`, you can see that the `class` attribute of each title element is `title project-namespace-path`, which can be obtained in batch.\n\n---\n\n## ✅️️ Encoding Idea\n\nAlthough the list of open source projects on Gitee can be scraped in 's' mode, we will be using the browser for demonstration purposes to showcase multi-tab operations.\n\nThe `get_tab()` method of `ChromiumPage` is used to retrieve objects of two tabs for operation by different threads.\n\n---\n\n## ✅️️ Example Code\n\nThe following code can be executed directly.\n\nIt should be noted that a recorder object is used here, please see [DataRecorder](http://g1879.gitee.io/datarecorder) for details.\n\n```python\nfrom threading import Thread\n\nfrom DrissionPage import ChromiumPage\nfrom DataRecorder import Recorder\n\n\ndef collect(tab, recorder, title):\n    \"\"\"Method used for web scraping\n    :param tab: ChromiumTab object\n    :param recorder: Recorder object\n    :param title: Category title\n    :return: None\n    \"\"\"\n    num = 1  # Current page number being scraped\n    while True:\n        # Traverse all title elements\n        for i in tab.eles('.title project-namespace-path'):\n            # Obtain the names of all repositories on a certain page and record them in the recorder\n            recorder.add_data((title, i.text, num))\n\n        # If there is a next page, click on it\n        btn = tab('@rel=next', timeout=2)\n        if btn:\n            btn.click(by_js=True)\n            tab.wait.load_start()\n            num += 1\n\n        # Otherwise, scraping is finished\n        else:\n            break\n\n\ndef main():\n    # Create a page object\n    page = ChromiumPage()\n    # First tab visits the URL\n    page.get('https://gitee.com/explore/ai')\n    # Get the first tab object\n    tab1 = page.get_tab()\n    # Create a new tab and visit another URL\n    tab2 = page.new_tab('https://gitee.com/explore/machine-learning')\n    # Get the second tab object\n    tab2 = page.get_tab(tab2)\n\n    # Create a recorder object\n    recorder = Recorder('data.csv')\n\n    # Use multiple threads to process multiple pages simultaneously\n    Thread(target=collect, args=(tab1, recorder, 'ai')).start()\n    Thread(target=collect, args=(tab2, recorder, 'Machine Learning')).start()\n\n\nif __name__ == '__main__':\n    main()\n```\n\n---\n\n## ✅️️ Result\n\nThe program generates a result file called `data.csv`, with the following content:\n\n```csv\nMachine Learning, MindSpore/mindspore, 1\nMachine Learning, PaddlePaddle/Paddle, 1\nMachine Learning, MindSpore/docs, 1\nMachine Learning, scruel/Notes-ML-AndrewNg, 1\nMachine Learning, MindSpore/graphengine, 1\nMachine Learning, inspur-inna/inna1.0, 1\nai, drinkjava2/人工生命, 1\nMachine Learning, MindSpore/course, 1\n\nMore content is omitted...\n```\n\n---\n\n## ✅️️ Explanation\n\nIn this example, the `page` is actually a tab object, which is equivalent to `tab1`.\n\nCreating a `tab1` object in the example is just for better readability, but it can be completely replaced by `page` in the position of `tab1`.\n\n"
  },
  {
    "path": "docs_en/demos/starbucks_pics.md",
    "content": "🌠 Download Starbucks Product Images\n---\n\nThis example demonstrates the functionality of the `download()` method.\n\n## ✅️️ Page Analysis\n\nTarget URL: https://www.starbucks.com.cn/menu/\n\nGoal: Download all product images and name them based on the product name.\n\nAs shown in the screenshot below:\n\n![](../imgs/sb1.jpg)\n\nBy inspecting the webpage using F12, we can see that each product image is contained within a `div` element with the `class` attribute set to `preview circle`. The image URL is hidden within the `style` attribute of the `div` element. The product name is located in a separate element following this `div` element.\n\nAs shown in the screenshot below:\n\n![](../imgs/sb2.jpg)\n\n---\n\n## ✅️️ Encoding Logic\n\nBased on the webpage structure, we can fetch all elements with the `class` attribute set to `preview circle`, iterate through them, and extract the image path and product name. We can then proceed to download the images.\n\nAdditionally, since the webpage structure is simple and does not use JavaScript to generate content, we can directly access it using a secure connection.\n\n---\n\n## ✅️️ Sample Code\n\n```python\nfrom DrissionPage import SessionPage\nfrom re import search\n\n# Create a session page object using secure connection\npage = SessionPage()\n# Access the target webpage\npage.get('https://www.starbucks.com.cn/menu/')\n\n# Get all elements with class attribute set to 'preview circle'\ndivs = page.eles('.preview circle')\n# Iterate through these elements\nfor div in divs:\n    # Use relative positioning to fetch the next sibling element of the current div element and retrieve its text\n    name = div.next().text\n\n    # Extract the image URL from the style attribute of the div element and concatenate it\n    img_url = div.attr('style')\n    img_url = search(r'\"(.*)\"', img_url).group(1)\n    img_url = f'https://www.starbucks.com.cn{img_url}'\n\n    # Perform the download\n    page.download(img_url, r'.\\imgs', rename=name)\n```\n\n:::tip Tips\n    You do not need to manually create the `imgs` folder as it will be automatically created by the `download()` method.\n:::\n\n---\n\n## ✅️️ Results\n\n![](../imgs/sb3.jpg)\n\nDuring program execution, the download progress is printed by default.\n\n![](../imgs/sb4.jpg)\n\nWe noticed that some product names contain `/`, which is an illegal character in file paths. This would typically require manual handling by the programmer when downloading files. However, the `download()` method in DrissionPage has built-in functionality to remove illegal characters and ensure successful file saving. Additionally, if there are conflicting filenames in the specified save path, this method automatically renames the new file by adding a serial number to avoid path conflicts. The method also returns the absolute path of the downloaded file, making it convenient for further program usage.\n\n:::tip Tips\n    When encountering conflicting filenames, the `download()` method provides three options: `skip`, `overwrite`, and `rename`.\n:::\n\n"
  },
  {
    "path": "docs_en/download/DownloadKit.md",
    "content": "# ⤵️ DownloadKit\n\nThe `DrissionPage` module comes with a built-in download tool called DownloadKit, which provides functionalities such as task management, multithreaded concurrency, large file chunking, automatic reconnection, and filename conflict handling.\n\nThe DownloadKit tool has been packaged as a separate library and its detailed usage can be found at: [DownloadKit](https://gitee.com/g1879/DownloadKit).\n\nThis document only introduces the main features of DownloadKit. For specific usage and configuration methods, please refer to the official documentation.\n\n## ✅️️ Functionality Overview\n\n### 📌 Supported Objects\n\nDownloadKit supports the following objects:\n\n- `SessionPage`\n- `ChromiumPage`\n- `WebPage`\n- `ChromiumTab`\n- `WebPageTab`\n- `ChromiumFrame`\n\n---\n\n### 📌 Downloader Features\n\n- Able to download files from specific URLs\n- Supports multi-threaded concurrent downloading of multiple files\n- Automatically splits and downloads large files using multiple threads\n- Can append data to existing files\n- Automatically creates the destination path\n- Supports file renaming during download\n- Automatically handles filename conflicts\n- Automatically removes illegal characters from paths and filenames\n- Supports POST requests\n- Supports custom connection parameters\n- Automatically retries failed tasks\n\n:::warning Note\n    DownloadKit is a wrapper around the `requests` library and does not call browser functions.\n    If the download target has specific requirements such as headers or data, they need to be manually added.\n:::\n\n---\n\n## ✅️️ Adding Tasks\n\n### 📌 Single-threaded Task\n\nThe `download()` method can be used to add a single-threaded task. This method is blocking and only uses one thread.\n\n**Example:**\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\nurl = 'https://www.baidu.com/img/flexible/logo/pc/result.png'\nsave_path = r'C:\\download'\n\nres = page.download(url, save_path)\nprint(res)\n```\n\nOutput:\n\n```shell\nurl: https://www.baidu.com/img/flexible/logo/pc/result.png\nfilename: result.png\ndestination path: C:\\download\n100% download complete C:\\download\\result.png\n\n('success', 'C:\\\\download\\\\result.png')\n```\n\n---\n\n### 📌 Concurrent Tasks\n\nUse the `download.add()` method to add concurrent tasks.\n\n**Example:**\n\n```python\nurl1 = 'https://dldir1.qq.com/qqfile/qq/TIM3.4.8/TIM3.4.8.22092.exe'\nurl2 = 'https://dldir1.qq.com/qqfile/qq/PCQQ9.7.16/QQ9.7.16.29187.exe'\nsave_path = 'files'\n\npage = SessionPage()\npage.download.add(url1, save_path)\npage.download.add(url2, save_path)\n```\n\n---\n\n### 📌 Parallel Chunked Download\n\nThe `split` parameter of the `download.add()` method can be used to enable or disable chunked downloading for large files.\n\nThe `download.set.block_size()` method can be used to set the chunk size.\n\nBy default, files larger than 50MB will be automatically downloaded in chunks.\n\n**Example:**\n\n```python\npage = SessionPage()\npage.download.set.block_size('30m')  # Set the chunk size\npage.download.add('http://xxxx/demo.zip')  # Download with default chunking\npage.download.add('http://xxxx/demo.zip', split=False)  # Download without chunking\n```\n\n---\n\n### 📌 Blocking Multi-threaded Task\n\nWhen using parallel chunked downloads, tasks can also be downloaded one by one using the `add()` method followed by `wait()`.\n\n**Example:**\n\n```python\npage = SessionPage()\npage.download.add('http://xxxx/demo.zip').wait()\npage.download.add('http://xxxx/demo.zip').wait()\n```\n\n---\n\n### 📌 Detailed Documentation\n\nThe examples above are simplified versions. For detailed usage, please refer to the [DownloadKit Adding Tasks](http://g1879.gitee.io/downloadkitdocs/usage/add_missions/) documentation.\n\n---\n\n## ✅️️ Download Settings\n\n### 📌 Global Settings\n\nThe `download.set.xxxx()` methods can be used to configure the default download behavior.\n\nThese settings include:\n\n- Save path\n- Maximum number of threads allowed\n- Enable or disable chunked downloads\n- Chunk size\n- Number of retry attempts for connection failures\n- Retry interval\n- Connection timeout\n- Filename conflict handling\n- Logging and display settings\n\n---\n\n### 📌 Individual Task Settings\n\nWhen creating a new task, the `download()` and `add()` methods' parameters can be used to set specific parameters for the current task, overriding the global settings.\n\nRefer to the previous section on adding parameters for more details.\n\n---\n\n### 📌 Detailed Documentation\n\nFor detailed configuration options, please refer to the [DownloadKit Runtime Settings](http://g1879.gitee.io/downloadkitdocs/usage/settings/) documentation.\n\n---\n\n## ✅️️ Task Management\n\n### 📌 Mission Objects\n\nThe `Mission` object is used to manage tasks and provides the following functionalities:\n\n- View task status, information, and progress\n- Save task parameters such as URL and connection parameters\n- Cancel ongoing tasks\n- Delete downloaded files\n\n---\n\n### 📌 Getting a Single Mission Object\n\nWhen using the `download.add()` method to add a task, a mission object will be returned.\n\n**Example:**\n\n```python\nmission = page.download.add('http://xxxx.pdf')\nprint(mission.id)  # Get the task ID\nprint(mission.rate)  # Print the download progress (percentage)\nprint(mission.state)  # Print the task status\nprint(mission.info)  # Print the task information\nprint(mission.result)  # Print the task result\n```\n\nIn addition to getting the object when adding a task, you can also use the `download.get_mission()` method. As seen in the previous example, the mission object has a `id` attribute. Pass the mission ID to this method to get the corresponding mission object.\n\n**Example:**\n\n```python\nmission_id = mission.id\nmission = page.download.get_mission(mission_id)\n```\n\n---\n\n### 📌 Getting All Mission Objects\n\nUsing the `download.missions` attribute of the page object, you can get all the download tasks. This attribute returns a `dict` that contains all the download tasks, with the task object's `id` as the key.\n\n```python\npage.download_set.save_path(r'D:\\download')\npage.download('http://xxxxx/xxx1.pdf')\npage.download('http://xxxxx/xxx1.pdf')\nprint(page.download.missions)\n```\n\n**Output:**\n\n```\n{\n    1: <Mission 1 D:\\download\\xxx1.pdf xxx1.pdf>\n    2: <Mission 2 D:\\download\\xxx1_1.pdf xxx1_1.pdf>\n    ...\n}\n```\n\n---\n\n### 📌 Get failed missions\n\nYou can use the `download.get_failed_missions()` method to get a list of failed download missions.\n\n```python\npage.download_set.save_path(r'D:\\download')\npage.download('http://xxxxx/xxx1.pdf')\npage.download('http://xxxxx/xxx1.pdf')\nprint(page.download.get_failed_missions()\n```\n\n**Output:**\n\n```\n[\n    <Mission 1 Status code: 404 None>,\n    <Mission 2 Status code: 404 None>\n    ...\n]\n```\n\n:::tip Tips\n    After getting the failed mission object, you can retrieve the task content from its `data` attribute for logging purposes or for retrying at a later time.\n:::\n\n---\n\n### 📌 Detailed documentation\n\nFor detailed configuration options, please refer to: [DownloadKit Mission Management](http://g1879.gitee.io/downloadkitdocs/usage/misssions/)\n\n"
  },
  {
    "path": "docs_en/download/browser.md",
    "content": "⤵️ Browser Download\n---\n\nThis section introduces the functionality for setting up browser download tasks.\n\n## ✅️ Overview\n\n### 📌 Functionality\n\nDrissionPage provides the following functionality for controlling browser download tasks:\n\n- Each tab object can independently set the file save path.\n- The file name can be specified before downloading to achieve file renaming.\n- The handling method for existing files with the same name can be configured.\n- The download progress of tasks can be obtained.\n- Waiting for the download tasks to finish.\n- Cancelling tasks.\n- Intercepting download tasks and obtaining their information.\n\n---\n\n### 📌 Supported Objects\n\nThe following objects support the above functions:\n\n- `ChromiumPage`\n- `WebPage`\n- `ChromiumTab`\n- `WebPageTab`\n- `ChromiumFrame`\n\n---\n\n### 📌 Required Concepts\n\nBefore we start, let's clarify some concepts.\n\nPage objects include both Page objects and Tab objects.\n\nThe Page object (`ChromiumPage` and `WebPage`) controls a tab and serves as the manager for all tabs, i.e., the browser.\n\nEach Tab object (`ChromiumTab` and `WebPageTab`) controls a tab.\n\nA tab can be controlled by multiple page objects.\n\nThey have the following characteristics in terms of download settings:\n\n- Newly created Tab objects inherit the path settings of the Page object by default.\n- Newly created Page or Tab objects use the `'rename'` method to handle file name conflicts by default.\n- For the same tab, multiple page objects share a set of download settings. If one object modifies the settings, the others will be affected.\n- Download tasks triggered by `<iframe>` elements also use the download settings of the Tab object they belong to.\n- If download settings are applied to `<iframe>` elements, the settings of the Tab object it belongs to will be modified.\n\n:::tip Tips\n    Some download tasks create a temporary tab, trigger downloads, and then immediately close. These tasks use the download settings of the Page object. That is, if a tab is not controlled by a Tab or Page object, the download tasks it triggers will be set according to the Page's settings, including save path and renaming.\n:::\n\n---\n\n## ⚠️ Cautions\n\n### 📌 Remember to Wait for Task Completion\n\nDue to technical reasons, the program can only rename the file after the download is complete. Before that, the file name is the temporary task ID.\n\nTherefore, it is necessary to wait for the download to complete before the file name can be correctly named. This is the case whether or not a file name is specified.\n\n**Example:**\n\n```python\npage = ChromiumPage()\npage('#button').click()  # Click the download button\npage.wait.download_begin()  # Wait for the download to start\npage.wait.all_downloads_done()  # Wait for all tasks to finish\n```\n\n---\n\n### 📌 Recommended to Set a Temporary Path When Operating with Multiple Tabs\n\nBecause the program needs to download tasks to a specified location and then move them to the target path, if the program involves multiple Tab objects triggering download tasks, it is best to set a download path for the Page object.\n\nEven if each Tab object sets its own path.\n\n**Example:**\n\n```python\npage = ChromiumPage()\npage.set.download_path('tmp')  # Set the overall path\n\ntab1 = page.get_tab(page.tabs[1])\ntab1.set.download_path('path1')\n\ntab2 = page.get_tab(page.tabs[2])\ntab2.set.download_path('path2')\n```\n\n:::tip Tips\n    In this example, `page` itself is a tab, so there are 3 tabs here.\n    If `get_tab()` is used to get the tab of `page` for settings, it will override the settings of `page`.\n:::\n\n---\n\n### 📌 Enabling Download Management Functionality\n\nThe download management functionality introduced in this section is not enabled by default. At this time, triggering download tasks and manual operations have no difference.\n\nThe management functionality will be enabled when the download path is set in the configuration or when the `set.download_path()` method is called.\n\n---\n\n## ✅️ Set Download Path\n\n### 📌 Set Overall Download Path\n\nUse the `set.download_path()` method of the Page object to set the download path. If not set, the default is to download to the current path of the program.\n\nAfter setting the download path of the Page object, all subsequently created Tab objects will use this address. If the previously created Tab objects have not set their own path, they will also use the new path.\n\nWhen using this method, the download paths of built-in `DownloadKit` objects (if any) will also change at the same time.\n\n|  Parameter Name  |       Type       | Default Value | Description |\n|:---------------:|:---------------:|:-------------:|-------------|\n|      `path`     | `str`<br/>`Path` |   Required    | Download path |\n\n**Returns:** `None`\n\n**Example:**\n\n```python\npage = ChromiumPage()\npage.set.download_path(r'C:\\tmp')\n```\n\n---\n\n### 📌 Set Tab Download Path\n\nThe method is used in the same way as setting the Page, but only takes effect in the current Tab object.\n\n**Example:**\n\n```python\npage = ChromiumPage()\ntab = page.get_tab(page.tabs[1])  # Create a Tab object\ntab.set.download_path(r'C:\\tmp1')  # Set the Tab download path\n```\n\n---\n\n## ✅️ Set File Name\n\nThe `download_file_name()` method can be used to set the file name before downloading, achieving file renaming.\n\nThe set file name can be without an extension, and the program will automatically add the extension based on the downloaded file.\n\nIf the set file name contains a `'.'` and the extension is different from the network file, the program will use the extension of the network file.\n\nIf you want to modify the file extension, simply set the `suffix` parameter.\n\nAfter each trigger for downloading, this setting will be cleared.\n\n| Parameter Name |   Type  | Default Value | Description            |\n|:--------------:|:-------:|:-------------:|------------------------|\n|     `name`     | `str`   |     `None`    | The file name          |\n|    `suffix`    | `str`   |     `None`    | The file extension, pass `''` to remove the extension |\n\n**Returns:** None\n\n**Example:**\n\n```python\npage = ChromiumPage()\npage.set.download_file_name('new_file')\npage('t:a').click()  # Click a link that triggers the download\npage.wait.download_begin()\npage.wait.all_download_done()  # Remember to wait for the task to trigger and end\n```\n\n---\n\n## ✅️ Waiting\n\n### 📌 Waiting for Download to Begin\n\nAfter clicking on a download link, the download does not trigger instantly and needs to be waited for in order to catch it.\n\nUse the `wait.download_begin()` method to wait for the download to begin.\n\nGenerally, for download tasks triggered by a tab, use the Tab object to wait, and for downloads triggered by uncontrolled tabs, use the Page object to wait.\n\nWhen the `cancel_it` parameter is set to True, the task will be canceled when it is caught, so that the returned download information can be used for other purposes.\n\n|  Parameter Name |   Type  | Default Value | Description                |\n|:--------------:|:-------:|:-------------:|----------------------------|\n|    `timeout`   | `float` |     `None`    | Timeout period, `None` for the default waiting time |\n|   `cancel_it`  | `bool`  |    `False`    | Whether to cancel the task when caught           |\n\n|   Return Type   | Description                    |\n|:--------------:|--------------------------------|\n| `DownloadMission` | Returns the download task object when waiting is successful |\n|     `False`    | Returns `False` when waiting fails |\n\n**Example:**\n\n```python\npage = ChromiumPage()\npage('t:a').click()  # Click a link that triggers the download\npage.wait.download_begin()\n```\n\n---\n\n### 📌 Waiting for All Download Tasks to Finish\n\nUse the `page.wait.all_downloads_done()` method of the Page object to wait for all download tasks in the browser to finish.\n\n|   Parameter Name    |   Type  | Default Value | Description            |\n|:-------------------:|:-------:|:-------------:|------------------------|\n|      `timeout`      | `float` |     `None`    | Timeout period, `None` for unlimited waiting |\n| `cancel_if_timeout` | `bool`  |    `False`    | Whether to cancel unfinished tasks if timeout     |\n\n|  Return Type  | Description               |\n|:------------:|---------------------------|\n|    `bool`    | Whether it successfully waited |\n\n---\n\n### 📌 Waiting for All Download Tasks of Tab to Finish\n\nUse the `tab.wait.downloads_done()` method of the Tab object to wait for the download tasks triggered by the Tab object to finish.\n\nThis method should be used for waiting for the tasks triggered by the Page object, as the Page object itself also controls a tab.\n\n|   Parameter Name    |   Type  | Default Value | Description            |\n|:-------------------:|:-------:|:-------------:|------------------------|\n|      `timeout`      | `float` |     `None`    | Timeout period, `None` for unlimited waiting |\n| `cancel_if_timeout` | `bool`  |    `False`    | Whether to cancel unfinished tasks if timeout     |\n\n|  Return Type  | Description               |\n|:------------:|---------------------------|\n|    `bool`    | Whether it successfully waited |\n\n---\n\n## ✅️ Intercepting Download Tasks\n\nThe `wait.download_begin()` method has a `cancel_it` parameter that, when set to True, cancels the download task.\n\nAt this time, you can use the task information returned by this method for the next operation, such as downloading using the `download()` method.\n\n**Example:**\n\n```python\npage = ChromiumPage()\npage('t:a').click()\ndata = page.wait.download_begin(cancel_it=True)\npage.download(data['url'])\n```\n\n---\n\n## ✅️ Handling Same-Name Files\n\nWhen encountering files with the same name during downloading, you can choose from three options: automatically rename, overwrite, or skip.\n\nUse `set.when_download_file_exists('xxxx')` to set the handling method.\n\nWhere `xxxx` can be `'rename'`, `'overwrite'`, or `'skip'`.\n\nYou can also use the first letter of each option: `'r'`, `'o'`, or `'s'`.\n\n### 📌 Automatically Rename\n\nMethod: `page.set.when_download_file_exists('rename')`\n\nThis method will automatically rename the new file by adding a serial number at the end.\n\nFor example, if there is already a file named 'abc.zip' in the save path, and another 'abc.zip' is downloaded, the new file will be automatically renamed to 'abc_1.zip'.\n\nSubsequent downloads will be named 'abc_2.zip', and so on.\n\n---\n\n### 📌 Overwrite Existing Files\n\nMethod: `page.set.when_download_file_exists('overwrite')`\n\nThis method will replace the existing file with the newly downloaded one.\n\n---\n\n### 📌 Skip\n\nMethod: `page.set.when_download_file_exists('skip')`\n\nThis method will cancel the download task when a file with the same name is found.\n\n---\n\n## ✅️ Task Management\n\nThe `wait.download_begin()` method returns a `DownloadMission` object for managing browser download tasks.\n\n### 📌 Get Task Information\n\nYou can get the task status, progress, save path, file name, and other information.\n\n| Attribute Name | Type | Description |\n|:--------------:|:----:|-------------|\n| `url` | `str` | Returns the URL of the task |\n| `tab_id` | `str` | Id of the Tab object that triggered the task |\n| `id` | `str` | Task ID |\n| `path` | `str` | Save path, excluding the file name |\n| `name` | `str` | File name |\n| `state` | `str` | Task state, 'running', 'done', 'canceled', 'skipped' |\n| `total_bytes` | `int` | Total number of bytes |\n| `received_bytes` | `int` | Number of bytes received |\n| `final_path` | `str` or `None` | The final complete path, generated only after the task is completed |\n\n**Example:**\n\nPrint the progress of the task in real-time.\n\n```python\nmission = page.wait.download_begin()\n\nwhile not mission.is_done:\n    print(f'\\r{mission.rate}%', end='')\n```\n\n---\n\n### 📌 Wait for Task Completion\n\nBy using the `wait()` method of the `DownloadMission` object, you can wait for the task to complete.\n\n| Parameter Name | Type | Default Value | Description |\n|:--------------:|:----:|:-------------:|-------------|\n| `show` | `bool` | `True` | Whether to print download information |\n| `timeout` | `float` | `None` | Timeout duration, `None` for infinite waiting |\n| `cancel_if_timeout` | `bool` | `False` | Whether to cancel the task if timeout |\n\n| Return Type | Description |\n|:-----------:|-------------|\n| `str` | Returns the final saved path when the download is complete |\n| `False` | Returns `False` if timeout or canceled |\n\n---\n\n### 📌 Cancel Task\n\nBy using the `cancel()` method of the `DownloadMission` object, you can cancel the task.\n\nWhen this method is called, the downloaded file will be deleted, even if the task has been completed.\n\n"
  },
  {
    "path": "docs_en/download/introduction.md",
    "content": "⤵️ Overview\n---\n\nDrissionPage provides powerful file download management capabilities.\n\nIt can initiate download tasks actively and also manage download tasks triggered by the browser.\n\n## ✅️️ `download()` method\n\nThis method can actively initiate download tasks and provide features such as task management, multi-threading, large file chunking, automatic reconnection, and file name conflict handling.\n\nThis method is supported by page objects, tab objects, and `<iframe>` element objects.\n\n:::tip Tips\n    When using this method, the program automatically synchronizes the cookies information of the object on which the method is called.\n:::\n\n**Example:**\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\npage.download('https://dldir1.qq.com/qqfile/qq/TIM3.4.8/TIM3.4.8.22092.exe')\n```\n\n---\n\n## ✅️️ Browser download tasks\n\nBrowser page objects, tab objects, and `<iframe>` objects can control browser download tasks.\n\nThe following features are included:\n\n- Each tab object can independently specify a download URL\n- It is possible to specify a renamed file name before downloading\n- Intercept download tasks and obtain task information\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.set.download_path('save_path')  # Set the file save path\npage.set.download_file_name('file_name')  # Set the renamed file name\npage('t:a').click()  # Click on a link that triggers a download\npage.wait.download_begin()  # Wait for the download to begin\npage.wait.downloads_done()  # Wait for the download to finish\n```\n\n"
  },
  {
    "path": "docs_en/features/features_demos/compare_with_requests.md",
    "content": "⭐ Comparison with requests\n---\n\nThe following code achieves the same functionality, comparing the amount of code for each:\n\n🔸 Get element content\n\n```python\nurl = 'https://baike.baidu.com/item/python'\n\n# Using requests:\nfrom lxml import etree\nheaders = {'User-Agent':'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36'}\nresponse = requests.get(url, headers = headers)\nhtml = etree.HTML(response.text)\nelement = html.xpath('//h1')[0]\ntitle = element.text\n\n# Using DrissionPage:\npage = WebPage('s')\npage.get(url)\ntitle = page('tag:h1').text\n```\n\n:::tip Tips\n    DrissionPage comes with default headers\n:::\n\n🔸 Download file\n\n```python\nurl = 'https://www.baidu.com/img/flexible/logo/pc/result.png'\nsave_path = r'C:\\download'\n\n# Using requests:\nr = requests.get(url)\nwith open(f'{save_path}\\\\img.png', 'wb') as fd:\n   for chunk in r.iter_content():\n       fd.write(chunk)\n\n# Using DrissionPage:\npage.download(url, save_path, 'img')  # Supports renaming, handles filename conflicts\n```\n\n"
  },
  {
    "path": "docs_en/features/features_demos/compare_with_selenium.md",
    "content": "⭐ Comparison with selenium\n---\n\nThe following code achieves the same functionality, comparing the code size of both:\n\n🔸 Using explicit wait to find the first element containing some text.\n\n```python\n# Using selenium:\nelement = WebDriverWait(driver).until(ec.presence_of_element_located((By.XPATH, '//*[contains(text(), \"some text\")]')))\n\n# Using DrissionPage:\nelement = page('some text')\n```\n\n🔸 Switching to a tab\n\n```python\n# Using selenium:\ndriver.switch_to.window(driver.window_handles[0])\n\n# Using DrissionPage:\ntab = page.get_tab(1)\n```\n\n🔸 Selecting from a dropdown list by text\n\n```python\n# Using selenium:\nfrom selenium.webdriver.support.select import Select\nselect_element = Select(element)\nselect_element.select_by_visible_text('text')\n\n# Using DrissionPage:\nelement.select('text')\n```\n\n🔸 Dragging an element\n\n```python\n# Using selenium:\nActionChains(driver).drag_and_drop(ele1, ele2).perform()\n\n# Using DrissionPage:\nele1.drag_to(ele2)\n```\n\n🔸 Scrolling to the bottom of the window (keeping the horizontal scrollbar unchanged)\n\n```python\n# Using selenium:\ndriver.execute_script(\"window.scrollTo(document.documentElement.scrollLeft, document.body.scrollHeight);\")\n\n# Using DrissionPage:\npage.scroll.to_bottom()\n```\n\n🔸 Setting headless mode\n\n```python\n# Using selenium:\noptions = webdriver.ChromeOptions()\noptions.add_argument(\"--headless\")\n\n# Using DrissionPage:\nset_headless(True)\n```\n\n🔸 Getting the content of a pseudo-element\n\n```python\n# Using selenium:\ntext = webdriver.execute_script('return window.getComputedStyle(arguments[0], \"::after\").getPropertyValue(\"content\");', element)\n\n# Using DrissionPage:\ntext = element.pseudo.after\n```\n\n🔸 Getting shadow-root\n\nThe latest version of selenium can directly get the shadow-root, but the generated ShadowRoot object has very limited features.\n\n```python\n# Using selenium:\nshadow_element = webdriver.execute_script('return arguments[0].shadowRoot', element)\n\n# Using DrissionPage:\nshadow_element = element.shadow_root\n```\n\n🔸 Directly getting attributes or text nodes using xpath (returning text)\n\n```python\n# Using selenium:\nVery complex\n\n# Using DrissionPage:\nclass_name = element('xpath://div[@id=\"div_id\"]/@class')\ntext = element('xpath://div[@id=\"div_id\"]/text()[2]')\n```\n\n"
  },
  {
    "path": "docs_en/features/features_demos/download_file.md",
    "content": "# Download Files\n\nDrissionPage comes with a convenient downloader that allows you to easily download files with just one line of code.\n\n```python\nfrom DrissionPage import WebPage\n\nurl = 'https://www.baidu.com/img/flexible/logo/pc/result.png'\nsave_path = r'C:\\download'\n\npage = WebPage('s')\npage.download(url, save_path)\n```\n\n"
  },
  {
    "path": "docs_en/features/features_demos/get_element_attributes.md",
    "content": "⭐ Get Element Attribute\n---\n\n```python\n# Continuing from previous code\nfoot = page.ele('#footer-left')  # Find element by id\nfirst_col = foot.ele('css:>div')  # Find element within the subordinates using css selector (the first one)\nlnk = first_col.ele('text:命令学')  # Find element using text content\ntext = lnk.text  # Get element text\nhref = lnk.attr('href')  # Get element attribute value\n\nprint(text, href, '\\n')\n\n# Concise chaining mode\ntext = page('@id:footer-left')('css:>div')('text:命令学').text\nprint(text)\n```\n\n**Output:**\n\n```shell\nLearn Git Command https://oschina.gitee.io/learn-git-branching/\n\nLearn Git Command\n```\n\n"
  },
  {
    "path": "docs_en/features/features_demos/switch_mode.md",
    "content": "⭐ Mode Switch\n---\n\nLog in to the website using a browser and switch to reading the webpage with requests. They will share login information.\n\n```python\nfrom DrissionPage import WebPage\nfrom time import sleep\n\n# Create a page object with the default d mode\npage = WebPage()  \n# Visit the personal center page (not logged in, redirect to the login page)\npage.get('https://gitee.com/profile')  \n\n# Enter the account password to log in\npage.ele('@id:user_login').input('your_user_name')  \npage.ele('@id:user_password').input('your_password\\n')\npage.wait.load_start()\n\n# Switch to the s mode\npage.change_mode()  \n# Output of session mode after login\nprint('Logged in title:', page.title, '\\n')  \n```\n\n**Output:**\n\n```shell\nLogged in title: Personal Information - Gitee.com\n```\n\n"
  },
  {
    "path": "docs_en/features/intimate_design.md",
    "content": "💖 Intimate Design\n---\n\nHere is an introduction to some of the user-friendly designs built into this library.\n\n## ✅️️ Universal Waiting\n\nIn an unstable network environment, programs often need to wait for a while before they can continue running. Waiting too little can cause errors in the program, while waiting too much can waste time. To solve these problems, this library has built-in timeout functionality in many sections that require waiting and can be flexibly set at any time, significantly reducing program complexity.\n\n- Built-in waiting for element lookup. The waiting time can be set individually for each element lookup. Some pages may display prompt messages irregularly. If waiting all the time would be too time-consuming, a very short timeout can be set independently to avoid waste.\n\n- Waiting for dropdown list options. Many dropdown lists are loaded using JavaScript. When selecting a dropdown list, this library will automatically wait for the list items to appear.\n\n- Waiting for pop-up windows. Sometimes the expected alert may not appear immediately. This library can also set a waiting time for handling pop-up messages.\n\n- Waiting for element state changes. The `wait.ele()` method can wait for elements to appear, disappear, or be deleted.\n\n- Waiting for page loading or completion. This not only saves time but also greatly improves program stability.\n\n- The clicking function also has built-in waiting. If an element is blocked, it can be continuously retried for clicking.\n\n- Setting page loading time limit and loading strategy. Sometimes it is not necessary to load complete page resources, and the loading strategy can be set according to actual needs.\n\n---\n\n## ✅️️ Automatic Retrying of Connections\n\nWhen accessing a website, network instability may cause connection failures. This library has a feature that automatically retries connections. When a webpage connection fails, it will be retried 3 times by default. Of course, the number of retries and the interval can also be manually set.\n\n```python\npage.get('xxxxxx', retry=5, interval=3)  # Retry 5 times with a 3-second interval when an error occurs\n```\n\n---\n\n## ✅️️ Minimalistic Locator Syntax\n\nThis library has developed a concise and efficient syntax for locating elements, supporting chain operations and relative positioning. Compared to the cumbersome syntax of selenium, it is extremely convenient.\n\nEvery time a search is performed, there is a built-in waiting period, and the timeout for each search can be set independently.\n\nLet's compare the use of timed searches:\n\n```python\n# Using selenium:\nelement = WebDriverWait(driver, 10).until(ec.presence_of_element_located((By.XPATH, '//*[contains(text(), \"some text\")]')))\n\n# Using DrissionPage:\nelement = page('some text', timeout=10)\n```\n\n---\n\n## ✅️️ No Need to Switch In and Out, Clear Logic\n\nThose who have used selenium know that selenium can only operate one tab or `<iframe>` element at a time. To operate other tabs or `<iframe>` elements, the `switch_to()` method needs to be used to switch, and after the operation, it needs to be switched back. If there are multiple levels of `<iframe>`, it needs to be switched in layer by layer, which is quite cumbersome.\n\nDrissionPage does not require these cumbersome operations. It treats each tab and `<iframe>` as independent objects that can be operated concurrently, and can directly cross multiple levels of `<iframe>` to obtain elements inside and process them directly, which is very convenient.\n\nLet's compare getting an element with an id of `'div1'` in a 2-level `<iframe>`:\n\n```python\n# Using selenium:\ndriver.switch_to.frame(0)\ndriver.switch_to.frame(0)\nele = driver.find_element(By.ID, 'div1')\ndriver.switch_to.default_content()\n\n# Using DrissionPage:\nele = page('#div1')\n```\n\nConcurrently operating multiple tabs, selenium does not have this feature:\n\n```python\ntab1 = page.get_tab(page.tabs[0])\ntab2 = page.get_tab(page.tabs[1])\n\ntab1.get('https://www.baidu.com')\ntab2.get('https://www.163.com')\n```\n\n---\n\n## ✅️️ Fully Integrated Convenience Features\n\nMany operation methods integrate common functions, such as the `click()` method, which has a built-in `by_js` parameter that allows direct clicking using JavaScript without the need for separate JavaScript statements.\n\n---\n\n## ✅️️ Powerful Downloading Functionality\n\nDrissionPage has a built-in download tool that can download large files in multiple threads and blocks. It can also directly read cache data to save images without the need for controlling the page to perform a save operation.\n\n```python\nimg = page('tag:img')\nimg.save('img.png')  # Save the image directly to a folder\n```\n\n---\n\n## ✅️️ More Convenient Features\n\n- The entire webpage can be captured as a screenshot, including areas outside the viewport.\n\n- Each time the program runs, the already opened browser can be reused without starting from scratch.\n\n- The 's' mode automatically corrects encoding when accessing webpages, without the need for manual settings.\n\n- The 's' mode automatically fills in the `Host` and `Referer` properties based on the current domain when connecting.\n\n- The download tool supports various ways to handle filename conflicts, automatically create target paths, and retry broken links.\n\n- `MixPage` can automatically download the appropriate version of chromedriver, eliminating the hassle of configuration.\n\n- Supports directly getting the content of `after` and `before` pseudo-elements.\n\n- Can intercept the file selection box and enter the path directly when uploading a file, without relying on a GUI or searching for `<input>` elements to input.\n\n"
  },
  {
    "path": "docs_en/get_elements/cheat_sheet.md",
    "content": "🔦 Syntax Cheat Sheet\n---\n\n## ✅️ Syntax for Positioning\n\n### 📌 Basic Usage\n\nThe following syntax only appears at the beginning of a statement.\n\n|   Syntax    |    Exact Match    |   Fuzzy Match   |    Match at Start    |    Match at End    |                  Description                 |\n|:-------:|:-----------------:|:---------------:|:--------------------:|:-----------------:|:--------------------------------------------:|\n| `@property`  |    `@property=`     |    `@property:`    |     `@property^`     |     `@property$`     |             Find elements by property              |\n| `@!property` |   `@!property=`    |   `@!property:`   |    `@!property^`    |    `@!property$`    |      Find elements where property does not match     |\n| `text`  |      `text=`      | `text:` or none |     `text^`     |     `text$`     |             Find elements by text              |\n| `@text()` |    `@text()=`     |    `@text():`   |   `text()^`   |   `text()$`   | Replace `text` with `text()` when used with `@` or `@@`, commonly used for multiple condition matching |\n|  `tag`  |   `tag=` or `tag:`   |       None      |       None       |       None       |                Find elements of certain type                |\n| `xpath` | `xpath=` or `xpath:` |       None      |       None       |       None       |             Find elements using xpath              |\n|  `css`  |   `css=` or `css:`   |       None      |       None       |       None       |          Find elements using css selector          |\n\n---\n\n### 📌 Combination Usage\n\n|         Syntax         |         Description          |\n|:----------------------:|:----------------------------:|\n|   `@@property1@@property2`   | Find elements that meet multiple conditions simultaneously |\n|   `@@property1@!property2`   | Use multiple property matching in conjunction with negation |\n|   `@|property1@|property2` | Find elements that meet any of multiple conditions |\n|   `tag:xx@property`    | Use tag in conjunction with property matching  |\n|   `tag:xx@@property1@@property2` | Use tag in conjunction with multiple property matching |\n|   `tag:xx@|property1@|property2`      | Use tag in conjunction with multiple property matching |\n|   `tab:@@text()=text@@property`   | Use tab in conjunction with text and property matching  |\n\n---\n\n### 📌 Simplified Syntax\n\n|    Original Syntax    |  Simplified Syntax   |    Exact Match    |   Fuzzy Match   |    Match at Start    |    Match at End    |       Note      |\n|:---------------------:|:-------------------:|:-----------------:|:--------------:|:--------------------:|:-----------------:|:--------------:|\n|   `@id`   |   `#`   | `#` or `#=`  |   `#:`   |   `#^`   |   `#$`   | Simplified syntax can only be used alone |\n| `@class`  |   `.`   | `.` or `.=`  |   `.:`   |   `.^`   |   `.$`   | Simplified syntax can only be used alone |\n|   `tag`   |   `t`   | `t:` or `t=` |    None     |    None     |    None     |    Can only be used at the beginning    |\n|  `text`   |  `tx`   |   `tx=`   | `tx:` or none |  `tx^`   |  `tx$`   | Fuzzy match text is used when there are no tags |\n| `@text()` | `@tx()` | `@tx()=`  | `@tx():` | `@tx()^` | `@tx()$` |              |\n|  `xpath`  |   `x`   | `x:` or `x=` | None     |    None     |    None |    Can only be used alone    |\n|   `css`   |   `c`   | `c:` or `c=` | None     |    None     |    None |    Can only be used alone    |\n\n---\n\n## ✅️ Relative Positioning\n\n|      Method      |          Description           |\n|:------------:|:---------------------------:|\n|  `parent()`  |     Find the parent element of the current element      |\n|  `child()`   |     Find a direct child node of the current element     |\n| `children()` |  Find all direct child nodes of the current element that match the condition  |\n|   `next()`   |    Find the first sibling node after the current element that matches the condition    |\n|  `nexts()`   |  Find all sibling nodes after the current element that match the condition  |\n|   `prev()`   |    Find the first sibling node before the current element that matches the condition    |\n|  `prevs()`   |  Find all sibling nodes before the current element that match the condition  |\n|  `after()`   |    Find the first node after the current element in the document that matches the condition   |\n|  `afters()`  | Find all nodes after the current element in the document that match the condition  |\n|  `before()`  |    Find the first node before the current element in the document that matches the condition   |\n| `befores()`  | Find all nodes before the current element in the document that match the condition  |\n\n## ✅️ Miscellaneous\n\n| Method | Simplified Writing | Description | Note |\n|:------:|:------------------:|:-----------:|:----:|\n| `get_frame()` | N/A | Find an `<iframe>` element on the page | Only available for page objects |\n| `shadow_root` | `sr` | Get the shadow root object within the current element | Only available for element objects |\n\n"
  },
  {
    "path": "docs_en/get_elements/introduction.md",
    "content": "🔦 Overview\n---\n\nThis chapter introduces how to get element objects.\n\nPositioning elements is a critical skill in automation. Although you can directly copy the absolute path in the developer tools, there are several drawbacks to doing so:\n\n- The code is lengthy and not readable.\n- Dynamic pages can cause elements to become invalid.\n- Relative positioning cannot be used.\n- If there are any changes to the web page or temporary elements appear, the code becomes less error-tolerant.\n- It is not possible to search for elements across `<iframe>`.\n\nTherefore, the author strongly recommends against using the right-click copy element path.\n\nThis library provides a set of concise and easy-to-use syntax for quickly locating elements, with built-in waiting functionality and support for chained searches, reducing code complexity.\n\nIt also supports css selectors, xpath, and the loc tuple native to Selenium.\n\nThere are roughly three methods for locating elements:\n\n- Finding child elements within a page or element\n- Relative positioning based on DOM structure\n- Relative positioning based on page layout\n\n## ✅️️ Usage\n\nAll page objects and element objects can search for elements within themselves, with element objects also able to locate other elements relative to themselves.\n\nPage objects include: `SessionPage`, `ChromiumPage`, `ChromiumTab`, `ChromiumFrame`, `WebPage`, `WebPageTab`\n\nElement objects include: `SessionElement`, `ChromiumElement`, `ShadowRoot`\n\n### 📌 Searching within a page\n\nUse the `ele()` and `eles()` methods of page objects to retrieve specified element objects within a page.\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\npage.get('https://www.baidu.com')\nele = page.ele('#su')\n```\n\n---\n\n### 📌 Searching within an element\n\nUse the `ele()`, `eles()`, `child()`, and `children()` methods of element objects to retrieve specified descendant element objects within an element.\n\n```python\nele1 = page.ele('#s_fm')\nele2 = ele1.ele('#su')\n\nson = ele1.child('tag:div')  # Get the first direct child div element\nsons = ele1.children('tag:div')  # Get all direct child div elements\n```\n\n---\n\n### 📌 Chained searching\n\nBecause objects themselves can search for objects, chained operations are supported, and the above examples can be merged as follows:\n\n```python\nele = page.ele('#s_fm').ele('#su')\n```\n\n---\n\n### 📌 Relative searching\n\nElement objects can execute relative searches based on themselves.\n\n```python\nele = page.ele('#su')\n\nparent = ele.parent(2)  # Get the second level parent element of the ele element\nbrother = ele.next('tag:a')  # Get the first a element after the ele element\nafter = ele.after('tag:div')  # Get the first div element in the document after the ele element\n```\n\n---\n\n### 📌 shadow root\n\nUse the `shadow_root` attribute of browser element objects to retrieve the `ShadowRoot` object under that element.\n\n```python\nshadow = page.ele('#ele1').shadow_root\n```\n\nSearching within a shadow root element follows the same methods as a regular element.\n\n```python\nshadow = page.ele('#ele1').shadow_root\nele = shadow.ele('#ele2')\n```\n\n---\n\n## ✅️️ Examples\n\nFirst, let's look at some examples. The usage will be explained in detail later.\n\n### 📌 Simple examples\n\nSuppose we have a page like this, which will be used for the examples in this chapter:\n\n```html\n<html>\n<body>\n<div id=\"one\">\n    <p class=\"p_cls\" name=\"row1\">First line</p>\n    <p class=\"p_cls\" name=\"row2\">Second line</p>\n    <p class=\"p_cls\">Third line</p>\n</div>\n<div id=\"two\">\n    Second div\n</div>\n</body>\n</html>\n```\n\nWe can use the page object to retrieve elements from it:\n\n```python\n# Get the element with id one\ndiv1 = page.ele('#one')\n\n# Get the element with name attribute row1\np1 = page.ele('@name=row1')\n\n# Get the element that contains the text \"Second div\"\ndiv2 = page.ele('Second div')\n\n# Get all div elements\ndiv_list = page.eles('tag:div')\n```\n\nWe can also retrieve an element and search for elements within it or around it:\n\n```python\n# Retrieve an element div1\ndiv1 = page.ele('#one')\n\n# Search for all p elements within div1\np_list = div1.eles('tag:p')\n\n# Get the next element after div1\ndiv2 = div1.next()\n```\n\n---\n\n### 📌 Practical examples\n\nCopy this code and run it directly to see the results.\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\npage.get('https://gitee.com/explore')\n\n# Get the ul element that contains the text \"Recommended Projects\"\nul_ele = page.ele('tag:ul@@text():Recommended Projects')  \n\n# Get all a elements under this ul element\ntitles = ul_ele.eles('tag:a')  \n\n# Iterate over the list and print the text of each a element\nfor i in titles:  \n    print(i.text)\n```\n\n**Output:**\n\n```shell\nRecommended Projects\nCutting-edge Technologies\nSmart Hardware\nIOT/Internet of Things/Edge Computing\nIn-car Applications\n...\n```\n\n"
  },
  {
    "path": "docs_en/get_elements/more.md",
    "content": "🔦 More Usages\n---\n\nThis section introduces the ways to access element objects in a browser page.\n\nThe methods for obtaining element objects using `SessionPage` are the same as those for obtaining element objects using `SessionPage`, but there are more features in this section that will be introduced.\n\n## ✅️️ Finding Elements in Static Mode\n\nStatic elements refer to the `SessionElement` element objects in the s mode, which are made up of pure text, so they can handle tasks very quickly.  \nFor complex web pages, when collecting data from hundreds or thousands of elements, converting them into static elements can significantly improve the speed by several orders of magnitude.  \nThe author once used the same logic to convert elements into static elements only, which accelerated a web page that took 30 seconds to complete to complete in a few seconds.  \nWe can even convert the entire web page into static elements and then extract information from it.  \nOf course, these elements cannot be interacted with, such as clicking.  \nYou can use the `s_ele()` method to convert the found dynamic elements into static elements, or to get a static copy of an element or the page itself.\n\n### 📌 `s_ele()`\n\nBoth the page object and element object have this method, which is used to find the first matching element and get its static version.\n\nThe `s_ele()` method has slightly different parameter names for page objects and element objects, but the usage is the same.\n\n|     Parameter Name    |                             Type                             | Default Value | Description                                                                                                                                                       |\n|:------------------------:|:--------------------------------------------------------:|:-----------------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `locator` (for element object) |            `str`<br/>`Tuple[str, str]`             |     required       | The locator information of the element, can be a query string or `loc` tuple.                                                                                          |\n| `locator` (for page object)    | `str`<br/>`ChromiumElement`<br/>`Tuple[str, str]` |     required       | The locator information of the element, can be a query string, `loc` tuple, or a `ChromiumElement` object. |\n\n|  Return Type  |                                    Description                                    |\n|:----------------:|----------------------------------------------------------------------------------------|\n| `SessionElement` |   Returns the static version of the first element object that meets the conditions.   |\n|  `NoneElement`  |   Returns the `NoneElement` object if no element that meets the conditions is found within the specified time limit. |\n\n:::warning Note\n    The `s_ele()` method of a page object or element object cannot search for elements within an `<iframe>`, and the static version of a page object cannot search for elements within an `<iframe>` either.\n    To use the static version of an element within an `<iframe>`, you can first obtain the element and then convert it into a static version. However, if you use the `ChromiumFrame` object, you can directly use `s_ele()` to find elements in it, which will be explained in later chapters.\n:::\n\n:::tip Tips\n    The `SessionElement` version obtained from a `ChromiumElement` element can still use relative positioning methods to locate ancestor or sibling elements.\n:::\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\n\n# Find an element in the page and get its static version\nele1 = page.s_ele('search text')\n\n# Find an element in a dynamic element and get its static version\nele = page.ele('search text')\nele2 = ele.s_ele()\n\n# Get the static copy of a page element (no parameter passed in)\ns_page = page.s_ele()\n\n# Get the static copy of a dynamic element\ns_ele = ele.s_ele()\n\n# Search for descendant elements in the static copy (since it is already a static element, the result of using ele() to find elements is also static)\nele3 = s_page.ele('search text')\nele4 = s_ele.ele('search text')\n```\n\n---\n\n### 📌 `s_eles()`\n\nThis method is similar to `s_ele()`, but it returns a list of all matching elements or a list of attribute values.\n\n|  Parameter Name   |             Type             | Default Value | Description                             |\n|:---------------------:|:--------------------------:|----------------:|------------------------------------------|\n| `locator` | `str`<br/>`Tuple[str, str]` |   required       | The locator information of the element, can be a query string or `loc` tuple. |\n\n|     Return Type      |                               Description                                |\n|:------------------------:|----------------------------------------------------------------------------------|\n| `List[SessionElement]` |   Returns a list of `SessionElement` versions of all found elements.  |\n\n**Example:**\n\n```python\nfrom DrissionPage import WebPage\n\npage = WebPage()\nfor ele in page.s_eles('search text'):\n    print(ele.text)\n```\n\n---\n\n## ✅️ Accessing the Currently Focused Element\n\nUse the `active_ele` attribute to get the currently focused element on the page.\n\n```python\nele = page.active_ele\n```\n\n---\n\n## ✅️️ `<iframe>` Elements\n\n### 📌 Finding `<iframe>` Elements\n\n`<iframe>` and `<frame>` elements can also be found using `ele()`, and the generated objects are `ChromiumFrame` objects instead of `ChromiumElement` objects.\n\nHowever, it is not recommended to use `ele()` to get `<iframe>` elements, because the IDE cannot correctly prompt subsequent operations.\n\nIt is recommended to use the `get_frame()` method of the Page object.\n\nThe usage is the same as `ele()`, you can use locators to search for the elements. It also adds the ability to locate elements using index, id, and name attributes.\n\n**Example:**\n\n```python\niframe = page.get_frame(1)  # Get the first iframe element on the page\niframe = page.get_frame('#theFrame')  # Get the iframe element object with the id \"theFrame\"\n```\n\n---\n\n### 📌 Finding Elements in a Different Level under the Page\n\n\n\nUnlike selenium, this library can directly search for elements within a same-origin `<iframe>`.\nAnd regardless of the hierarchy, it can directly access elements within multiple layers of `<iframe>`. This greatly simplifies the program logic and makes it more convenient to use.\n\nAssuming there is a two-level `<iframe>` in the page, and there is an element `<div id='abc'></div>`, it can be retrieved in the following way:\n\n```python\npage = ChromiumPage()\nele = page('#abc')\n```\n\nThere is no need to switch in and out before and after retrieval, and it does not affect the retrieval of other elements on the page.\n\nIf using selenium, it needs to be written like this:\n\n```python\ndriver = webdriver.Chrome()\ndriver.switch_to.frame(0)\ndriver.switch_to.frame(0)\nele = driver.find_element(By.ID, 'abc')\ndriver.switch_to.default_content()\n```\n\nObviously, it is more cumbersome, and it is not possible to operate on elements outside the `<iframe>` after switching into the `<iframe>`.\n\n:::warning Attention\n    - Cross-level search is only supported by the page object, the element object cannot directly search for elements inside the iframe.\n    - Cross-level search can only be used for `<iframe>` with the same domain as the main frame, for different domains, please use the following method.\n:::\n\n---\n\n### 📌 Searching within an iframe element\n\nThis library treats `<iframe>` as a special element/page object, which allows the simultaneous operation of multiple `<iframe>` without the need to switch back and forth.\n\nFor `<iframe>` with a different domain name, we cannot directly search for elements inside it through the page, but we can first get the `<iframe>` element, and then search within it. Of course, this operation can also be done for non-cross-domain `<iframe>` elements.\n\nAssuming the id of an `<iframe>` is `'iframe1'`, and we want to find an element with an id of `'abc'` within it:\n\n```python\npage = ChromiumPage()\niframe = page('#iframe1')\nele = iframe('#abc')\n```\n\nThis `<iframe>` element is a page object, so it can continue to search for elements across `<iframe>` (relative to this `<iframe>` without cross-domain).\n\n---\n\n## ✅️️ `ShadowRoot`\n\nThis library treats shadow-root as an element object, referred to as the `ShadowRoot` object.\nThis object can search for sub-elements and perform relative positioning within the DOM, just like ordinary elements.\nWhen positioning relative to a `ShadowRoot` object, it is regarded as the first object within its parent object, and the rest of the positioning logic is the same as ordinary objects.\n\nThe `ShadowRoot` object can be obtained using the `shadow_root` property of the element object.\n\n:::warning Attention\n    - If there are other `ShadowRoot` elements among the sub-elements of the `ShadowRoot` element, these sub-`ShadowRoot` elements cannot be directly located using positioning statements. Only the parent element can be located first, and then the `shadow_root` property can be used to locate them.\n:::\n\n```python\n# Get a shadow-root element\nsr_ele = page.ele('#app').shadow_root\n\n# Find sub-elements under this element\nele1 = sr_ele.ele('tag:div')\n\n# Get other elements through relative positioning\nele1 = sr_ele.parent(2)\nele1 = sr_ele.next('tag:div', 1)\nele1 = sr_ele.after('tag:div', 1)\neles = sr_ele.nexts('tag:div')\n\n# Locate shadow-root elements in sub-elements\nsr_ele2 = sr_ele.ele('tag:div').shadow_root\n```\n\nSince shadow-root cannot perform cross-level search, chained operations are very common. Therefore, a shorthand notation `sr` is designed, which has the same function as `shadow_root`, both of which are used to obtain the `ShadowRoot` within an element.\n\n**Example of chained operations for multiple levels of shadow-root:**\n\nThe following code prints the first page of the browser history, which is obtained through multiple levels of shadow-root.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('chrome://history/')\n\nitems = page('#history-app').sr('#history').sr.eles('t:history-item')\nfor i in items:\n    print(i.sr('#item-container').text.replace('\\n', ''))\n```\n\n---\n\n## ✅️️ Waiting\n\nDue to factors such as network and the uncertainty of JS execution time, it is often necessary to wait for elements to be loaded into the DOM before they can be used.\n\nAll element search operations in the browser have built-in waiting functionality. The waiting time defaults to the `timeout` attribute of the page where the element is located (default is 10 seconds), and can also be set separately each time the search is performed. The waiting time set separately does not change the original setting of the page.\n\n```python\nfrom DrissionPage import ChromiumPage\n\n# Set the timeout for searching elements to 15 seconds when initializing the page\npage = ChromiumPage(timeout=15)\n# Set the timeout for searching elements to 5 seconds\npage.set.timeouts(5)\n\n# Use the page timeout to search for elements (5 seconds)\nele1 = page.ele('search text')\n# Set an independent waiting time for this search (1 second)\nele1 = page.ele('search text', timeout=1)\n# Search for descendant elements, using the page timeout (5 seconds)\nele2 = ele1.ele('search text')\n# Search for descendant elements, using the separately set timeout (1 second)\nele2 = ele1.ele('some text', timeout=1)  \n```\n\n"
  },
  {
    "path": "docs_en/get_elements/not_found.md",
    "content": "# 🔦 Element Not Found\n\n## ✅️ Default Behavior\n\nBy default, when an element is not found, an `NoneElement` object is returned instead of immediately throwing an exception.\n\nThis object evaluates to `False` when evaluated in an `if` statement, and calling its methods will throw an `ElementNotFoundError` exception.\n\nThis allows you to use an `if` statement to check if an element is found, or use a `try` block to catch the exception.\n\nWhen multiple elements are searched for but not found, an empty `list` is returned.\n\n**Example using `if` statement:**\n\n```python\nele = page.ele('xxxxxxx')\n\n# Check if the element is found\nif ele:\n    print('Element found.')\n\nif not ele:\n    print('Element not found.')\n```\n\n**Example using `try` block:**\n\n```python\ntry:\n    ele.click()\nexcept ElementNotFoundError:\n    print('Element not found.')\n```\n\n---\n\n## ✅️ Immediate Exception Throwing\n\nIf you want to immediately throw an exception when an element is not found, you can use the following method to configure it.\n\nThis setting is globally applicable and only needs to be set once at the beginning of the project.\n\nWhen multiple elements are searched for but not found, an empty `list` is still returned.\n\nSetting the global variable:\n\n```python\nfrom DrissionPage.common import Settings\n\nSettings.raise_when_ele_not_found = True\n```\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom DrissionPage.common import Settings\n\nSettings.raise_when_ele_not_found = True\n\npage = ChromiumPage(timeout=1)\npage.get('https://www.baidu.com')\nele = page('#abcd')  # ('#abcd') element does not exist\n```\n\nOutput:\n\n```console\nDrissionPage.errors.ElementNotFoundError: \nElement not found.\nmethod: ele()\nargs: {'locator': '#abcd'}\n```\n\n---\n\n## ✅️ Set Default Return Value\n\nIf you want to retrieve an attribute after finding an element, but this element may not exist, or if one of the chained searches fails, you can set the value that is returned when the search fails instead of throwing an exception. This can simplify some scraping logic.\n\nUse the `set.NoneElement_value()` method of the browser page object to set this value.\n\n| Parameter |  Type  | Default | Description                  |\n|:---------:|:------:|:-------:|------------------------------|\n|  `value`  | `Any`  |  `None` | The value to be returned     |\n| `on_off`  | `bool` |  `True` | Indicate whether to enable it |\n\n**Returns:** `None`\n\n**Example:**\n\nFor example, when iterating through multiple objects in a list on a page, but some of them may be missing certain child elements, you can write:\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.set.NoneElement_value('Not Found')\nfor li in page.eles('t:li'):\n    name = li('.name').text\n    age = li('.age').text\n    phone = li('.phone').text\n```\n\nThis way, if a child element does not exist, instead of throwing an exception, it will return the string `'Not Found'`.\n\n"
  },
  {
    "path": "docs_en/get_elements/simplify.md",
    "content": "🔦 Simplified Syntax\n---\n\nTo further simplify the code, the syntax for positioning can be expressed in a simplified form, making the statements shorter and chain operations clearer.\n\n## ✅️ Simplified Locator Syntax\n\n- There are simplified forms for positioning syntax.\n- Both pages and element objects have implemented the `__call__()` method, so `page.ele('...')` can be simplified to `page('...')`.\n- The search methods all support chain operations.\n\nExample:\n\n```python\n# Find elements with tag div\nele = page.ele('tag:div')  # Original syntax\nele = page('t:div')  # Simplified syntax\n\n# Find elements with xpath\nele = page.ele('xpath://xxxxx')  # Original syntax\nele = page('x://xxxxx')  # Simplified syntax\n\n# Find elements with text 'something'\nele = page.ele('text=something')  # Original syntax\nele = page('tx=something')  # Simplified syntax\n```\n\nSimplified syntax mapping table\n\n| Original Syntax | Simplified Syntax |         Description         |\n|:--------------:|:----------------:|:-----------------------------:|\n|     `@id`      |       `#`        |  Represents the id attribute. The simplified syntax only takes effect when the statement is used at the beginning and separately.   |\n|    `@class`    |       `.`        | Represents the class attribute. The simplified syntax only takes effect when the statement is used at the beginning and separately. |\n|     `text`     |       `tx`       |           Match by text          |\n|   `@text()`   |    `@tx()`      |        Match elements by text using @ or @@       |\n|      `tag`     |       `t`        |           Match by element tag           |\n|     `xpath`    |       `x`        |       Find elements using xpath        |\n|      `css`     |       `c`        |    Find elements using css selector    |\n\n---\n\n## ✅️ Simplified Shadow Root\n\nWhen obtaining the shadow root element of an element, `ele.shadow_root` is commonly used.\n\nSince this property is often used for a large number of chain operations, its name is too long and affects readability. Therefore, it can be simplified as `ele.sr`.\n\n**Example:**\n\n```python\ntxt = ele.sr('t:div').text\n```\n\n---\n\n## ✅️ Simplified Relative Positioning Parameters\n\nWhen using relative positioning, sometimes it is necessary to get a specific element after the current element, regardless of the type of the element. It is usually written as `ele.next(index=2)`.\n\nHowever, there is a simplified syntax that can be directly written as `ele.next(2)`.\n\nWhen the first argument `filter_loc` accepts a number, it will automatically treat it as an index, replacing the `index` parameter. Therefore, the writing can be slightly simplified.\n\n**Example:**\n\n```python\nele2 = ele1.parent(2)\nele2 = ele1.next(2)('tx=xxxxx')\nele2 = ele1.before(2)\n# and so on\n```\n\n"
  },
  {
    "path": "docs_en/get_elements/usage.md",
    "content": "## 🔦 Basic Usage\n\n## ✅️️ Methods for finding elements\n\n### 📌 `ele()`\n\nBoth the page object and the element object have this method, used to find an element that matches a specific condition.\n\nThe `ele()` method parameters have slightly different names for page objects and element objects, but the usage is the same.\n\nThe methods for obtaining elements in `SessionPage` and `ChromiumPage` are the same, but the former returns a `SessionElement` object, while the latter returns a `ChromiumElement` object.\n\n|      Parameter Name       |              Type               | Default | Description                                                  |\n| :-----------------------: | :-----------------------------: | :-----: | ------------------------------------------------------------ |\n| `loc_or_str` (element object) |       `str` `Tuple[str, str]`      | Required | The locating information of the element. Can be a query string or a loc tuple |\n| `loc_or_ele` (page object) | `str` `SessionElement` `Tuple[str, str]` | Required | The locating information of the element. Can be a query string, loc tuple, or a `SessionElement` object |\n|          `index`          |              `int`               |   `1`   | The index of the element to retrieve. Starts from `1`, negative numbers can be used to count from the end |\n|         `timeout`         |             `float`              |  `None` | The timeout for waiting for the element to appear. If `None`, the value set in the page object will be used, and it is invalid in `SessionPage` |\n\n|     Return Type      | Description                                                  |\n| :-----------------: | ------------------------------------------------------------ |\n| `SessionElement`  | The first element object that matches the condition found by `SessionPage` or `SessionElement` |\n| `ChromiumElement` | The first element object that matches the condition found by the browser page object or element object |\n|  `ChromiumFrame`  | Returns `ChromiumFrame` when the result is a frame element, but this tip is not included in the IDE |\n|   `NoneElement`   | Returns when no element that matches the condition is found |\n\nNotes:\n\n-   The loc tuple refers to the Selenium locator, for example: `(By.ID, 'XXXXX')`. The same below.\n-   `ele('xxxx', index=2)` is the same as `eles('xxxx')[1]`, but the former is much faster.\n\n**Example:**\n\n```python\nfrom DrissionPage import SessionPage\n\npage = SessionPage()\n\n# Find an element within the page\nele1 = page.ele('#one')\n\n# Find a descendant element within ele1\nele2 = ele1.ele('The second row')\n\n```\n\n___\n\n### 📌 `eles()`\n\nThis method is similar to `ele()`, but it returns a list of all matching elements.\n\nBoth the page object and the element object can call this method.\n\n`eles()` returns a regular list, and indexing is needed for chained operations, such as `page.eles('...')[0].ele('...')`.\n\n|  Parameter Name  |         Type          | Default | Description                                                  |\n| :--------------: | :-------------------: | :-----: | ------------------------------------------------------------ |\n| `loc_or_str` | `str` `Tuple[str, str]` | Required | The locating information of the element. Can be a query string or a loc tuple |\n|    `timeout`    |        `float`        |  `None` | The timeout for waiting for the element to appear. If `None`, the value set in the page object will be used, and it is invalid in `SessionPage` |\n\n|          Return Type           | Description                                                    |\n| :---------------------------: | --------------------------------------------------------------- |\n|    `List[SessionElement]`    | A list of all elements found by `SessionPage` or `SessionElement` |\n| `List[ChromiumElement, ChromiumFrame]` | A list of all elements found by the browser page object or element object |\n\n**Example:**\n\n```python\n# Get all p elements within the page\np_eles = page.eles('tag:p')\n\n# Get all p elements within ele1\np_eles = ele1.eles('tag:p')\n\n# Print the text of the first p element\nprint(p_eles[0])\n```\n\n___\n\n## ✅️️ Matching Modes\n\nThe matching mode refers to the way in which the matching conditions are applied in a query and there are four types: exact matching, fuzzy matching, matching at the beginning, and matching at the end.\n\n### 📌 Exact Matching `=`\n\nRepresents exact matching, matches the text or attribute that exactly matches the specified value.\n\n```python\n# Get the element with name attribute 'row1'\nele = page.ele('@name=row1')\n```\n\n___\n\n### 📌 Fuzzy Matching `:`\n\nRepresents fuzzy matching, matches the text or attribute that contains the specified string.\n\n```python\n# Get the element with name attribute containing 'row1'\nele = page.ele('@name:row1')\n```\n\n___\n\n### 📌 Matching at the Beginning `^`\n\nRepresents matching at the beginning, matches the text or attribute that begins with the specified string.\n\n```python\n# Get the element with name attribute starting with 'row1'\nele = page.ele('@name^ro')\n```\n\n___\n\n### 📌 Matching at the End `$`\n\nRepresents matching at the end, matches the text or attribute that ends with the specified string.\n\n```python\n# Get the element with name attribute ending with 'w1'\nele = page.ele('@name$w1')\n```\n\n___\n\n## ✅️️ Query Syntax\n\nThe keyword is placed at the leftmost side of the query statement to indicate which method to use to search for elements. It only takes effect when used at the beginning of the statement and used alone.\n\n## \n\n```python\n# Find the element with the id attribute equal to \"one\" on the page\nele1 = page.ele('#one')\n\n# Find the element within ele1 that has an id attribute containing the text \"ne\"\nele2 = ele1.ele('#:ne')\n```\n\n___\n\n### 📌 Class selector `.`\n\nRepresents the `class` attribute and is only effective when used at the beginning of the statement and on its own. It can be used in conjunction with matching patterns.\n\n```python\n# Find the element with the class attribute equal to \"p_cls\"\nele2 = ele1.ele('.p_cls')\n\n# Find the element with the class attribute starting with \"_cls\"\nele2 = ele1.ele('.^_cls')\n```\n\nWhen using only `.`, it defaults to exact matching of the `class` attribute of the element. If an element has multiple class names, the complete value of the `class` attribute must be specified (the order of class names should also be unchanged). If you need to match only one of the multiple class names, you can use the fuzzy matching operator `:`.\n\n```python\n# Exact matching of the element with the class attribute equal to \"p_cls1 p_cls2\"\nele2 = ele1.ele('.p_cls1 p_cls2')\n\n# Fuzzy matching of the element with the class attribute containing the class name 'p_cls2'\nele2 = ele1.ele('.:p_cls2')\n```\n\nIf you still need a more complex matching method, please use the multiple attribute matching operator.\n\n___\n\n### 📌 Single attribute selector `@`\n\nRepresents a single attribute and matches only one attribute.\n\nThe `@` keyword has only one simple functionality, which is to match the content after `@` and not parse the string afterwards. Therefore, even if the following string also contains `@` or `@@`, it will be treated as content to be matched. So for multiple attribute matching, including all attributes including the first attribute, they must all start with `@@`.\n\nNote:\n\nIf the attribute includes special characters (such as `@`), this method cannot match it correctly and needs to use the CSS selector method. Special characters need to be escaped with `\\`.\n\n```python\n# Find the element with the name attribute equal to \"row1\"\nele2 = ele1.ele('@name=row1')\n\n# Find the element with the name attribute containing the text \"row\"\nele2 = ele1.ele('@name:row')\n\n# Find the element with the name attribute starting with \"row\"\nele2 = ele1.ele('@name^row')\n\n# Find elements with a name attribute\nele2 = ele1.ele('@name')\n\n# Find elements with no attributes\nele2 = ele1.ele('@')\n\n# Find the element with the email attribute equal to \"abc@def.com\", even if there are multiple @, it will not be repeated\nele2 = ele1.ele('@email=abc@def.com')\n\n# Example of attribute with special characters, match elements with the attribute abc@def equal to v\nele2 = ele1.ele('css:div[abc\\@def=\"v\"]')\n```\n\n___\n\n### 📌 Multiple attribute and selector `@@`\n\nUsed when matching elements that meet multiple conditions simultaneously, each condition is preceded by `@@`.\n\nNote:\n\n-   When matching text or attributes with `@@`, `@|`, or `@!`, multiple attribute matching cannot be used. Xpath needs to be used instead.\n-   If the attribute includes special characters (such as `@`), this method cannot match it correctly and needs to use the CSS selector method. Special characters need to be escaped with `\\`.\n\n```python\n# Find elements with the name attribute equal to \"row1\" and the class attribute containing the text \"cls\"\nele2 = ele1.ele('@@name=row1@@class:cls')\n```\n\n`@@` can be used in combination with the `tag` introduced below:\n\n```python\nele = page.ele('tag:div@@class=p_cls@@name=row1')\n```\n\n___\n\n### 📌 Multiple attribute or selector `@|`\n\nUsed when matching elements that meet any of multiple conditions, each condition is preceded by `@|`.\n\nThe usage is the same as `@@`, and the precautions are the same as `@@`.\n\n```python\n# Find elements with the id attribute equal to \"one\" or the id attribute equal to \"two\"\nele2 = ele1.ele('@|id=one@|id=two')\n```\n\n`@|` can be used in combination with the `tag` introduced below:\n\n```python\nele = page.ele('tag:div@|class=p_cls@|name=row1')\n```\n\n___\n\n### 📌 Attribute negation selector `@!`\n\nUsed to negate a condition, can be used in conjunction with `@@` or `@|`, or used alone.\n\nWhen used in combination with `@@` or `@|`, the relationship between `@@` or `@|` determines whether it is \"and\" or \"or\".\n\n**Example:**\n\n```python\n# Match elements with arg1 equal to \"abc\" and arg2 not equal to \"def\"\npage.ele('@@arg1=abc@!arg2=def')\n\n# Match div elements with arg1 equal to \"abc\" or arg2 not equal to \"def\"\npage.ele('t:div@|arg1=abc@!arg2=def')\n\n# Match elements with arg1 not equal to \"abc\"\npage.ele('@!arg1=abc')\n\n# Match elements with no arg1 attribute\npage.ele('@!arg1')\n```\n\n___\n\n## \n\n### 📌 Text Matching Operator `text`\n\nThe text to be matched. If the query string does not start with any keywords, it indicates a fuzzy search based on the input text.  \nIf there are multiple direct text nodes within an element, when performing an exact search, it can match the concatenation of all text nodes into a string, and when performing a fuzzy search, it can match each text node individually.\n\nWhen there are no matching operators specified, it defaults to matching text.\n\n```python\n# Find the element with text \"第二行\"\nele2 = ele1.ele('text=第二行')\n\n# Find the element with text containing \"第二\"\nele2 = ele1.ele('text:第二')\n\n# Equivalent to the previous line\nele2 = ele1.ele('第二')  \n```\n\nTips\n\nIf the text to be searched contains `text:`, it can be written as follows, where the first `text:` is a keyword and the second one is the content to be searched:\n\n```python\nele2 = page.ele('text:text:')\n```\n\n___\n\n### 📌 Text Matching Operator `text()`\n\nThe text keyword used when searching for attributes, must be used with `@` or `@@`.\n\n```python\n# Find the element with text \"第二行\"\nele2 = ele1.ele('@text()=第二行')\n\n# Find the element with text containing \"第二行\"\nele2 = ele1.ele('@text():二行')\n\n# Find the element with text starting with \"第二\" and class attribute \"p_cls\"\nele2 = ele1.ele('@@text()^第二@@class=p_cls')\n\n# Find the element with text \"二行\" and no attributes (because the first @@ is empty)\nele2 = ele1.ele('@@@@text():二行')\n\n# Find the element with direct child text containing the string \"二行\"\nele = page.ele('@text():二行')\n```\n\n___\n\n### 📌 Tips for `@@text()`\n\nIt is worth mentioning that `text()` combined with `@@` or `@|` can achieve a very convenient search method.\n\nIn web pages, elements and text are often interspersed, for example:\n\n```python\n\n<li class=\"explore-categories__item\">\n    <a href=\"/explore/new-tech\" class=\"\">\n        <i class=\"explore\"></i>\n        前沿技术\n    </a>\n</li>\n<li class=\"explore-categories__item\">\n    <a href=\"/explore/program-develop\" class=\"\">\n        <i class=\"explore\"></i>\n        程序开发\n    </a>\n</li>\n```\n\nIn the example above, to find the `<a>` element that contains the text `'前沿技术'`, you can write:\n\n```python\nele = page.ele('text:前沿技术')\n# Or\nele = page.ele('@text():前沿技术')\n```\n\nBoth of these methods can find the element containing direct text content.\n\nBut if you want to find the `<li>` element using text, you can't find it because the text is not its direct content.\n\nYou can write it like this:\n\n```python\nele = page.ele('tag:li@@text():前沿技术')\n```\n\nThe difference between `@@text()` and `@text()` is that the former can search for all text within the element, not just direct text, so it can achieve some very flexible searches.\n\nNote\n\nWhen using `@@` or `@|`, do not use `text()` as the sole query condition, otherwise it will locate the highest-level element in the entire document.\n\n❌ Incorrect usage:\n\n```python\nele = page.ele('@@text():前沿技术')\nele = page.ele('@|text():前沿技术@|text():程序开发')\n```\n\n⭕ Correct usage:\n\n```python\nele = page.ele('tag:li@|text():前沿技术@|text():程序开发')\n```\n\n___\n\n### 📌 Tag Matching Operator `tag`\n\nIndicates the tag of the element. It only takes effect when it is used at the beginning of the statement and used alone, and it can be used with `@`, `@@`, or `@|`. `tag:` has the same effect as `tag=`, there is no `tag^` and `tag$` syntax.\n\n```python\n# Locate the div element\nele2 = ele1.ele('tag:div')\n\n# Locate the p element with class attribute \"p_cls\"\nele2 = ele1.ele('tag:p@class=p_cls')\n\n# Locate the p element with text \"第二行\"\nele2 = ele1.ele('tag:p@text()=第二行')\n\n# Locate the p element with class attribute \"p_cls\" and text \"第二行\"\nele2 = ele1.ele('tag:p@@class=p_cls@@text()=第二行')\n\n# Locate the p element with class attribute \"p_cls\" or text \"第二行\"\nele2 = ele1.ele('tag:p@|class=p_cls@|text()=第二行')\n\n## 翻译 private_upload\\default_user\\2024-01-24-19-46-31\\get_elements_基本用法_DrissionPage.md.part-3.md\n\n# Find p elements containing the string \"二行\" in direct text nodes\nele2 = ele1.ele('tag:p@text():二行')\n\n# Find p elements containing the string \"二行\" in internal text nodes\nele2 = ele1.ele('tag:p@@text():二行')\n```\nNote\n\nThere is a difference between `tag:div@text():text` and `tag:div@@text():text`. The former only searches in the direct text nodes of the `div` element, while the latter searches in the entire internal content of the `div` element.\n\n---\n\n### 📌 css selector matching symbol `css`\n\nRepresents finding elements using css selector. `css:` and `css=` have the same effect, and there is no syntax like `css^` and `css$`.\n\n```python\n# Find div elements\nele2 = ele1.ele('css:.div')\n\n# Find div child elements, this syntax is unique to this library and not natively supported\nele2 = ele1.ele('css:>div')\n```\n\n---\n\n### 📌 xpath matching symbol `xpath`\n\nRepresents finding elements using xpath. `xpath:` and `xpath=` have the same effect, and there is no syntax like `xpath^` and `xpath$`.\n\nAdditionally, the `ele()` method of the element object supports the complete xpath syntax, such as being able to directly retrieve the attributes (as strings) of elements using xpath.\n\n```python\n# Find the first div element in descendants\nele2 = ele1.ele('xpath:.//div')\n\n# Same as the previous line, when finding descendants of an element, the `.` in front of `//` can be omitted\nele2 = ele1.ele('xpath://div')\n\n# Use xpath to retrieve the class attribute of a div element (this functionality is not available in the page source)\nele_class_str = ele1.ele('xpath://div/@class')\n```\n\nTips\n\nWhen finding descendants of an element, the selenium native code requires a `.` to be added in front of xpath; otherwise, it will search throughout the entire page. The author considers this design to be unnecessary, as we should only search for elements within the element that has already been found. Therefore, when using xpath to find elements under an element, the `.` in front of `//` or `/` can be omitted.\n\n---\n\n### 📌 selenium loc tuple\n\nThe find methods can directly accept the native selenium locators for locating elements, making it easier for project migration.\n\n```python\nfrom DrissionPage.common import By\n\n# Find the element with id \"one\"\nloc1 = (By.ID, 'one')\nele = page.ele(loc1)\n\n# Find using xpath\nloc2 = (By.XPATH, '//p[@class=\"p_cls\"]')\nele = page.ele(loc2)\n```\n\n---\n\n## ✅️️ Relative positioning\n\nThe following methods allow you to obtain direct child nodes, sibling nodes, ancestor elements, and adjacent nodes in the DOM based on a specific element.\n\nTips\n\nHere, we are referring to \"nodes\" and not just \"elements,\" as relative positioning can retrieve nodes other than elements, including text and comment nodes.\n\nNote\n\nIf the element is within an `<iframe>`, the relative positioning cannot go beyond the `<iframe>` document.\n\n### 📌 Get parent element\n\n🔸 `parent()`\n\nThis method retrieves a certain level of parent element of the current element, either by specifying a filter or a level.\n\n|  Parameter Name  |               Type                | Default Value | Description                                             |\n| :--------------: | :------------------------------: | :-----------: | ------------------------------------------------------- |\n| `level_or_loc`   | `int` `str` `Tuple[str, str]`    |     `1`       | Level of the parent element, starting from `1`, or use a locator to filter in the ancestor elements |\n|     `index`      |              `int`               |     `1`       | When `level_or_loc` is a locator, use this parameter to select which result to choose, counting from the current element upwards. This parameter is not valid when `level_or_loc` is a number. |\n\n| Return Type | Description |\n| --- | --- |\n| `SessionElement` | The found element object |\n| `NoneElement` | Returns `NoneElement` when no result is obtained |\n\n**Example:**\n\n```python\n# Get the second level parent element of ele1\nele2 = ele1.parent(2)\n\n# Get the element with id \"id1\" in the parent element of ele1\nele2 = ele1.parent('#id1')\n```\n\n---\n\n### 📌 Get direct child nodes\n\n🔸 `child()`\n\nThis method returns a direct child node of the current element, with the option to specify a filter and the index of the result.\n\n|   Parameter Name   |              Type               | Default Value | Description                                               |\n| :----------------: | :----------------------------: | :-----------: | --------------------------------------------------------- |\n|  `filter_loc`      | `str` `Tuple[str, str]` `int`  |     `''`      | Query syntax used to filter nodes, when it is of type `int`, the `index` parameter is ignored |\n|     `index`        |              `int`             |     `1`       | Which result to retrieve from the query results, starting from `1`. A negative number can be inputted to count from the end |\n|    `timeout`       |             `float`            |    `None`     | No actual effect                                          |\n|   `ele_only`       |             `bool`             |    `True`     | Whether to only find elements. When set to `False`, it includes text and comment nodes in the search range |\n\n| Return Type | Description |\n| --- | --- |\n| `SessionElement` | The found element object |\n| `str` | Returns a string when a non-element node is obtained |\n| `NoneElement` | Returns `NoneElement` when no result is obtained |\n\n## \n\n🔸 `children()`\n\nThis method returns a list of all the direct child nodes of the current element that meet the specified conditions. Query syntax can be used for filtering.\n\n|   Parameter   |         Type          | Default | Description                                               |\n| :-----------: | :------------------: | :-----: | --------------------------------------------------------- |\n| `filter_loc`  | `str` `Tuple[str, str]` |  `''`   | Query syntax used for filtering the nodes                 |\n|   `timeout`   |        `float`       | `None`  | Not applicable                                            |\n|  `ele_only`   |       `bool`        |  `True` | If `False`, the search includes text and comment nodes     |\n\n| Return Type | Description                                            |\n| ----------- | ------------------------------------------------------ |\n| `List[SessionElement, str]` | List of results                                         |\n\n___\n\n### 📌 Get the Next Sibling Node\n\n🔸 `next()`\n\nThis method returns a specific sibling node after the current element, based on the specified conditions and index.\n\n|   Parameter   |           Type           | Default | Description                                               |\n| :-----------: | :----------------------: | :-----: | --------------------------------------------------------- |\n| `filter_loc`  | `str` `Tuple[str, str]` `int` |  `''`   | Query syntax used for filtering the nodes. If `int` type is used, the `index` parameter is ignored |\n|   `index`     |           `int`          |   `1`   | Index of the query result, starting from `1`. Negative numbers can be used to indicate reverse indexing |\n|  `timeout`    |         `float`          | `None`  | Not applicable                                            |\n|  `ele_only`   |          `bool`          |  `True` | If `False`, the search includes text and comment nodes     |\n\n| Return Type | Description                                            |\n| ----------- | ------------------------------------------------------ |\n| `SessionElement` | Found element object                                  |\n| `str` | Returns a string when a non-element node is obtained    |\n| `NoneElement` | Returns `NoneElement` when no result is obtained        |\n\n**Example:**\n\n```python\n# Get the first sibling element after ele1\nele2 = ele1.next()\n\n# Get the third sibling element after ele1\nele2 = ele1.next(3)\n\n# Get the third sibling div element after ele1\nele2 = ele1.next('tag:div', 3)\n\n# Get the text of the first text node after ele1\ntxt = ele1.next('xpath:text()', 1)\n```\n\n___\n\n🔸 `nexts()`\n\nThis method returns a list of all the sibling nodes after the current element that meet the specified conditions. Query syntax can be used for filtering.\n\n|   Parameter   |         Type          | Default | Description                                               |\n| :-----------: | :------------------: | :-----: | --------------------------------------------------------- |\n| `filter_loc`  | `str` `Tuple[str, str]` |  `''`   | Query syntax used for filtering the nodes                 |\n|   `timeout`   |        `float`       | `None`  | Not applicable                                            |\n|  `ele_only`   |       `bool`        |  `True` | If `False`, the search includes text and comment nodes     |\n\n| Return Type | Description                                            |\n| ----------- | ------------------------------------------------------ |\n| `List[SessionElement, str]` | List of results                                         |\n\n**Example:**\n\n```python\n# Get all the sibling elements after ele1\neles = ele1.nexts()\n\n# Get all the sibling div elements after ele1\ndivs = ele1.nexts('tag:div')\n\n# Get all the text nodes after ele1\ntxts = ele1.nexts('xpath:text()')\n```\n\n___\n\n### 📌 Get the Previous Sibling Node\n\n🔸 `prev()`\n\nThis method returns a specific sibling node before the current element, based on the specified conditions and index.\n\n|   Parameter   |           Type           | Default | Description                                               |\n| :-----------: | :----------------------: | :-----: | --------------------------------------------------------- |\n| `filter_loc`  | `str` `Tuple[str, str]` `int` |  `''`   | Query syntax used for filtering the nodes. If `int` type is used, the `index` parameter is ignored |\n|   `index`     |           `int`          |   `1`   | Index of the query result, starting from `1`. Negative numbers can be used to indicate reverse indexing |\n|  `timeout`    |         `float`          | `None`  | Not applicable                                            |\n|  `ele_only`   |          `bool`          |  `True` | If `False`, the search includes text and comment nodes     |\n\n| Return Type | Description                                            |\n| ----------- | ------------------------------------------------------ |\n| `SessionElement` | Found element object                                  |\n| `str` | Returns a string when a non-element node is obtained    |\n| `NoneElement` | Returns `NoneElement` when no result is obtained        |\n\n**Example:**\n\n```python\n# Get the first sibling element before ele1\nele2 = ele1.prev()\n\n# Get the third sibling element before ele1\nele2 = ele1.prev(3)\n\n# Get the third sibling div element before ele1\nele2 = ele1.prev(3, 'tag:div')\n\n# Get the text of the first text node before ele1\ntxt = ele1.prev(1, 'xpath:text()')\n```\n\n___\n\n🔸 `prevs()`\n\nThis method returns a list of all the sibling nodes before the current element that meet the specified conditions. Query syntax can be used for filtering.\n\n## \n\n| Parameter Name |        Type        | Default Value | Description                                           |\n| :------------: | :----------------: | :-----------: | ----------------------------------------------------- |\n|  `filter_loc`  | `str` `Tuple[str, str]` |     `''`    | Query syntax used for filtering nodes                  |\n|   `timeout`    |      `float`       |    `None`     | No actual effect                                       |\n|  `ele_only`    |       `bool`       |    `True`     | Whether to search for elements only, including text and comment nodes when set to `False` |\n\n|  Return Type  | Description |\n| :-----------: | ----------- |\n| `List[SessionElement, str]` | List of results |\n\n**Example:**\n\n```python\n# Get all preceding sibling elements of ele1\neles = ele1.prevs()\n\n# Get all preceding sibling div elements of ele1\ndivs = ele1.prevs('tag:div')\n```\n\n___\n\n### 📌 Find Nodes in Subsequent Document\n\n🔸 `after()`\n\nThis method returns a certain node after the current element, with the option to specify filter conditions and the position of the node. The search scope is not limited to sibling nodes, but the entire DOM document.\n\n| Parameter Name |      Type       | Default Value | Description                                                                                             |\n| :------------: | :-------------: | :-----------: | ------------------------------------------------------------------------------------------------------- |\n|  `filter_loc`  | `str` `Tuple[str, str]` `int` |     `''`    | Query syntax used for filtering nodes. `index` parameter is invalid when `filter_loc` is of type `int`. |\n|    `index`     |      `int`      |      `1`      | The position of the queried result. Starts from `1`, negative numbers can be used to indicate the end. |\n|   `timeout`    |     `float`     |    `None`    | No actual effect                                                                                         |\n|  `ele_only`    |      `bool`     |    `True`     | Whether to search for elements only, including text and comment nodes when set to `False` |\n\n|  Return Type   | Description           |\n| :------------: | --------------------- |\n| `SessionElement` | Found element object  |\n|      `str`     | Returns a string when a non-element node is fetched |\n| `NoneElement`  | Returns `NoneElement` when no result is obtained |\n\n**Example:**\n\n```python\n# Get the second element after ele1\nele2 = ele1.after(index=2)\n\n# Get the third div element after ele1\nele2 = ele1.after('tag:div', 3)\n\n# Get the text of the first text node after ele1\ntxt = ele1.after('xpath:text()', 1)\n```\n\n___\n\n🔸 `afters()`\n\nThis method returns a list of all nodes that meet the conditions after the current element. Query syntax can be used for filtering. The search scope is not limited to sibling nodes, but the entire DOM document.\n\n| Parameter Name |     Type      | Default Value | Description               |\n| :------------: | :-----------: | :-----------: | ------------------------- |\n|  `filter_loc`  | `str` `Tuple[str, str]` |     `''`    | Query syntax used for filtering nodes |\n|   `timeout`    |    `float`    |    `None`     | No actual effect           |\n|  `ele_only`    |     `bool`    |    `True`     | Whether to search for elements only, including text and comment nodes when set to `False` |\n\n|  Return Type   | Description |\n| :------------: | ----------- |\n| `List[SessionElement, str]` | List of results |\n\n**Example:**\n\n```python\n# Get all elements after ele1\neles = ele1.afters()\n\n# Get all div elements after ele1\ndivs = ele1.afters('tag:div')\n```\n\n___\n\n### 📌 Find Nodes in Previous Document\n\n🔸 `before()`\n\nThis method returns a certain node before the current element, with the option to specify filter conditions and the position of the node. The search scope is not limited to sibling nodes, but the entire DOM document.\n\n| Parameter Name |      Type       | Default Value | Description                                                                                            |\n| :------------: | :-------------: | :-----------: | ------------------------------------------------------------------------------------------------------ |\n|  `filter_loc`  | `str` `Tuple[str, str]` `int` |     `''`    | Query syntax used for filtering nodes. `index` parameter is invalid when `filter_loc` is of type `int`. |\n|    `index`     |      `int`      |      `1`      | The position of the queried result. Starts from `1`, negative numbers can be used to indicate the end. |\n|   `timeout`    |     `float`     |    `None`    | No actual effect                                                                                        |\n|  `ele_only`    |      `bool`     |    `True`     | Whether to search for elements only, including text and comment nodes when set to `False` |\n\n|  Return Type   | Description           |\n| :------------: | --------------------- |\n| `SessionElement` | Found element object  |\n|      `str`     | Returns a string when a non-element node is fetched |\n| `NoneElement`  | Returns `NoneElement` when no result is obtained |\n\n**Example:**\n\n```python\n# Get the second element before ele1\nele2 = ele1.before(index=2)\n\n# Get the third div element before ele1\nele2 = ele1.before('tag:div', 3)\n\n# Get the text of the first text node before ele1\ntxt = ele1.before('xpath:text()', 1)\n```\n\n___\n\n🔸 `befores()`\n\nThis method returns a list of all nodes that meet the conditions before the current element. Query syntax can be used for filtering. The search scope is not limited to sibling nodes, but the entire DOM document.\n\n## \n\n| Parameter Name |      Type      | Default Value | Description                         |\n| :------------: | :------------: | :-----------: | ----------------------------------- |\n| `filter_loc`   | `str` `Tuple[str, str]` |     `''`      | Query syntax used for filtering nodes |\n|   `timeout`    |     `float`    |    `None`     | No actual effect                     |\n|   `ele_only`   |     `bool`     |    `True`     | Whether to search only for elements. When set to `False`, text and comment nodes are also included in the search range |\n\n| Return Type                   | Description   |\n| ---------------------------- | ------------- |\n| `List[SessionElement, str]`  | List of results |\n\n**Example:**\n\n```python\n# Get all elements before ele1\neles = ele1.befores()\n\n# Get all div elements before ele1\ndivs = ele1.befores('tag:div')\n```\n\n"
  },
  {
    "path": "docs_en/get_start/basic_concept.md",
    "content": "## ☀️ Basic Concepts\n\nThis section explains some basic concepts of DrissionPage and gives an overview of its structure.\n\nIf you find it a bit confusing, you can skip this section directly.\n\n## ✅️️ Web Automation\n\nThere are usually two forms of web automation, each with its own advantages and disadvantages:\n\n- Sending data packets directly to the server to retrieve the required data.\n- Interacting with the browser and web pages.\n\nThe former is lightweight, fast, and suitable for multi-threading and distributed deployment, such as the requests library. However, when the data packet becomes complex, or even when encryption technology is added, the complexity of the development process increases significantly.\n\nTherefore, DrissionPage integrates the two forms by treating web pages as units, and re-encapsulates the Chromium protocol and requests to achieve interoperability between the two modes. It also adds commonly used page and element control functions, which greatly reduces the difficulty and amount of development.\nThe object used to manipulate the browser is called Driver, and the object used to manage connections is called Session. Drission is the combination of the two. The Page represents the encapsulation in POM mode.\nIn the previous version, this library was implemented by re-encapsulating selenium and requests.\nStarting from version 3.0, the author started from scratch and implemented all the features of selenium using the chromium protocol, thus eliminating the dependency on selenium. It has more features, stronger performance, and more flexible development.\n\nIf you want to learn about the old version, please refer to the chapter \"Old Version Usage\".\n\n## ✅️️ Basic Usage Logic\n\nWhether it is browser control or sending and receiving data packets, the operational logic is the same.\n\nThat is, first create a page object, and then obtain element objects from the page object. By reading or operating on the element objects, data acquisition or page control can be achieved.\n\nTherefore, the most important objects are the page object and the element object generated from it.\n\n---\n\n## ✅️️ Main Objects\n\nThere are three commonly used types of page objects:\n\n- `ChromiumPage`: The page object used solely for browser control.\n- `SessionPage`: The page object used solely for sending and receiving data packets.\n- `WebPage`: The page object that integrates both browser control and sending and receiving data packets.\n\n### 📌 `ChromiumPage`\n\n`ChromiumPage` is the page object used for browser control. It is used solely for controlling the browser, and cannot send or receive data packets. It supports Chromium-based browsers such as Chrome, Edge, etc. When creating a page object, the program automatically starts the browser. If a browser with the specified port already exists, it takes over that browser.\n\n:::warning Note\n    Before trying the following code, please close any open Chrome browsers.<br/>If the startup fails, please refer to the \"Preparation\" section in the \"Getting Started\" guide for browser configuration.\n:::\n\n```python\nfrom DrissionPage import ChromiumPage\n\n# Create a page object\npage = ChromiumPage()\n# Control the browser to visit Baidu\npage.get('https://www.baidu.com')\n# Locate the input box and enter the keyword\npage.ele('#kw').input('DrissionPage')\n# Click the \"百度一下\" button\npage.ele('@value=百度一下').click()\n```\n\n---\n\n### 📌 `ChromiumElement`\n\nThe `ChromiumElement` object is an element object on a browser page. It can perform operations such as clicking, entering text, dragging, and running JavaScript scripts on the elements in the browser. It can also find its sub-elements or adjacent elements based on this element.\n\n```python\n# Get the element object with ID 'kw'\nele = page('#kw')\n# Click the element\nele.click()\n# Enter text\nele.input('some text')\n# Get the class attribute\nattr = ele.attr('class')\n# Set the style attribute\nele.set.attr('style', 'display:none;')\n# Get all the 'a' elements among its sub-elements\nlinks = ele.eles('tag:a')\n```\n\nIn addition to the most commonly used `ChromiumElement` object, the browser also produces `ChromiumFrame`, `ShadowRoot`, and `ChromiumTab` objects. See the relevant chapters for detailed usage.\n\n---\n\n### 📌 `SessionPage`\n\n`SessionPage` is the page object used for sending and receiving data packets. It is used solely for sending and receiving data packets, and cannot control the browser.\n\n```python\nfrom DrissionPage import SessionPage\n\n# Create a page object\npage = SessionPage()\n# Visit Baidu\npage.get('https://www.baidu.com')\n# Get the element object\nele = page('#kw')\n# Print the element's HTML\nprint(ele.html)\n```\n\n**Output:**\n\n```shell\n<input id=\"kw\" name=\"wd\" class=\"s_ipt\" value=\"\" maxlength=\"255\" autocomplete=\"off\">\n```\n\n---\n\n### 📌 `SessionElement`\n\nThe `SessionElement` object is an element object generated by `SessionPage`. It can read the element information, or find sub-elements or position other elements based on it, but it cannot perform operations such as clicking.\nThis type of object has a very high parsing efficiency. When the web page is too complex, it can convert the `ChromiumElement` element to a `SessionElement` for parsing to improve speed. At the same time, subordinate elements can be searched during the conversion.\n\n```python\n# Get the tag attribute of the element\ntag = ele.tag\n# Find the first sub-element with name 'name1' under the element\nele1 = ele.ele('@name=name1')\n```\n\n---\n\n### 📌 `WebPage`\n\nThe WebPage object integrates both the browser control and the ability to send and receive data packets, and can share login information between the two.\n\nIt has two working modes: d mode and s mode. The d mode is used for browser control, while the s mode is used for sending and receiving data packets. The WebPage can switch between the two modes, but can only be in one mode at a time.\n\nIn d mode, the elements obtained by WebPage are ChromiumElement, and in s mode, the elements obtained are SessionElement.\n\n```python\nfrom DrissionPage import WebPage\n\n# Create a WebPage object\npage = WebPage()\n# Access a website\npage.get('https://gitee.com/explore')\n# Find the text box element and enter a keyword\npage('#q').input('DrissionPage')\n# Click the search button\npage('t:button@tx():搜索').click()\n# Wait for the page to load\npage.wait.load_start()\n# Switch to data packet mode\npage.change_mode()\n# Get all row elements\nitems = page('#hits-list').eles('.item')\n# Iterate through the elements\nfor item in items:\n    # Print the element text\n    print(item('.title').text)\n    print(item('.desc').text)\n    print()\n```\n\n**Output:**\n\n```shell\ng1879/DrissionPage\nBased on Python web automation tool. It can control the browser and send/receive data packets. It can balance the convenience of browser automation and the efficiency of requests. It has powerful functionality, built-in numerous user-friendly designs and convenient features. The syntax is concise and elegant, with less code.\n\nmirrors_g1879/DrissionPage\nDrissionPage\n\ng1879/DrissionPageDocs\nDrissionPage documentation\n```\n\nFor detailed usage, see the \"Creating Page Objects\" and \"Operating Pages\" sections.\n\n---\n\n## ✅️️ Object Relationship Diagram\n\nThe following diagram lists the generation relationships of various objects used in this library.\n\n```\n├─ SessionPage\n|     └─ SessionElement\n|           └─ SessionElement\n├─ ChrmoiumPage\n|     ├─ ChromiumTab\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumFrame\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumElement\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     └─ ChromiumShadowElement\n|           └─ ChromiumElement\n|           └─ SessionElement\n├─ WebPage\n|     ├─ ChromiumTab\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumFrame\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumElement\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumShadowElement\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     └─ SessionElement\n|           └─ SessionElement\n├─ SessionOptions\n└─ ChrmoiumOptions\n```\n\n---\n\n## ✅️️ Working Modes\n\nAs mentioned above, the WebPage object can control the browser and access network data in two modes: d mode and s mode.\nThe page object can switch between these two modes, with consistent usage methods, but can only be in one mode at any given time.\n\n### 📌 d mode\n\nThe d mode represents Driver and Dynamic. \nThe d mode is used for browser control, allowing not only reading information obtained by the browser, but also manipulating the page, such as clicking, filling in forms, toggling tabs, changing element attributes, and executing JavaScript scripts, etc. \nThe d mode is powerful, but its runtime is significantly slower due to browser restrictions and consumes a large amount of memory.\n\n---\n\n### 📌 s mode\n\nThe s mode represents Session, Speed, and Silence.\nThe s mode runs several orders of magnitude faster than the d mode, but can only read or send data based on data packets and cannot manipulate the page or run JavaScript.\nWhen crawling data, if the website data packets are relatively simple, the s mode is preferred.\n\n---\n\n### 📌 Mode Switching\n\nThe WebPage object can switch between d mode and s mode, which is usually used in the following scenarios:\n\n- When the login verification is strict and difficult to deconstruct, such as when there is a CAPTCHA, use the browser to handle the login, and then switch to s mode to crawl data. This avoids dealing with complex JavaScript and allows for faster processing with the s mode.\n- When the page data is generated by JavaScript and the page structure is extremely complex, you can use the d mode to read the page elements, and then convert the elements to s mode elements for analysis. This can significantly improve the processing speed of the d mode.\n\nIn the above two scenarios, the first one involves converting the entire page object:\n\n```python\npage.change_mode()\n```\n\nThe second one involves converting only certain elements to s mode while in d mode, such as converting table elements to s mode elements, which can be several orders of magnitude faster than directly parsing d mode elements. It is even possible to convert the entire page to s mode elements.\n\n```python\n# Get the table element on the page\nele = page.ele('tag:table')\n# Convert the element to s mode element\nele = ele.s_ele()\n# Get all row elements\nrows = ele.eles('tag:tr')\n```\n\n---\n\n## ✅️️ Structure Diagram\n\n`WebPage` inherits from `ChromiumPage` and `SessionPage`. The former is responsible for controlling the browser, and the latter is responsible for sending and receiving data packets. Therefore, `WebPage` can control the browser, send and receive data packets, and share login status in both modes.\n\n![](../imgs/webpage.jpg)\n\n---\n\n## ✅️️ Configuration Management\n\nBoth requests and browsers usually require some configuration information to work properly, such as the long `user_agent`, the path of the browser exe file, and browser configurations.\nThis code is often cumbersome and repetitive, which is not conducive to code simplicity.  \nTherefore, DrissionPage uses a configuration file to record common configuration information, and the program will automatically read the contents of the default configuration file. Therefore, in the example, you usually can't see the code for the configuration information.\n\nThis feature supports users to save different configuration files and choose according to the situation. It can also support writing configurations directly in the code to shield the reading of configuration files.\n\n:::tip Tips\n    When you need to package the program, you must write the configuration in the code or manually copy the configuration file to the running path after packaging, otherwise an error will occur. See relevant sections for details.\n:::\n\n### 📌 `SessionOptions`\n\nConfiguration object for `SessionPage` and `WebPage` s mode.\n\n---\n\n### 📌 `ChromiumOptions`\n\nConfiguration object for `ChromiumOptions` and `WebPage` d mode.\n\n---\n\n## ✅️️ Locators\n\nLocators are used to locate elements on the page. They are a major feature of this library and can be used to retrieve elements in a very concise way, making it simple and easy to use. The readability and usability are higher than other methods such as xpath, and it is compatible with xpath, css selector, and selenium locators.\n\nHere is a comparison:\n\nLocate the element that contains the text `'abc'`:\n\n```python\n# DrissionPage\nele = page('abc')\n\n# selenium\nele = driver.find_element(By.XPATH, '//*[contains(text(), \"abc\"]')\n```\n\nLocate the element with class name `'abc'`:\n\n```python\n# DrissionPage\nele = page('.abc')\n\n# selenium\nele = driver.find_element(By.CLASS_NAME, 'abc')\n```\n\nLocate the sibling element of ele element:\n\n```python\n# DrissionPage\nele1 = ele.next()  # Get the next element\nele1 = ele.prev(index=2)  # Get the second previous element\n\n# selenium\nele1 = ele.find_element(By.XPATH, './/following-sibling::*')  # Get the next element\nele1 = ele.find_element(By.XPATH, './/preceding-sibling::*[2]')  # Get the second previous element\n```\n\nClearly, the locator statements in this library are more concise and easy to understand. There are also many flexible and easy-to-use methods, see the \"Find Elements\" section for more details.\n\n"
  },
  {
    "path": "docs_en/get_start/before_start.md",
    "content": "🌏 Preparations\n---\n\nBefore we begin, let's do some simple setup.\n\nIf you only need to send and receive data packets, no preparation is required.\n\nIf you want to control a browser, you need to set the path to the browser. By default, the program is set to control Chrome, so Chrome will be used in the following demonstration. The same method can be used to set up Edge or other Chromium-based browsers.\n\n:::warning Note\n    The author has found some strange issues with Chrome version 92, which prevents it from starting on some computer environments. Please avoid using this version if possible.\n:::\n\n## ✅️️ Steps to Follow\n\n### 1️⃣ Attempt to Start the Browser\n\nBy default, the program will automatically search for the Chrome path in the system.\n\nExecute the following code. If the browser starts and accesses the project documentation, it means that you can use it directly and skip the following steps.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('http://g1879.gitee.io/DrissionPageDocs')\n```\n\n---\n\n### 2️⃣ Set the Path\n\nIf the previous step prompts an error, it means that the program did not find the Chrome browser in the system.\n\nYou can use one of the methods below to set the path. The setting will be recorded in the default configuration file and will be used by the program for future startups.\n\n:::tip How to Obtain the Browser Path\n    - The browser path here does not necessarily have to be Chrome; it can be any Chromium-based browser like Edge.\n    - Open the browser and enter `chrome://version` in the address bar (or `edge://version` for Edge), then press Enter.\n    ![](../imgs/find_browser_path.png)  \n    The red box in the image shows the path you need to obtain.  \n    This method is not limited to Windows; you can also use it to obtain the path on Linux with a graphical interface.    \n:::\n\n**🔸 Method 1:**\n\nCreate a temporary Python file and enter the following code, replacing the path with the executable file path of Chrome on your computer. Then, run the file.\n\n```python\nfrom DrissionPage import ChromiumOptions\n\npath = r'D:\\Chrome\\Chrome.exe'  # Please change to the executable file path of Chrome on your computer\nChromiumOptions().set_browser_path(path).save()\n```\n\nThis code will record the browser path in the configuration file, and the browser will be launched using the new path in the future.\n\nAdditionally, if you want to temporarily switch the browser path to test if it runs and operates normally, you can remove `.save()` and use the following code in conjunction with the code from step 1️⃣.\n\n```python\nfrom DrissionPage import ChromiumPage, ChromiumOptions\n\npath = r'D:\\Chrome\\Chrome.exe'  # Please change to the executable file path of Chrome on your computer\nco = ChromiumOptions().set_browser_path(path)\npage = ChromiumPage(co)\npage.get('http://g1879.gitee.io/DrissionPageDocs')\n```\n\n**🔸 Method 2:**\n\nEnter the following command in the command line (replace the path with the one on your computer):\n\n```shell\ndp -p D:\\Chrome\\chrome.exe\n```\n\n:::warning Note\n    - Make sure that the Python environment in the command line is the same as the project environment.\n    - Make sure to use the `cd` command to navigate to the project path first.\n:::\n\n---\n\n### 3️⃣ Retry Controlling the Browser\n\nNow, please re-execute the code from step 1️⃣. If it successfully accesses the project documentation, it means that the setup is complete.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('http://g1879.gitee.io/DrissionPageDocs')\n```\n\n---\n\n## ✅️️ Explanation\n\nOnce you have completed the preparations, there is no need to close the browser. You can continue to use the current browser for the examples that follow.\n\n"
  },
  {
    "path": "docs_en/get_start/examples/control_browser.md",
    "content": "🗺️ Controlling the Browser\n---\n\nNow, let's understand how DrissionPage works through some examples.\n\nThis example demonstrates the use of `ChromiumPage` to control the browser login to the gitee website.\n\n## ✅️️ Page Analysis\n\nURL: [https://gitee.com/login](https://gitee.com/login)\n\nWhen we open the URL and press F12, we can see the HTML of the page as follows:\n\n![](../../imgs/gitee_1.jpg)\n\nThe username input box has an `id` of `'user_login'`, the password input box has an `id` of `'user_password'`, and the login button has a `value` of `'登录'`.\n\nWe can locate these three elements using these three attributes and then input data and click on them.\n\n---\n\n## ✅️️ Sample Code\n\nYou can copy the following code to your editor, change the account and password to your own, and execute it directly to see the results.\n\n```python\nfrom DrissionPage import ChromiumPage\n\n# Create a page object and start or take over the browser\npage = ChromiumPage()\n# Navigate to the login page\npage.get('https://gitee.com/login')\n\n# Locate the username textbox and get the element\nele = page.ele('#user_login')\n# Input the account into the textbox\nele.input('Your account')\n# Locate the password textbox and input the password\npage.ele('#user_password').input('Your password')\n# Click the login button\npage.ele('@value=登录').click()\n```\n\n---\n\n## ✅️️ Sample Explanation\n\nLet's go through the code line by line:\n\n```python\nfrom DrissionPage import ChromiumPage\n```\n\n↑ First, we import the `ChromiumPage` class for controlling the browser.\n\n```python\npage = ChromiumPage()\n```\n\n↑ Next, we create a `ChromiumPage` object.\n\n```python\npage.get('https://gitee.com/login')\n```\n\n↑ The `get()` method is used to visit the given URL. It will wait for the page to load completely before executing the subsequent code. You can also modify the wait strategy, such as waiting for the DOM to load without waiting for resource downloading, by stopping the loading. This will be explained in later sections.\n\n```python\nele = page.ele('#user_login')\n```\n\n↑ The `ele()` method is used to find elements, and it returns a `ChromiumElement` object for manipulating the element.\n\n`'#user_login'` is the locator text, and `#` means locating the element by the `id` attribute.\n\nIt is worth mentioning that `ele()` has built-in waiting. If the element is not loaded, it will wait until the element appears or the time limit is reached. The default timeout is 10 seconds.\n\n```python\nele.input('Your account')\n```\n\n↑ The `input()` method is used to input text into the element.\n\n```python\npage.ele('#user_password').input('Your password')\n```\n\n↑ We can also perform chained operations, directly getting the element and inputting text.\n\n```python\npage.ele('@value=登录').click()\n```\n\n↑ After inputting the account and password, we get the button element using the same method and perform a click operation on it.\n\nThe difference is that this time we search by its `value` attribute. `@` denotes searching by attribute name.\n\nBy doing this, we have completed the automatic login operation on the gitee website.\n\n"
  },
  {
    "path": "docs_en/get_start/examples/data_packets.md",
    "content": "🗺️ Sending and Receiving Data Packets\n---\n\nThis example demonstrates how to use the `SessionPage` to collect data from the gitee website by sending and receiving data packets.\n\nThis example does not use a browser.\n\n## ✅️️ Page Analysis\n\nURL: [https://gitee.com/explore/all](https://gitee.com/explore/all)\n\nThe goal of this example is to retrieve the names and links of all repositories. To avoid putting pressure on the website, we will only collect data from 3 pages.\n\nOpen the URL, press `F12`, and we can see the HTML of the page as follows:\n\n![](../../imgs/gitee_2.jpg)\n\nFrom the HTML code, we can see that the titles of all open source projects are `<a>` elements with a `class` attribute of `'title project-namespace-path'`. We can iterate through these `<a>` elements to retrieve their information.\n\nAt the same time, we observe that the URLs of the list pages are accessed with the page number as a parameter. For example, the URL of the first page is `https://gitee.com/explore/all?page=1`, where the page number is the `page` parameter. Therefore, we can access different pages by modifying this parameter.\n\n---\n\n## ✅️️ Example Code\n\nThe following code can be run directly to see the results:\n\n```python\nfrom DrissionPage import SessionPage\n\n# Create a page object\npage = SessionPage()\n\n# Crawl 3 pages\nfor i in range(1, 4):\n    # Visit a specific page\n    page.get(f'https://gitee.com/explore/all?page={i}')\n    # Get a list of all repository <a> elements\n    links = page.eles('.title project-namespace-path')\n    # Iterate through all <a> elements\n    for link in links:\n        # Print link information\n        print(link.text, link.link)\n```\n\n**Output:**\n\n```shell\n小熊派开源社区/BearPi-HM_Nano https://gitee.com/bearpi/bearpi-hm_nano\n明月心/PaddleSegSharp https://gitee.com/raoyutian/PaddleSegSharp\nRockChin/QChatGPT https://gitee.com/RockChin/QChatGPT\nTopIAM/eiam https://gitee.com/topiam/eiam\n\n...省略...\n```\n\n---\n\n## ✅️️ Example Explanation\n\nLet's go through the code line by line:\n\n```python\nfrom DrissionPage import SessionPage\n```\n\n↑ First, we import the `SessionPage` class for sending and receiving data packets.\n\n```python\npage = SessionPage()\n```\n\n↑ Next, we create a `SessionPage` object.\n\n```python\nfor i in range(1, 4):\n    p.get(f'https://gitee.com/explore/all?page={i}')\n```\n\n↑ Then, we iterate 3 times to construct the URL for each page, and use the `get()` method to access the page URL.\n\n```python\n    links = p.eles('.title project-namespace-path')\n```\n\n↑ After accessing the URL, we use the `eles()` method of the page object to get all elements with a `class` attribute of `'title project-namespace-path'`.\n\nThe `eles()` method is used to find multiple elements that meet the condition, and it returns a list of the elements.\n\nHere, the condition for the search is the `class` attribute, with `.` indicating the search is based on the `class` attribute.\n\n```python\n    for link in links:\n        print(link.text, link.link)\n```\n\n↑ Finally, we iterate through the list of elements obtained and retrieve and print the attributes of each element.\n\n`.text` retrieves the text of the element, and `.link` retrieves the `href` or `src` attribute of the element.\n\n"
  },
  {
    "path": "docs_en/get_start/examples/switch_mode.md",
    "content": "🗺️ Switch Mode\n---\n\nThis example demonstrates how to switch between controlling the browser and sending/receiving packets using the `WebPage`.\n\nUsually, switching modes is used to deal with websites that have strict login checks. The login can be handled by the browser, and then the mode can be switched to sending/receiving packets to collect data.\n\nHowever, this scenario requires corresponding accounts, which is not convenient for demonstration purposes. In this example, we use the browser to search on gitee and then switch to the mode of sending/receiving packets to read the data. Although this example has little practical significance, it can help understand its working mode.\n\n## ✅️️ Page Analysis\n\nURL: [https://gitee.com/explore](https://gitee.com/explore)\n\nWhen we open the URL and press `F12`, we can see the page's HTML as follows:\n\n![](../../imgs/change1.png)\n\nThe input box `<input>` element has an `id` attribute of `'q'`, and the search button `<button>` element's `text` contains the text `'搜索'` (search), which can be used as conditions to find elements.\n\nAfter entering a keyword and searching, we can view the page's HTML again:\n\n![](../../imgs/change2.png)\n\nBy analyzing the HTML code, we can see that the titles of each result are inside elements with an `id` of `'hits-list'` and a `class` of `'item'`. Therefore, we can retrieve all these elements in the page and then traverse to obtain their information.\n\n---\n\n## ✅️️ Example Code\n\nYou can directly run the following code:\n\n```python\nfrom DrissionPage import WebPage\n\n# Create a page object\npage = WebPage()\n# Access the URL\npage.get('https://gitee.com/explore')\n# Find the text box element and enter the keyword\npage('#q').input('DrissionPage')\n# Click the search button\npage('t:button@tx():搜索').click()\n# Wait for the page to load\npage.wait.load_start()\n# Switch to packet mode\npage.change_mode()\n# Get all row elements\nitems = page('#hits-list').eles('.item')\n# Traverse the retrieved elements\nfor item in items:\n    # Print the element's text\n    print(item('.title').text)\n    print(item('.desc').text)\n    print()\n```\n\n**Output:**\n\n```shell\ng1879/DrissionPage\nA web automation tool based on Python. It can control the browser and send/receive packets. It can combine the convenience of browser automation and the efficiency of requests. It has powerful features and built-in humanized designs and convenient functions. The syntax is concise and elegant, with minimal code.\n\nmirrors_g1879/DrissionPage\nDrissionPage\n\ng1879/DrissionPageDocs\nDocumentation for DrissionPage\n```\n\n---\n\n## ✅️️ Example Explanation\n\nLet's go through the code line by line:\n\n```python\nfrom DrissionPage import WebPage\n```\n\n↑ First, we import the `WebPage` class from the `DrissionPage` module.\n\n```python\npage = WebPage()\n```\n\n↑ Then, we create a `WebPage` object.\n\n```python\npage.get('https://gitee.com/explore')\n```\n\n↑ Next, we control the browser to access gitee.\n\n```python\npage('#q').input('DrissionPage')\npage('t:button@tx():搜索').click()\npage.wait.load_start()\n```\n\n↑ We simulate entering a keyword and clicking the search button.\n\nThe methods used to find elements in this code have been explained in the previous examples and will not be discussed in detail here.\n\nThe `wait.load_start()` method is used to wait for the page to enter the loading state, avoiding exceptions caused by too fast operations.\n\n```python\npage.change_mode()\n```\n\n↑ The `change_mode()` method is used to switch the working mode from controlling the browser to sending/receiving packets.\n\nWhen switching, the program will re-access the current URL in the new mode.\n\n```python\nitems = page('#hits-list').eles('.item')\n```\n\n↑ After switching, we can use the same syntax as controlling the browser to retrieve page elements. Here, we retrieve all the result rows on the page, and it returns a list of these element objects.\n\n```python\nfor item in items:\n    print(item('.title').text)\n    print(item('.desc').text)\n    print()\n```\n\n↑ Finally, we iterate through these elements and print the text they contain.\n\n"
  },
  {
    "path": "docs_en/get_start/import.md",
    "content": "# 🌏 Import\n---\n\n## ✅️ Page Classes\n\nPage classes are the main tools used to control the browser or send/receive data packets.\n\n`DrissionPage` contains three main page classes. Choose one based on your needs.\n\n### 📌 `ChromiumPage`\n\nIf you only need to control the browser, import `ChromiumPage`.\n\n```python\nfrom DrissionPage import ChromiumPage\n```\n\n---\n\n### 📌 `SessionPage`\n\nIf you only need to send/receive data packets, import `SessionPage`.\n\n```python\nfrom DrissionPage import SessionPage\n```\n\n---\n\n### 📌 `WebPage`\n\n`WebPage` is the most comprehensive page class, allowing you to control the browser and send/receive data packets.\n\n```python\nfrom DrissionPage import WebPage\n```\n\n---\n\n## ✅️ Configuration Tools\n\n### 📌 `ChromiumOptions`\n\nThe `ChromiumOptions` class is used to set browser startup options.\n\nThese options only take effect when launching the browser and do not affect an already running browser.\n\n```python\nfrom DrissionPage import ChromiumOptions\n```\n\n---\n\n### 📌 `SessionOptions`\n\nThe `SessionOptions` class is used to set the startup options for the `Session` object.\n\nThis is used to configure the connection parameters for `SessionPage` or `WebPage` modes.\n\n```python\nfrom DrissionPage import SessionOptions\n```\n\n---\n\n### 📌 `Settings`\n\n`Settings` is used to set global runtime configurations, such as whether to throw exceptions when an element is not found.\n\n```python\nfrom DrissionPage.common import Settings\n```\n\n---\n\n## ✅️ Other Tools\n\nOther tools that may be used are located in the `DrissionPage.common` path.\n\n### 📌 `Keys`\n\nThe `Keys` class represents keyboard keys, used to simulate pressing control, alt, and other keys.\n\n```python\nfrom DrissionPage.common import Keys\n```\n\n---\n\n### 📌 `Actions`\n\nThe `Actions` class represents a sequence of actions.\n\nIt is already built-in to the browser page object and does not need to be explicitly imported unless specifically required.\n\n```python\nfrom DrissionPage.common import Actions\n```\n\n---\n\n### 📌 `By`\n\nThe `By` class is similar to the one used in Selenium, making it easier to migrate projects.\n\n```python\nfrom DrissionPage.common import By\n```\n\n---\n\n### 📌 Other Tools\n\n - `wait_until`: waits until the given method returns `True`\n - `make_session_ele`: generates a `ChromiumElement` object from an HTML string\n - `configs_to_here`: copies configuration files to the current path\n - `get_blob`: retrieves a specified blob resource\n\n```python\nfrom DrissionPage.common import wait_until\nfrom DrissionPage.common import make_session_ele\nfrom DrissionPage.common import configs_to_here\n```\n\n---\n\n## ✅️ Exceptions\n\nExceptions are located in the `DrissionPage.errors` path.\n\nFor a complete list of exceptions, refer to the Advanced Usage section.\n\n```python\nfrom DrissionPage.errors import ElementNotFoundError\n```\n\n---\n\n## ✅️ Derived Object Types\n\nObjects such as Tab and Element are generated from Page objects. To perform type checking during development, import these types from the `DrissionPage.items` path.\n\n```python\nfrom DrissionPage.items import SessionElement\nfrom DrissionPage.items import ChromiumElement\nfrom DrissionPage.items import ShadowRoot\nfrom DrissionPage.items import NoneElement\nfrom DrissionPage.items import ChromiumTab\nfrom DrissionPage.items import WebPageTab\nfrom DrissionPage.items import ChromiumFrame\n```\n\n"
  },
  {
    "path": "docs_en/get_start/installation.md",
    "content": "🌏 Installation\n---\n\n## ✅️️ System Requirements\n\nOperating System: Windows, Linux, or Mac.\n\nPython Version: 3.6 and above.\n\nSupported Browsers: Chromium-based browsers (such as Chrome and Edge).\n\n---\n\n## ✅️️ Installation\n\nPlease use pip to install DrissionPage:\n\n```shell\npip install DrissionPage\n```\n\n---\n\n## ✅️️ Upgrading\n\n### 📌 Upgrade to the Latest Stable Version\n\n```shell\npip install DrissionPage --upgrade\n```\n\n---\n\n### 📌 Upgrade to a Specific Version\n\n```shell\npip install DrissionPage==4.0.0b17\n```\n\n"
  },
  {
    "path": "docs_en/history/1.x.md",
    "content": "# v0.x-v1.4\ndescription: DrissionPage Version History\n---\n\nVersions v0.x to v1.4 were made based on selenium and requests-html, where the former is responsible for controlling the browser part and the latter for sending and receiving data packets.\n\n## v1.4.0\n\n- In d mode, use js to handle xpath through the `evaluate()` method, abandoning the use of selenium's native methods in order to support directly obtaining text nodes and element attributes using xpath.\n- Added support in d mode to obtain element text and attributes using xpath.\n- Optimized and fixed minor issues.\n\n## v1.3.0\n\n- Seamlessly integrates with selenium code.\n- Download functionality supports POST method.\n- Elements have an added `texts` attribute, which returns the content of each text node within the element.\n- Added support in s mode to obtain element text and attributes using xpath.\n\n## v1.2.1\n\n- Optimized web page encoding logic.\n- Improved logic for obtaining file names in the `download()` function.\n- Optimized logic for obtaining file sizes in the `download()` function.\n- Optimized closing session logic for `MixPage` objects.\n\n## v1.2.0\n\n- Added support for shadow-root.\n- Added automatic retry connection functionality.\n- `MixPage` can now directly accept configurations.\n- Fixed some bugs.\n\n## v1.1.3\n\n- Added parameter for throwing exceptions in connection-related functions.\n- Optimized encoding judgment in s mode.\n- Optimized `check_page()` in d mode.\n- Fixed issue with missing `args` parameter in `run_script()`.\n\n## v1.1.1\n\n- Removed `get_tabs_sum()` and `get_tab_num()` functions, replaced by `tabs_count` and `current_tab_num` attributes.\n- Added `current_tab_handle` and `tab_handles` attributes.\n- `to_tab()` and `close_other_tabs()` functions can accept `handle` value.\n- `create_tab()` can accept a URL to open in a new tab.\n- Other optimizations and bug fixes.\n\n## v1.1.0\n\n- Added xpath and CSS path properties to element objects.\n- Fixed issue in driver mode where element objects could not obtain direct child elements using CSS (blame selenium).\n- In s mode, it is now possible to locate parent elements using xpath.\n- Optimized efficiency of obtaining sibling elements and parent elements in d mode.\n- Optimized tab handling functionality.\n- Other minor optimizations and fixes.\n\n## v1.0.5\n\n- Fixed bug where URL error occurred when switching modes.\n\n## v1.0.3\n\n- `DriverOptions` now supports chaining operations.\n- `download()` function added parameter to handle cases where a file with the same name already exists, with options to skip, overwrite, or automatically rename.\n- Renaming in the `download()` function now only requires inputting the file name without the extension, and it can automatically recognize the extension even if it is inputted.\n\n## v1.0.1\n\n- Enhanced drag and drop functionality and chrome settings.\n\n## v0.14.0\n\n- Added proxy setting and modification to `Drission` class.\n\n## v0.12.4\n\n- `click()`'s `by_js` parameter can now accept `False`.\n- Fixed some bugs.\n\n## v0.12.0\n\n- Added `tag:tagName@arg=val` as a way to locate elements.\n- Added simplified way to create `MixPage` objects.\n\n## v0.11.0\n\n- Improved `easy_set` functions.\n- Elements have additional multi-level locator functions.\n\n## v0.10.2\n\n- Improved `attr` and `attrs` functionality.\n\n## v0.10.1\n\n- Added compatibility with all native parameters for `set_headless()` and `to_iframe()`.\n\n## v0.9.4\n\n- Fixed bugs.\n\n## v0.9.0\n\n- Added element drag and drop functionality and handling of alert dialogs.\n\n## v0.8.4\n\n- Basic completion.\n\n"
  },
  {
    "path": "docs_en/history/2.x.md",
    "content": "# v1.5-v2.x\ndescription: DrissionPage Version History\n---\n\nVersion 1.5 to 2.x, control the browser based on selenium and use the author's homemade functions to send and receive data packets.\n\n## v2.7.3\n\n- Merged the `screenshot_as_bytes()` method of the page object and element object into `screenshot()`.\n- The `input()` method automatically converts non-text parameters to text input.\n\n## v2.7.2\n\n- Added the `screenshot_as_bytes()` method to the d mode page and element objects.\n\n## v2.7.1\n\n- DriverPage:\n  - Added `get_session_storage()`, `get_local_storage()`, `set_session_storage()`, `set_local_storage()`, and `clean_cache()` methods.\n  - Changed the `cmd_args` parameter of `run_cdp()` to `**cmd_args`.\n- Explicitly close the chromedriver.exe process when closing the driver.\n- Optimized the logic for closing browser processes.\n\n## v2.6.2\n\n- Added the `stop_loading()` method to the d mode.\n- Optimized and improved the listener functionality.\n\n## v2.6.0\n\n- Added the `Listener` class.\n  - Can listen to browser data packets.\n  - Can listen asynchronously.\n  - Can execute operations upon receiving certain data packets.\n- Abandoned support for selenium versions below 4.1.\n- Resolved issues when using newer versions of browsers.\n\n## v2.5.9\n\n- Optimized the logic for creating connections in the s mode.\n\n## v2.5.7\n\n- Added the `timeout` parameter to the `select()`, `deselect()`, and other methods of list elements, allowing for waiting for list elements to load.\n- Improved handling of message boxes.\n- Changed `drag()` and `drag_to()` to no longer check if dragging was successful, instead returning `None`.\n- `DriverOptions` objects now support chaining methods inherited from their parent class.\n- Other optimizations and bug fixes.\n\n## v2.5.5\n\n- Added the `run_cdp()` method to `DriverPage`.\n- Removed the `go_anyway` parameter from the `get()` and `post()` methods.\n- Connection retries now default to not printing prompts.\n\n## v2.5.0\n\n- Used DownloadKit library to replace the original `download()` method, adding support for multi-threaded concurrency.\n- Added the `set_ua_to_tab()` method to `DriverPage`.\n- Removed the `scroll_to()` method.\n- Other optimizations and bug fixes.\n\n## v2.4.3\n\n- Changed the way `wait_ele()`, `to_frame()`, and `scroll_to()` work, using class methods to avoid using strings for selecting functionality.\n- Changed `scroll_to()` to the `scroll` attribute.\n- Added the `to_location()` method for scrolling the page or element.\n- Improved usage of the `Select` class.\n\n## v2.3.3\n\n- Added the `forward()` method to `DriverPage`.\n- Changed `close_current_tab()` of `DriverPage` to `close_tabs()`, allowing for closing multiple tabs at once.\n- Added the `run_async_script()` method to `DriverPage`.\n- Added the `timeouts` attribute to `DriverPage`.\n- Added the `set_timeouts()` method to `DriverPage`.\n- Added the `scroll_to()` method to `DriverElement`, allowing for scrolling within an element.\n- Added the `set_page_load_strategy()` method to `DriverOptions`.\n- Added the `page_load_strategy`, `set_window_rect`, and `timeouts` properties to the ini file.\n- Other optimizations and bug fixes.\n\n## v2.2.1\n\n- Added new layout-based relative positioning methods: `left()`, `right()`, `below()`, `above()`, `near()`, `lefts()`, `rights()`, `belows()`, `aboves()`, `nears()`.\n- Modified DOM-based relative positioning methods: deleted the `parents()` method, changed the `parent` attribute to the `parent()` method, changed the `next` attribute to the `next()` method, changed the `prev` attribute to the `prev()` method, changed the `nexts()` and `prevs()` methods to return multiple objects.\n- Added `after()`, `before()`, `afters()`, `befores()`, and other DOM-based relative positioning methods.\n- Added `@@` and `@@-` syntax to the locator syntax for matching multiple conditions and excluding conditions at the same time.\n- Improved functionality of `ShadowRootElement`, now supports full locator syntax when searching for elements in the shadow-root.\n- Changed the `after` and `before` attributes of `DriverElement` to `pseudo_after` and `pseudo_before`.\n- Added the `timeout` parameter to `input()` of `DriverElement`.\n- Added the `insure_clear` parameter to `clear()` of `DriverElement`.\n- Optimized the `submit()` method of `DriverElement`.\n- Added the `active_ele` attribute to `DriverPage` to get the focused element.\n- Renamed the `get_style_property()` method of `DriverPage` to `style()`.\n- Added the offset parameter to `hover()` of `DriverPage`.\n- Renamed `current_tab_num` of `DriverPage` to `current_tab_index`.\n- Changed the `to_frame()` method of `DriverPage` to return the page object itself for easier chaining operations.\n- Optimized logic for automatically downloading the driver.\n- Added the `local_port` parameter to `set_paths()`.\n- Default to using port `9222` to start the browser.\n- Other optimizations and bug fixes.\n\n## v2.0.0\n\n- Supports generating `SessionElement` from `DriverElement` or HTML text, which can significantly improve the crawling speed of page information in `d` mode (using the new `s_ele()` and `s_eles()` methods)\n- Supports hiding and showing browser process windows at any time (only supports Windows system)\n- s mode and d mode use the same logic for extracting text, with a significant increase in text extraction efficiency in d mode\n- `input()` can automatically detect and ensure successful input\n- `click()` supports continuous retries after failure, can be used to ensure successful clicking and waiting for the disappearance of page overlay layers\n- Fixed issues related to paths on Linux and macOS systems\n- `download()` can more accurately obtain the file name\n- Other stability and efficiency optimizations\n\n## v1.11.7\n\n- Added `set_headers()` method to `SessionOptions`\n- Adjusted initialization parameters of `MixPage`\n- Added `timeout` parameter to `click()`, which continuously retries the click within the timeout period. Can be used to monitor the disappearance of overlay layers\n- Added `timeout` parameter to `process_alert()`\n- Other optimizations and bug fixes\n\n## v1.11.0\n\n- Renamed `set_property()` method to `set_prop()`\n- Added `prop()`\n- Changed `clear()` to use the native Selenium method\n- Added `r_click()` and `r_click_at()`\n- `input()` now returns `None`\n- Added `input_txt()`\n\n## v1.10.0\n\n- Improved logic for launching the browser\n- Can read startup parameters when starting in debug mode\n- Improved handling of `select` tags\n- Renamed `to_iframe()` in `MixPage` class to `to_frame()`\n- Added \"half\" option to `scroll_to()` in `MixPage` class, which scrolls half a page\n- Added `kill_browser()` method to Drission class\n\n## v1.9.0\n\n- Added `click_at()` method to elements, which supports clicking with offset\n- `download()` supports retrying\n- Elements' `input()` method accepts combination keys, such as `ctrl+a`\n- Other optimizations\n\n## v1.8.0\n\n- Added `retry_times` and `retry_interval` attributes to specify the number of retries\n- Elements object now has `raw_text` attribute\n- Shortened element search strings, using `x` for `xpath`, `c` for `css`, `t` for `tag`, and `tx` for `text`\n- s mode elements' `text` tries to match d mode as much as possible\n- Other improvements and bug fixes\n\n## v1.7.7\n\n- When creating `WebDriver`, it can automatically download chromedriver.exe\n- Fixed issues when unable to get `content-type`\n\n## v1.7.1\n\n- In d mode, if a debugging port is specified, the browser process can be started automatically and connected\n- Removed dependency on cssselect library\n- Improved efficiency of element search\n- Adjusted logic for obtaining element xpath and css_path\n\n## v1.7.0\n\n- Optimized logic for handling `cookies`\n- Added `get_cookies()` and `set_cookies()` methods to `MixPage`\n- Added `SessionOptions` class\n- Added `remove_attr()` method to `DriverElement` for browsing files\n- Fixed issue with `Session` importing `cookies` when initializing `MixPage`\n- `close_other_tabs()` method in `MixPage` now accepts a list or tuple to keep multiple tabs\n- Other optimizations\n\n## v1.6.1\n\n- Added `.` and `#` as shortcuts for finding elements by class and id, respectively\n- easy_set now recognizes the version of Chrome and automatically downloads the matching chromedriver.exe\n- Improved configuration functionality\n- Fixed issues with shadow-root\n\n## v1.5.4\n\n- Optimized logic for obtaining encoding\n- Fixed issue with progress not being displayed during downloads\n\n## v1.5.2\n\n- Fixed issue with including text nodes after elements when getting HTML\n- Fixed possible errors when getting encoding\n- Optimized `download()` and encoding retrieval code\n\n## v1.5.1\n\n- Fixed bug in obtaining encoding\n\n## v1.5.0\n\n- s mode now uses the lxml library instead of requests_html\n- Can directly call page objects and element objects to get child elements, `element('@id=ele_id')` is equivalent to `element.ele('@id=ele_id')`\n- `nexts()` and `prevs()` methods can retrieve text nodes\n- Can retrieve pseudo-element properties and text\n- Element objects now have `link` and `inner_html` attributes\n- Various optimizations\n\n"
  },
  {
    "path": "docs_en/history/3.x.md",
    "content": "description: DrissionPage Version History\n---\n\n## v3.2.35\n\n- Fixed an issue where simulated actions were not responding when the browser window was minimized\n- Take over the browser without the need for the `'--remote-allow-origins=*'` parameter\n- `tabs` attribute ignores privacy statement\n- Fixed an error when selecting dropdown lists in version 8.x browsers\n- Fixed an issue where dropdown boxes did not trigger linkage in certain cases\n- Fixed issues caused by damaged configuration files\n- Fixed an issue where the `get()` method failed to connect when the `url` parameter contained certain special characters\n\n## v3.2.33\n\n- Headless mode automatically enabled for Linux\n- Added default browser paths for MAC and Linux systems\n- Fixed issues that may occur when capturing element screenshots\n- Fixed an issue where `quit()` did not wait for the browser process to end correctly\n- Suppressed unnecessary prompts for MAC and Linux systems\n- Fixed an issue where `set.timeouts()` did not correctly set the `timeout` attribute\n- Fixed rare error when closing tabs\n- Fixed inaccurate element `size` in certain cases\n\n## v3.2.31\n\n- Added `user_agent` attribute to the page class\n- Added `base64_to_bytes` parameter to the `get_src()` method\n- Redesigned the `find_tabs()` method\n- Updated to the new version of `DownloadKit`, with the addition of append mode for downloads\n- `switch_to` attribute of the `new_tab()` method changed to default to `False`\n- `center` parameter of the `scroll.to_see()` method changed to default to `None`\n- Automatically uses the correct syntax when executing `set_argument('--headless')` in `ChromiumOptions`\n- `get()` now supports ipv6\n- Element screen coordinates will be multiplied by pixel ratio before being returned\n- Bug fixes\n  - Fixed occasional error in `wait.data_packets()` for missing target\n  - Fixed encoding issue when headers are not standardized on the website\n  - Resolved issue of clicking being blocked by fixed elements on the page after scrolling\n  - Fixed inaccurate navigation in certain cases after `back()`\n  - Fixed issue where cookies starting with `'Secure-aa'` and `'Host-'` could not be set\n  - Fixed issue where the `get_cookies()` method of `WebPage` could not retrieve cookies from all domains\n  - Fixed issue where `wait.load_start()` could not correctly set the timeout\n  - Fixed compatibility issue with video encoding on certain computers\n\n## v3.2.30\n\n- Optimized logic for capturing data packets, removed `targets` parameter from `wait.data_packets()`\n- `type()` method of action chains now accepts `list` and `tuple`\n- Page objects can now directly return text or comments with xpath\n- Restored support for python 3.6\n- Completely removed previously deprecated methods and attributes\n- Added `auto_port` mode for using a range of ports\n- Fixed error in `select.by_index()`\n- Fixed error in `get_session_storage()`\n- Fixed issue where dropdown boxes did not trigger `onChange`\n- Fixed issue when using `s_ele()` for elements in `<iframe>`\n- Fine-tuned logic of `run_js()`\n\n## v3.2.26\n\n- New features\n  - Added `child()` and `children()` methods for relative positioning\n  - Added `ele_only` parameter for relative positioning\n  - Added `get_frames()` method to page objects\n  - Added `wait.new_tab()` method to page objects\n  - Added `wait.data_packets()` method to page objects\n  - Added `find_tabs()` method to `ChromiumPage`\n  - Added `focus()` method to element objects\n  - Added `states.is_checked` property to element objects\n  - Added non-thrifty mode and js mode to screen recording\n  - Can set exceptions to be thrown when unable to click\n  - Added double-click methods to elements and action chains\n- API and feature changes\n  - Removed `wait_loading` parameter from `click()`\n  - Added `count` parameter to `click.at()`\n  - Changed `speed` parameter of `drag()` and `drag_to()` to `duration`\n  - Updated `set_headless()` method to be compatible with new version of browser\n  - `ChromiumPage` can now accept only the port number when creating\n  - `new_tab()` now returns the id of the new tab\n  - Added `timeout` parameter to `get_frame()` method, and can accept id or name as conditions\n  - Added element characteristics to `wait` attribute of `ChromiumFrame`\n  - Adjusted screen recording functionality API\n- Optimization and bug fixes\n  - Fixed issue where same-domain `ChromiumFrame` did not close connections in a timely manner\n  - Improved cookie handling logic\n  - Automatically replace `'localhost'` with `'127.0.0.1'` for faster speed\n  - Browser path can accept folder paths\n  - Improved stability of `ChromiumFrame` and element search\n  - Fixed issue when retrieving all data with `get_local_storage()` and `get_session_storage()`\n  - Correctly parse dictionaries returned by js\n  - Fixed issue where `timeout` was ineffective in certain cases of `get_src()`\n  - Fixed issue where `Keys.ENTER` did not correctly simulate pressing enter\n\n## v3.2.19\n\n- Modified `click()` strategy, defaulting to force simulated clicking\n- Added `timeout` parameter to `click()`\n- Added retry count and interval settings to page objects\n- Able to adapt to Chrome 111 version even without using ini files\n\n## v3.2.16\n\n- Adapted to Chrome version 111\n- Fixed issues related to cookies\n- Fixed issues with the `set_headers()` method in the browser page object not working\n- Added the `all_domains` parameter to the `get_cookies()` method in the browser page object\n- Optimized clicking before entering text\n- Removed two parameters from the `set_cookies()` method in the `WebPage` class\n\n## v3.2.14\n\n- Added `^` and `$` symbols to element search to indicate matching at the beginning and end of content\n- Added the `rect.window_state` attribute to the page object\n- Added the `is_alive` attribute to the page object\n- Added the `enabled()`, `disabled()`, and `disable_or_deleted()` methods to the element object for waiting\n- Added the `by_loc()`, `cancel_by_loc()`, and `all()` methods to list elements\n- Added the `all_info` parameter to the `get_cookies()` method\n- Set the default redirect to `True` for the `Session` object\n- Removed dependency on the `tldextract` library\n- Improved waiting for `<iframe>` elements\n- Improved startup speed\n- Optimized element waiting logic and dropdown menu logic\n\n## v3.2.12\n\n- Added support for taking screenshots of elements inside a cross-domain `<iframe>`\n- Added the `as_base64` parameter to the screenshot method\n- Added scrolling behavior and waiting for scrolling to finish settings to the page object\n- Optimized connection between the browser and page scrolling logic\n- Fixed waiting for image saving issue\n- Corrected the order of the `size` attribute of elements\n\n## v3.2.11\n\n- Added screen recording feature\n- Removed the `retry` and `timeout` parameters from the `click()` method\n- Added the `timeout` parameter to the `get_src()` and `save()` methods\n- Added the `NoResourceError` exception\n- Allowed specifying the use of system-installed browser user data folders\n- Other optimizations\n\n## v3.2.7\n\n- Changed the import path for `ActionChains` to `from DrissionPage.common import ActionChains`\n- Changed the import path for `Keys` to `from DrissionPage.common import Keys`\n- Added the `By` class\n- Resolved conflicts when starting the browser\n- Fixed image saving and `ChromiumPage` creation issues\n\n## v3.2.5\n\n- Added the property to check if a browser element is covered\n- Added methods to wait for covering and wait for covering cancellation for browser elements\n- Added the `WebPageTab` object, which can be generated from `WebPage` and supports mode switching\n- The `get_tab()` method of `WebPage` now returns a `WebPageTab` object\n- The `to_tab()`, `close_tabs()`, and `close_other_tabs()` methods can now accept tab objects\n- The `to_front()` method is now in the `set` attribute and can specify tab functionality\n- Fixed initialization issue when creating `ChromiumPage` with a driver\n\n## v3.2.3\n\n- Changes in features\n  \n  - Removed automatic mode switching for `WebPage`\n  - When an element is not found, it now returns `NoneElement` and supports throwing exceptions\n  - Default download method now uses the browser\n  - Removed the `wait_ele()` method of elements, and changed it to wait for self-state changes\n\n- Consolidated many similar APIs\n\n- Added features\n  \n  - Intercepting and automatically filling paths for file upload controls\n  - Prioritize reading ini files in the project path\n  - Added the OR syntax to find elements\n  - Added a batch of exceptions\n  - Added a command-line tool\n  - Added a batch of position attributes to page and element objects\n  - Added a batch of setting methods to `SessionPage`\n  - Added several new waiting methods\n  - Added the `get_frame()` method\n\n- Optimization and fixes\n  \n  - Reworked the underlying and business logic, optimized program logic, greatly enhanced stability\n  - Complete isolation between new and old versions, allowing new versions to develop freely without worrying about affecting programs developed with `MixPage`\n  - Now returns exception information that developers can understand\n  - Fixed issues caused by page loading and exiting triggering pop-up windows\n  - Fixed the 500 error that could occur when loading `<iframe>`\n  - Fixed memory not being released correctly\n  - Fixed clicking being blocked by fixed bars\n  - Automatically waits for content to load when a new `<iframe>` appears\n  - Fixed `<iframe>` getting stuck when navigating between the same domain and cross-domain\n  - Fixed offset issue when taking screenshots of elements inside `<iframe>`\n\n## v3.1.6\n\n- Added the `latest_tab` property to `ChromiumPage`\n\n- Removed the `tab_id` parameter in the initialization of `WebPage`\n\n- Fixed issue where empty elements could be obtained if the page was not fully loaded\n\n- Fixed issue where getting document during redirect in new tab was incorrect\n\n- Fixed issue with memory not being released when using multiple tabs or iframes\n\n- Improved stability\n\n## v3.1.1\n\n- Enhanced download functionality\n  \n  - `ChromiumPage` can also use built-in downloader to download files\n  - Intercept and take over browser download tasks\n  - Added `download_set` attribute to set download parameters\n  - Added the `wait_download_begin()` method\n\n- Improving browser startup settings\n  \n  - Optimizing the structure of the ini file\n  - Adding `ChromiumOptions` to replace `DriverOptions`, completely eliminating the dependency on Selenium\n  - Adding automatic port allocation functionality\n  - Enhancing the design of `SessionOptions`, adding a series of methods for setting parameters\n  - Improving user configuration file settings\n\n- Refactoring of certain code\n  \n  - Optimizing the startup logic of page objects\n  - Optimizing the logic of configuration classes\n  - Optimizing project structure\n\n- Details\n  \n  - Supporting relative file paths when uploading files\n\n- Bug fixes\n  \n  - Fixing errors in `get_tab()`\n  - Fixing incorrect tab switching issue when opening a new tab for the first time in a new browser\n  - Fixing errors when closing the current tab\n  - Fixing errors when changing the browser window size\n\n## v3.0.34\n\n- Removed `check_page()` method from `WebPage`\n- Added `browser_path` parameter to `set_paths()` in `DriverOptions` and `easy_set` methods\n- Added `browser_path` property to `DriverOptions`\n- `ChromiumFrame` now supports scrolling on pages\n- Improved scrolling to element functionality\n- Changed the order of relative positioning parameters in `SessionElement`\n- `SessionPage` can also read timeout settings from ini files\n- Added `timeout` item to `session_options` in ini file\n- Added `timeout` property and `set_timeout()` method to `SessionOptions`\n- Optimized and fixed some issues\n\n## v3.0.31\n\n- Renamed `run_script()` and `run_async_script()` to `run_js()` and `run_async_js()`\n- All returned coordinate data is now converted to a `tuple` of `int` type\n- Modified comments\n\n## v3.0.30\n\n- Added `m_click()` method to elements\n- Added `type()`, `m_click()`, `r_hold()`, `r_release()`, `m_hold()`, `m_release()` methods to action chains\n- The `on_ele` parameter of action chains can now accept text locators\n- Added `set_headers()` method to `WebPage`, `SessionPage`, and `ChromiumPage`\n\n## v3.0.28\n\n- Various size and position information now returned as `tuple` instead of `dict`\n- Improved `ChromiumFrame`\n- Fixed inaccurate positioning issue when in small window, fixed inability to retrieve elements within iframes using `s_ele()`\n- Added `wait_loading()` method and parameter\n- Other optimizations and bug fixes\n\n## v3.0.22\n\n- Added `copy_cookies` parameter to `change_mode()`\n- Adjusted parameter order of `prev()`, `next()`, `before()`, and `after()` in elements objects generated by `WebPage`\n- Fixed occasional failure when reading a page\n- Replaced type annotations with stub files\n\n## v3.0.20\n\nMajor update. Introducing `WebPage`, rewriting the underlying logic to eliminate the dependency on Selenium, enhancing functionality, and improving performance. Supports browsers with Chromium engine (e.g., Chrome and Edge). Advantages over `MixPage` include:\n\n- No webdriver features\n- No need to download different drivers for different versions of browsers\n- Faster execution speed\n- Ability to traverse elements across iframes without switching in and out\n- Treating iframes as regular elements, allowing direct element search within them for clearer logic\n- Ability to operate on multiple tabs in the browser simultaneously, even if the tabs are not active\n- Directly accessing browser cache to keep images, without the need for GUI clicks to save them\n- Ability to take screenshots of the entire webpage, including areas outside the viewport (supported in browsers version 90 and above)\n\nOther updates:\n\n- Added `ChromiumTab` and `ChromiumFrame` classes for handling tab and frame elements\n- Introduced `ActionChains` for performing actions in conjunction with `WebPage`\n- Removed `set_window_rect` property from ini file and `DriverOption`\n- Browser startup configuration now supports plugins\n- Browser startup configuration now supports the `prefs` attribute of `experimental_options`\n\n"
  },
  {
    "path": "docs_en/history/4.x.md",
    "content": "v4.x\n---\n\n## v4.0.3.3\n\nThis version of the project has undergone a significant refactor and introduced many new features. It has improved the runtime logic, optimized the project structure, and resolved many accumulated issues. There have been substantial improvements compared to the previous version.\n\nHowever, there have been many changes to the API, and it is not completely backward compatible.\n\n- Improved packet capture functionality\n  - The `listen` attribute has been added to the page object, replacing `FlowViewer`\n  - The `wait.set_targets()` method has been removed\n  - The `wait.stop_listening()` method has been removed\n  - The `wait.data_packets()` method has been removed\n  - The `FlowViewer` path has been removed from `DrissionPage.common`\n  - Use `listen.set_start()` and `listen.stop()` to start and stop listening\n  - Use `listen.wait()` to block and wait for packets\n  - Use `listen.steps()` to synchronously retrieve listening results\n  - Added `listen.wait_silent()` to wait for all requests to complete (excluding targets)\n  - Optimized the structure of listening results, separating request and response data\n- Refactored connection logic\n  - The `page_load_strategy` attribute in the page object has been renamed to `load_mode`\n  - The `set.load_strategy` has been renamed to `set.load_mode`\n  - The `timeout` parameter in the `get()` method now covers the entire process\n  - The `timeout` parameter also applies to loading triggered by non-`get()` methods (e.g., clicking on links)\n  - The `s` mode of `SessionPage` and `WebPage` will now retry when receiving empty data\n  - The `get()` method of `SessionPage` can point to a local file\n  - Added a new `none` loading mode\n- Improved download management functionality\n  - The `download_set` attribute has been removed from the page object\n  - Added `set.download_path()` method\n  - Added `set.download_file_name()` method\n  - The `download()` method is now supported by Tab objects and Frame objects\n  - Each Tab object can individually set the download path and rename the file\n  - Intercept browser download tasks and retrieve their information\n  - Cancel browser download tasks, get download progress, and wait for task completion\n  - Set the handling method when encountering an existing folder\n  - Browser download task management is not enabled by default\n- Improved page object\n  - `ChromiumPage` and `WebPage` are now fixed singletons\n  - The Tab object obtained from `get_tab()` is now a default singleton, can be set to allow multiple instances with `Settings`\n  - Browser page objects no longer receive the `ChromiumDriver` object upon startup\n  - The `driver_or_options` parameter in the `WebPage` object has been renamed to `chromium_options`\n  - The `addr_driver_opts` parameter in the `ChromiumPage` object has been renamed to `addr_or_opts`\n  - Page objects include built-in action chains\n  - `ready_state`, `is_loading`, and `is_alive` attributes have been merged into the `states` attribute\n  - Added `raw_data` parameter to page objects, which returns raw data in the `s` mode\n  - All page objects now have a `close()` method, used for closing connections for `SessionPage` and closing tabs for browser page objects\n  - Browser page objects have a `wait()` method for waiting for a specific number of seconds\n  - Browser page objects have `wait.ele_loaded()` method for waiting for elements to load in the DOM\n  - Browser page objects have `wait.title_change()` and `wait.url_change()` methods for waiting for title and URL changes\n  - Browser page objects have `wait.alert_closed()` method for waiting for a popup to be manually closed\n  - Browser page objects have `set.blocked_urls()` method to set ignored connections\n  - Tab and Page objects have `disconnect()`, `reconnect()`, and `save()` methods\n  - Tab and Page objects have `add_init_js()` and `remove_init_js()` methods\n  - `wait.ele_delete()` method has been renamed to `wait.ele_deleted()`\n  - `wait.ele_display()` method has been renamed to `wait.ele_displayed()`\n  - `wait.load_complete()` method has been renamed to `wait.doc_loaded()`\n  - The `quit()` method has added a `force` parameter, which allows forcefully closing the browser process\n  - The `ChromiumFrame` object has an added `rect` attribute\n  - The `frame_size` attribute in `ChromiumFrame` has been renamed to `rect.size`\n  - Optimized access speed for `SessionPage` and `WebPage` in `s` mode\n  - When `WebPage` is in `d` mode, `post()` returns a `Response` object\n- Improved tab management\n  - Removed the `to_tab()` method\n  - Removed the `to_main_tab()` and `set.main_tab()` methods\n  - Removed the `main_tab` attribute\n  - The `switch_to` parameter has been removed from the `new_tab()` method\n  - Added `new_window`, `background`, and `new_context` parameters to the `new_tab()` method\n  - `rect.borwser_size` has been renamed to `rect.window_size`\n  - `rect.borwser_location` has been renamed to `rect.window_location`\n  - `set.window.maximized()` has been renamed to `set.window.max()`\n  - `set.window.minimized()` has been renamed to `set.window.mini()`\n  - `set.window.fullscreen()` has been renamed to `set.window.full()`\n  - Tab objects have `set.activate()`, `close()`, `handle_alert()`, and `states.has_alert`\n  - The `tab_id` parameter in `get_tab()` has been renamed to `id_or_num`, it can accept an index\n- Improved cookie settings\n  - `set.cookies()` can accept a single cookie\n  - Added `set.cookies.clear()` method to clear cookies\n  - Added `set.cookies.remove()` method to delete a cookie item\n- Improved element-related features\n\n- Added the `@!` syntax for element lookup.\n- Removed the `@@-` and `@|-` syntax.\n- Added an `index` parameter to `ele()` and `s_ele()` methods to specify which element to retrieve.\n- Added support for receiving an index as the first parameter for relative positioning.\n- Changed the `size`, `location`, and `locations` attributes to the `rect` attribute.\n- Changed the `locations.xxxx` properties to `rect.xxxx`.\n- Changed the size and location information from `int` type to `float` type.\n- Added the `states.has_rect` attribute to indicate whether the element has size and location information.\n- Added the `states.is_whole_in_viewport` attribute to indicate whether the entire element is within the viewport.\n- Improved the `click()` method:\n  - Added the `wait_stop` parameter to specify whether to wait for the element's motion to stop before clicking (default is `True`).\n  - By default, the `click()` method waits for the element's motion to stop before clicking.\n  - Renamed the `click.twice()` method to `click.multiple()`.\n- Added detailed error message for element lookup failure.\n- Added the ability to set a default value for failed element lookup.\n- Added the `wait_stop_moving()` method to wait for element's movement to stop.\n- Added the `wait()` method to wait for a certain number of seconds.\n- Added the `check()` method to select or deselect an element.\n- Added the `to_center()` method for scrolling to the center of the viewport.\n- Added the `select.by_option()` and `select.cancel_by_option()` methods for selecting or canceling the selection of list item elements.\n- Added the `states.has_rect` attribute.\n- Added the `states.is_whole_in_viewport` attribute to determine if the entire element is within the viewport.\n- Added the `states.is_covered` attribute to indicate the ID of the covering element when the element is covered.\n- Added the `by_js` parameter to the `input()` method.\n- Changed the `rename` parameter of the `save()` method to `name`.\n- Supported `blob` type in the `get_src()` method.\n- Improved accuracy of the `css_path` retrieval.\n- Changed the default value of the `timeout` parameter in relative positioning to `None`.\n- Renamed the `wait.delete()` method to `wait.deleted()`.\n- Renamed the `wait.disabled_or_delete()` method to `wait.disabled_or_deleted()`.\n- Renamed the `wait.display()` method to `wait.displayed()`.\n- Enabled comparison of two elements using `==`.\n- Improved element lookup speed.\n\nImprovements to the startup configuration:\n- Removed the `easy_set()` method.\n- Automatically close the privacy statement when starting or taking over the browser.\n- Automatically use headless mode when starting the browser on a headless system. Use `set_headless(False)` to disable this.\n- When `set_headless(False)` is used but a headless browser is taken over, the browser will be closed and a new headful browser will be started.\n- Supported multi-threading in the `auto_port()` method.\n- Changes to the INI file:\n  - Renamed the `chrome_options` class to `chromium_options`.\n  - Changed the `binary_location` item to `browser_path`.\n  - Changed the `page_load_strategy` item to `load_mode`.\n  - Changed the `debugger_address` item to `address`.\n  - Removed the `'--remote-allow-origins=*'` parameter from the `arguments` item.\n  - Added the parameters `'--no-default-browser-check'`, `'--disable-suggestions-ui'`, `'--disable-popup-blocking'`, `'--hide-crash-restore-bubble'`, and `'--disable-features=PrivacySandboxSettings4'` to the `arguments` item.\n  - Added the `tmp_path` item to the `paths` class.\n  - Removed the `experimental_options` item.\n  - Added the `prefs`, `flags`, and `existing_only` items to the `chrome_options` class.\n  - Added the `others` class, which includes the `retry_times` and `retry_interval` items.\n- Changes to the `ChromiumOptions` class:\n  - Added the `set_flag()` and `clear_flags_in_file()` methods for setting experimental options.\n  - Added the `existing_only()` method and the `is_existing_only` attribute to specify whether to only take over the browser without automatically starting a new one.\n  - Added the `ignore_certificate_errors()` method to ignore certificate errors.\n  - Added the `retry_times` and `retry_interval` attributes and the `set_retry()` method to set retry parameters.\n  - Added the `incognito()` method to enable incognito mode.\n  - Added the `set_tmp_path()` method.\n  - Added the `tmp_path` and `is_auto_port` attributes.\n  - Added the `tmp_path` parameter to the `auto_port()` method.\n  - Split the `set_paths()` method into separate methods for setting the browser path, local port, address, download path, user data path, and cache path.\n  - Renamed the `set_page_load_strategy()` method to `set_load_mode()`.\n  - Renamed the `set_headless()` method to `headless()`.\n  - Renamed the `set_no_imgs()` method to `no_imgs()`.\n  - Renamed the `set_no_js()` method to `no_js()`.\n  - Renamed the `set_mute()` method to `mute()`.\n  - Renamed the `debugger_address` attribute to `address`.\n- Changes to the `SessionOptions` class:\n  - Renamed the `set_paths()` method to `set_download_path()`.\n  - Added the `retry_times` and `retry_interval` attributes and the `set_retry()` method to set retry parameters.\n- Deleted the 2.x code.\n- Added the `next_one` parameter to the `handle_alert()` method to handle the next appearing alert.\n- Added the `set.auto_handle_alert()` method to the browser page object to automatically handle alerts.\n- Added the `set.encoding()` method and the `encoding` attribute to the `SessionPage` class.\n- Enabled clicking on `<option>` elements.\n- Added the `timeout` parameter to the `run_js()`, `run_js_loaded()`, and `run_async_js()` methods.\n- Removed the `timeout` parameter from the `run_async_js()` method.\n\n- Change `implicit` of `timeouts` to `base`.\n- Change `ActionChains` to `Actions`.\n- Add `duration` parameter to the move method of the action chain.\n- Add `input()` method to the action chain.\n- Allow `key_down()` and `key_up()` methods of the action chain to accept key name text.\n- Change the `text` parameter of the `type()` method of the action chain to `keys`.\n- Add `name` attribute to the `get_screenshot()` method of the element, which allows specifying the file name.\n- Add `scroll_to_center` parameter to the `get_screenshot()` method of the element, which scrolls to the center of the page before taking a screenshot.\n- Modify the `wait.new_tab()` method to return the new tab ID upon success.\n- Exclude F12 windows from the `tabs`.\n- Add `wait_until()` method to the `DrissionPage.common` path, which supports custom combination wait conditions.\n- Add `get_blob()` method to the `DrissionPage.common` path.\n- Changes to exceptions:\n  - Change `CallMethodError` to `CDPError`.\n  - Change `ElementLossError` to `ElementLostError`.\n  - Change `ContextLossError` to `ContextLostError`.\n  - Change `TabClosedError` to `PageDisconnectedError`.\n  - Add `WaitTimeoutError`.\n  - Add `GetDocumentError`.\n  - Add `WrongURLError`.\n  - Add `StorageError`.\n  - Add `CookieFormatError`.\n  - Add `TargetNotFoundError`.\n- Changes to settings:\n  - Add `singleton_tab_obj`, which sets whether the Tab object allows multiple instances.\n  - Rename `raise_ele_not_found` to `raise_when_ele_not_found`.\n  - Rename `raise_click_failed` to `raise_when_click_failed`.\n- Optimization:\n  - Add default browser paths for MAC and Linux systems.\n  - Completely refactor object startup and running logic to greatly improve stability.\n  - No longer require `--remote-allow-origin` parameter for taking over or starting the browser.\n  - Add timeout facility to all code involving loops to prevent freezing.\n  - Completely refactor `ChromiumFrame` to improve stability.\n  - Adjust project structure.\n- Bug fixes:\n  - Fix issue with failing to retrieve documents when network connection is extremely unstable.\n  - Fix issue with relative positioning `timeout` not taking effect.\n  - Fix potential deviation issue when locating elements inside shadow roots.\n  - Fix issue with obtaining screen coordinates of elements inside cross-origin `ChromiumFrame`.\n  - Fix issue with failing to load plugins with relative paths.\n  - Add timeouts to all loops to avoid freezing.\n  - Fix issue of white space appearing outside the window when taking element screenshots.\n  - Fix issue with Tab not inheriting the download path from Page.\n  - Fix issue with incorrect retrieval of href attribute for elements inside `<iframe>`.\n  - Fix issue with setting expires for cookies.\n\n"
  },
  {
    "path": "docs_en/history/statement.md",
    "content": "Self-Introduction\n---\n\n## ✅️ Introduction\n\nA few years ago, there was a very strange system.\n\nIts business logic was extremely peculiar, the interface design was incomprehensible, the runtime speed varied and the error handling was inconsistent.\n\nUnfortunately, this system had to hold several events every year, and everyone who used it complained about it.\n\nEven more unfortunately, I was one of the administrators of this system.\n\nEvery time an event was held, the phone on my desk would ring off the hook every day.\n\nIn order to process the collected data, I had to ask several interns to manually organize it for a week.\n\nIn short, it was a tool that caused productivity to regress.\n\nUnable to bear it any longer, I started learning automation to save myself from the heavy and laborious work.\n\n---\n\n## ✅️ Getting Started and Development\n\nI searched online and found that Selenium was the most popular web automation tool at the time, so I began learning and using it, embarking on the path of automation.\n\nAt first, I felt that Selenium was amazing. With just a dozen lines of code, I could accomplish tasks that used to take me half a day.\n\nAs I delved deeper into my understanding, I gradually felt that Selenium was like a shell of a house. It only provided the most basic tools, but to use them, I had to do a considerable amount of encapsulation myself. So I learned the POM (Page Object Model) pattern and started working on my own page objects.\n\nHowever, as a beginner, I was at a loss when encountering various strange error messages, and didn't know how to deal with various unstable situations. There were even some pre-existing pitfalls that were difficult to handle. During this time, I encountered countless obstacles, spent countless efforts, and encountered and overcome numerous challenges.\n\nGradually, the tools I encapsulated became more mature. With the increase in usage scenarios, there were also more requirements.\n\n---\n\n## ✅️ Birth\n\nBased on my experience and requirements, I summarized the following needs:\n\nFirstly, the statements in Selenium were too verbose. Implicit waits had a narrow scope of application, explicit wait statements were overly complex, element search statements were lengthy, and chain operations were unsightly, making it unbearable for me as a minimalist.\n\nSecondly, the element search methods were not user-friendly. This was the most commonly used operation, but it was often written in long and cumbersome ways. I wanted to create a concise and efficient syntax for element searching.\n\nAdditionally, I wanted to combine browsers with requests, leveraging their strengths to achieve a balance between writing speed and execution speed.\n\nFinally, I had already encapsulated a batch of useful methods and filled in many of Selenium's own pitfalls. I wanted to be able to conveniently use my handy tools wherever I went.\n\nTherefore, this library was born.\n\nDrission is a term I coined. It is a combination of the first half of \"Driver\" and the second half of \"Session\".\n\nBecause the object that controls the browser in Selenium is called `WebDriver`, and the object used for sending and receiving data packets in requests is called `Session`, Drission is an attempt to combine them.\n\nAnd Page represents the library in units of pages, encapsulating it using the POM pattern.\n\n---\n\n## ✅ Version Iteration\n\nAlthough I have a foundation in programming and front-end development, I am also self-taught in Python, so it's like feeling my way across a river.\n\nFortunately, the project-driven learning method has progressed quickly. Whatever the project needs, I learn that knowledge. It's like completing a puzzle, gradually filling in various knowledge gaps.\n\nAt the beginning, there were many things I didn't understand, so I used existing libraries. Versions v0.x to v1.4 were based on Selenium and requests-html. Selenium was responsible for controlling the browser, and requests-html was responsible for sending and receiving packets.\n\nDuring this phase, I achieved unification of the APIs for controlling the browser and sending/receiving packets, interoperability of cookies, and established the basic usage logic.\n\nHowever, using requests-html as the underlying layer was a bit too heavy. After gradually understanding its operating principles, I refactored this part of the underlying code using requests and lxml. This brought me to the second phase.\n\nThe second phase was from v1.5 to v2.x. The browser control part still relied on Selenium, while the packet sending/receiving and parsing functions were completely self-developed. During this phase, running the program felt much easier, and I was able to add more useful features and optimizations. However, the bottleneck shifted to Selenium.\n\nThe more I used it, the deeper my understanding became, and my dissatisfaction with Selenium grew. Selenium was restricted by chromedriver, and many of my ideas could not be implemented. For example, it was unable to take screenshots of entire web pages; or when switching between tabs, previously obtained elements would become invalid; or downloading the corresponding chromedriver for different versions of the browser, which could result in being unable to use newer versions of the browser due to lack of a new driver, and so on.\n\nThere is another important point. In recent years, our country has been suppressed by the Americans in various ways. I already had a sense of frustration and wanted to contribute a small amount to the domestic open source community.\n\nSo I arrived at the third phase.\n\nAfter 2-3 years of use, I encountered enough pitfalls and gained some insights into automation. With a mindset of giving it a try, I boldly took a step towards developing my own underlying technology. In version 3.x, DrissionPage completely eliminated its reliance on Selenium and conducted a complete overhaul of the underlying infrastructure.\n\nOnce I made up my mind to develop my own technology, a whole new world opened up. Breaking free from the constraints of the chromedriver framework, I suddenly felt a sense of liberation. From then on, DrissionPage not only runs faster than Selenium, but it also enables various technological innovations. Those who have used it should have experienced this, so I won't dwell on it here.\n\nBy the way, it's worth mentioning that DrissionPage has an unexpected side effect. It can actually pass through human-machine detection tools like cloudflare and Google. This is something even the author didn't anticipate. Perhaps it's because DrissionPage is a niche creation that these big companies are not familiar with.\n\n---\n\n## ✅ Random Thoughts\n\nFor some reason, after version 3.x, the previously obscure DrissionPage has suddenly become popular. Gitee even granted it a GVP. The stars on GitHub have been steadily increasing. It's a pleasant surprise, and I'm grateful for everyone's support.\n\nIn fact, the author is a very laid-back developer. Development is not their main profession, and building libraries is just a hobby. Truth be told, as additional features were added, many of them were not even used by the author. Continuing to develop is mostly driven by interest. This is a piece of work that the author has carefully crafted, hoping to make it as perfect as possible. The code I've written is running in the world, like an extension of my own life. More importantly, I feel that I've made a small contribution to the software industry in our country, and that makes it meaningful.\n\nHowever, automated software can often be a double-edged sword. Here, as the author, I have to put on armor. Please do not use DrissionPage for any work that may violate laws or ethical constraints. Please use DrissionPage responsibly, adhere to the spider protocol, and do not use it for any illegal purposes. By choosing to use DrissionPage, you are agreeing to this agreement. The author assumes no responsibility for any legal risks or losses resulting from your violation of this agreement, and you bear all the consequences.\n\nFinally, returning to the beginning, the system that once disgusted me has been greatly improved by a responsible development team. Although it had a rough start, they actively participated in the business and continuously iterated the product. After several major updates, the system is now incredibly useful. Therefore, I have high hopes for Chinese software and believe it will continue to improve in the future.\n\n"
  },
  {
    "path": "docs_en/usage_introduction.md",
    "content": "Introduction to Usage\n---\n\nThis chapter provides a detailed overview of several page objects and their usage.\n\nIn the \"Getting Started > Basic Concepts\" section, we briefly introduced several page objects, which will not be repeated here.\n\n## ✅️️ `SessionPage`\n\nA page object used for sending and receiving data packets. It can only send and receive data packets and cannot control the browser.\n\n### 📌 `SessionElement`\n\nAn element object obtained from `SessionPage`, which allows reading element information. It can also be used as a reference to obtain surrounding or descendant elements.\n\n---\n\n## ✅️️ `ChromiumPage`\n\nAn object used to control the browser. In addition to controlling a page, it can also perform operations on the overall browser, such as adjusting window size and position, managing file downloads, adding and deleting tabs, etc.\n\n### 📌 `ChromiumTab`\n\nA browser tab object, similar to `ChromiumPage`. It can control functions within a page but cannot control the overall browser functions.\n\n---\n\n### 📌 `ChromiumFrame`\n\nA `<frame>` or `<iframe>` element object, which serves as both a page object and an element with characteristics. It can perform operations such as page navigation and retrieving internal elements.\n\n---\n\n### 📌 `ChromiumElement`\n\nAn element object obtained from the aforementioned browser page objects. It supports interactions such as clicking, entering text, dragging, etc.\n\n---\n\n### 📌 `ChromiumShadowElement`\n\nA shadow-root object, with element characteristics, which allows obtaining descendant elements within it.\n\n---\n\n## ✅️️ `WebPage`\n\nA page element that integrates the functionalities of both `SessionPage` and `ChromiumPage`. It possesses all the functionalities of both elements combined. It has two modes, namely s and d modes, which can synchronize login information between the two modes.\n\n### 📌 s mode\n\nThe s mode functionality is the same as `SessionPage`, and the generated elements are instances of `SessionElement`.\n\n---\n\n### 📌 d mode\n\nThe d mode functionality is the same as `ChromiumPage`, and the generated elements are instances of `ChromiumElement`.\n\n---\n\n## ✅️️ Configuration Objects\n\nConfiguration objects are used to provide initialization information when creating page objects. They only take effect when the page objects are created and cannot be modified after creation.\n\n### 📌 `SessionOptions`\n\nA configuration object used for `SessionPage` and `WebPage` s mode.\n\n---\n\n### 📌 `ChromiumOptions`\n\nA configuration object used for `ChromiumPage` and `WebPage` d mode.\n\n---\n\n## ✅️️ Relationship Diagram\n\nThe following diagram lists the generation relationships between various objects used in this library.\n\n```\n├─ SessionPage\n|     └─ SessionElement\n|           └─ SessionElement\n├─ ChromiumPage\n|     ├─ ChromiumTab\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumFrame\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumElement\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     └─ ChromiumShadowElement\n|           └─ ChromiumElement\n|           └─ SessionElement\n├─ WebPage\n|     ├─ ChromiumTab\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumFrame\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumElement\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     ├─ ChromiumShadowElement\n|     |     └─ ChromiumElement\n|     |     └─ SessionElement\n|     └─ SessionElement\n|           └─ SessionElement\n├─ SessionOptions\n└─ ChromiumOptions\n```\n\n\n"
  },
  {
    "path": "docs_en/whatsnew/3_2.md",
    "content": "💥 3.2 Function Introduction\n---\n3.2 has significant changes compared to 3.1. It has streamlined the underlying logic, fixed many issues, improved stability, and integrated user APIs.\n\nLooking forward to receiving feedback and suggestions.\n\nCommunication methods:\n\n- QQ Group: 897838127\n\n- [Click to Submit Issues](https://gitee.com/g1879/DrissionPage/issues)\n\n## ✅️️ Feature Changes\n\n### 📌 `WebPage` no longer automatically switches modes\n\nIn the previous version, when calling a method that is not exclusive to the current mode, the mode would automatically switch. For example, calling the `post()` method in d mode would automatically switch to s mode.\n\nIn 3.2, the mode only switches when `change_mode()` is explicitly called. Therefore, the browser can be controlled in s mode without conflict. More flexible to use.\n\n### 📌 `WebPage`'s tab object can also switch modes\n\nThe `WebPage`'s `get_tab()` method now returns a `WebPageTab` object, which can also switch modes. It is created in d mode by default.\n\n### 📌 Changed return value when element not found\n\nIn the previous version, when an element was not found, it would return `None`. In version 3.2, it will return a `NoneElement` object.\n\nThis object evaluates as `False` when using an `if` statement, and calling its functionality will throw an `ElementNotFoundError` exception. This way, you can use `if` to check if the element is found or use `try` to catch the exception.\n\nWhen multiple elements are not found, an empty `list` will still be returned, which is consistent with the previous version.\n\nFor example, when we search for a nonexistent element:\n\n```python\nele = page.ele('xxxxxxx')\n```\n\n❌ The current version will not work with `None`:\n\n```python\nif ele is None:\n    print('Not found.')\n```\n\n⭕ The correct approach is:\n\n```python\n# using if statement\nif not ele:\n    print('Not found.')\n\nif ele:\n    print('Found.')\n```\n\n```python\n# using try statement\ntry:\n    ele.click()\nexcept ElementNotFoundError:\n    print('Not found.')\n```\n\n### 📌 Adjustments to some methods\n\n- `run_js()` changes the order of arguments\n\n- `drag()` and `drag_to()` methods remove the `shake` parameter\n\n- Element objects remove the `wait_ele()` method and use `wait.xxxx()` methods to wait for changes in their own attributes\n\n- `ChromiumPage`'s `to_tab()`, `close_tabs()`, and `close_other_tabs()` methods can accept tab objects\n\n- Change import path for `ActionChains` to `from DrissionPage.common import ActionChains`\n\n- Change import path for `Keys` to `from DrissionPage.common import Keys`\n\n### 📌 Downloads now default to using the browser\n\nTo prevent users who are unfamiliar with DownloadKit from thinking that the program does not respond after clicking.\n\nNow, clicking will default to using the browser to download to the current program path. The save location can be set using `download_set.save_path()`.\n\n## ✅️️ API Integration\n\nWith the increase in functionality, there are also more and more similar APIs. For example, there are 9 methods in `ChromiumPage` that start with `set_`, and there are 5 ways to click on an element. If all of these similar APIs are piled together, it will appear very messy.\n\nTherefore, in version 3.2, a large number of APIs have been integrated to avoid a bloated prompt interface and enhance development flexibility.\n\nThe old version of the APIs will still be retained, but they will be deprecated in future versions.\n\nOld version:\n\n```python\npage.set_timeouts(20, 30, 40)\n```\n\nNew version:\n\n```python\npage.set.timeouts(20, 30, 40)\n```\n\n### 📌 Browser page object\n\n# Academic Paper Translation\n\n| Old Version | New Version | Explanation |\n|:---:|:---:|:---:|\n| set_timeouts() | set.timeouts() | Timeout Settings |\n| set_session_storage() | set.session_storage() | Session storage settings |\n| set_local_storage() | set.local_storage() | Local storage settings |\n| set_user_agent() | set.user_agent() | User agent settings |\n| set_cookies() | set.cookies() | Cookies settings |\n| set_headers() | set.headers() | Headers settings |\n| set_page_load_strategy.xxxx() | set.load_strategy.xxxx() | Page loading strategy settings |\n| set_window.xxxx() | set.window.xxxx() | Browser size and position settings |\n| set_main_tab() | set.main_tab() | Main tab settings |\n| wait_loading() | wait.load_start() | Wait for page to start loading |\n| wait_ele().xxxx() | wait.ele_xxxx() | Wait for element to reach a certain state |\n| scroll_to_see() | scroll.to_see() | Scroll the element into view |\n| hide_browser() | set.window.hide() | Hide the browser |\n| show_browser() |  set.window.show() | Show the browser |\n| to_front() | set.tab_to_front() | Set a tab to active state |\n\n### 📌 Element Objects\n\n| Old Version | New Version | Explanation |\n|:---:|:---:|:---:|\n| wait_ele().xxxx() | wait.xxxx() | Wait for element to reach a certain state |\n| r_click() | click.right() | Right-click the element |\n| m_click() | click.middle() | Middle-click the element |\n| r_click_at() | click.right_at() | Right-click the element with offset |\n| click_at() | click.left_at() | Left-click the element with offset |\n| set_attr() | set.attr() | Set a attribute property |\n| set_prop() | set.prop() | Set a property attribute |\n| set_innerHTML() | set.innerHTML() | Set the content of innerHTML |\n| midpoint | locations.midpoint | Get the midpoint of the element on the page |\n| client_location | locations.viewport_location | Get the top-left corner of the element in the viewport |\n| client_midpoint | locations.viewport_midpoint | Get the midpoint of the element in the viewport |\n| is_selected | states.is_selected | Check if the element is selected |\n| is_displayed | states.is_displayed | Check if the element is displayed |\n| is_enabled | states.is_enabled | Check if the element is enabled |\n| is_alive | states.is_alive | Check if the element is still in the DOM |\n| is_in_viewport | states.is_in_viewport | Check if the element is in the viewport |\n| pseudo_before | pseudo.before | Get the content of the before pseudo-element |\n| pseudo_after | pseudo.after | Get the content of the after pseudo-element |\n| obj_id | ids.obj_id | Get the object id of the element |\n| node_id | ids.node_id | Get the node id of the element |\n| backend_id | ids.backend_id | Get the backend id of the element |\n| doc_id | ids.doc_id | Get the doc id of the element |\n\n## ✅️️ New Features\n\n### 📌 Intercepting Upload Control Filling Paths\n\nIn previous versions, to upload a file, the developer needs to find the file upload control in the DOM first. Some `<input>` elements that are loaded in real-time and disguised are not easy to find, and sometimes they are controlled by JavaScript.\n\nIn the new version, there is no need to bother finding the upload control anymore. Just set the path to be uploaded and then trigger the file selection box. The program will automatically intercept the selection box and input the path into the control, which is very convenient.\n\nExample:\n\n```python\n# Set the file path to be uploaded\npage.set.upload_files('demo.txt')\n# Click the button to trigger the file selection box\nbtn_ele.click()\n# Wait for the path to be filled\npage.wait.upload_paths_inputted()\n```\n\nAfter clicking the button, the file selection box is intercepted and will not pop up, but you can see that the file path has been entered.\n\nBecause this action is asynchronous input, you need to wait explicitly for the input to complete before proceeding to the next step.\n\n### 📌 Prioritize Reading Project Path INI File\n\nIn the previous version, the default INI file was stored in the installation directory of DrissionPage, and any modifications had to be made through code, which was inconvenient for debugging. In the new version, it will prioritize searching for the `'dp_configs.ini'` file in the user's project folder and use it, making it easy to manually change the configuration during development. When packaging the project, you can directly package it without causing any issues of not finding the file.\n\nThe `configs_to_here()` method is added in the `easy_set` function, which can directly copy the default INI file to the current path.\n\n### 📌 Added OR Syntax to Find Elements\n\nThe OR syntax `@|` is added to find elements for matching multiple properties:\n\n```python\npage('@|class=xxx@|name=abc')\n```\n\nThe OR syntax `@|` cannot be used together with the AND syntax `@@`.\n\n### 📌 Throw Exception When Element is Not Found\n\nWhen an element is not found, in addition to returning a `NoneElement` object, you can also set it to throw an exception directly.\n\n```python\nfrom DrissionPage.easy_set import raise_when_ele_not_found\n\nraise_when_ele_not_found(True)\n```\n\nThis setting is a global setting, and once set, it will take effect for the entire project.\n\n**Example:**\n\n```python\nfrom DrissionPage import SessionPage\nfrom DrissionPage.easy_set import raise_when_ele_not_found\n\nraise_when_ele_not_found(True)\n```\n\n```\n\nThe code above will throw an `ElementNotFound` exception.\n\n### 📌 Adding a Batch of Exceptions\n\nAdd a batch of exceptions, call position:\n\n​```python\nfrom DrissionPage.errors import ElementLossError\n```\n\n- `AlertExistsError`: Throws if there is an unhandled alert when calling page functions.\n- `ContextLossError`: Throws if elements are called after the page is refreshed.\n- `ElementLossError`: Throws if elements become invalid due to page or self refreshing.\n- `CallMethodError`: Throws if there is an exception when calling CDP.\n- `TabClosedError`: Throws if the tab is closed and its functions are still called.\n- `ElementNotFoundError`: Throws if an element cannot be found.\n- `JavaScriptError`: Throws if there is a JavaScript runtime error.\n- `NoRectError`: Throws if there is no size or location information for an element.\n\n### 📌 Adding Methods and Properties\n\n`ChromiumPage`:\n\n| Name                                 | Description                           |\n|:------------------------------------:|:---------------------------------------:|\n| run_cdp_loaded()                     | Executes CDP command, waits for page load to complete before executing |\n| run_js_loaded()                      | Executes JS statement, waits for page load to complete before executing  |\n| wait.load_complete()                 | Waits for page to finish loading            |\n| wait.upload_paths_inputted()         | Waits for file paths to be entered into the file selection box              |\n| rect.browser_location                | Gets the coordinates of the top-left corner of the browser on the screen    |\n| rect.page_location                   | Gets the coordinates of the top-left corner of the page on the screen       |\n| rect.viewport_location               | Gets the coordinates of the viewport on the screen                      |\n| rect.browser_size                    | Gets the size of the browser                             |\n| rect.page_size                       | Gets the size of the page                               |\n| rect.viewport_size                   | Gets the size of the viewport without the scrollbar            |\n| rect.viewport_size_with_scrollbar    | Gets the size of the viewport with the scrollbar               |\n| remove_ele()                         | Removes an element from the page                      |\n| get_frame()                          | Gets a `ChromiumFrame` object                              |\n\n`SessionPage` adds a series of setting methods:\n\n| Name                               | Description                           |\n|:----------------------------------:|:---------------------------------------:|\n| set.header()                       | Sets a header value                    |\n| set.proxies()                      | Sets proxies                           |\n| set.auth()                         | Sets login information                 |\n| set.hooks()                        | Sets callback methods                  |\n| set.params()                       | Sets connection parameters             |\n| set.cert()                         | Sets a certificate                     |\n| set.stream()                       | Sets whether to use streaming response content |\n| set.trust_env()                    | Sets whether to trust the environment   |\n| set.max_redirects()                | Sets the maximum number of redirects    |\n| set.max_redirects()                | Adds an adapter                        |\n\n`ChromiumElement`:\n\n| Name                               | Description                           |\n|:----------------------------------:|:---------------------------------------:|\n| locations.screen_location         | Gets the coordinates of the top-left corner of the element on the screen |\n| locations.screen_midpoint         | Gets the coordinates of the midpoint of the element on the screen     |\n| locations.screen_click_point      | Gets the coordinates of the click point of the element on the screen |\n| locations.click_point             | Gets the coordinates of the click point of the element on the page  |\n| locations.viewport_click_point    | Gets the coordinates of the click point of the element on the viewport |\n| states.is_covered                 | Gets whether the element is covered      |\n| click.at()                        | Clicks on the element with an offset, specified key optional |\n| wait.covered()                    | Waits for the element to be covered        |\n| wait.covered()                    | Waits for the element to not be covered    |\n\n### 📌 Command Line Tools\n\nAdded support for command line tools.\n\n- `--set-browser-path` (`-p`): Sets the browser path in the configuration file.\n- `--set-user-path` (`-u`): Sets the user data path in the configuration file.\n- `--configs-to-here` (`-c`): Copies the default configuration file to the current path.\n- `--launch-browser` (`-l`): Launches the browser, passing in the port number, 0 means using the value in the configuration file.\n\n```shell\ndp --set-browser-path '/Application/Goolge Chrome.app/Contents/MacOS/Google Chrome'\n```\n\n## ✅️️ Optimization and Bug Fixes\n\n- [x] Reworked the underlying and business logic of the program, optimizing the program logic and greatly enhancing stability.\n- [x] New and old versions are completely isolated, allowing for development without worrying about affecting programs developed with `MixPage`.\n- [x] Now returns exception information that developers can understand.\n- [x] Fixed issues caused by page loading and exiting triggering pop-ups.\n- [x] Fixed possible 500 errors when loading `<iframe>`.\n- [x] Fixed click issue with cross-origin `<iframe>`.\n- [x] When elements without position and size information are obtained, exceptions are now thrown.\n- [x] Fixed issue with memory not being properly released.\n- [x] Fixed click being obstructed by fixed bar.\n- [x] Automatically waits for content to load when taking over newly appeared `<iframe>`.\n- [x] Fixed `<iframe>` getting stuck when switching between same-origin and cross-origin.\n- [x] Fixed offset issue when taking screenshots of elements inside `<iframe>`.\n```\n\n\n```"
  },
  {
    "path": "docs_en/whatsnew/4_0.md",
    "content": "💥 4.0 Function Introduction\n---\n\n3.x was a preliminary attempt to independently develop the underlying framework. Many aspects of it were explored blindly, and there were some immature areas.\n\nAfter a period of use and gained experience, 4.0 made significant changes to the underlying framework based on 3.x. It added a large number of features, improved operational efficiency and stability, optimized project structure, and solved many existing problems. There has been a qualitative improvement compared to the old version.\n\nHowever, many APIs have changed and are not completely compatible with the old version.\n\nSome changes to the APIs are necessary for functional optimization, while others are due to my insistence on concise naming. Taking advantage of the major version update, I also took the opportunity to change the names that I had not been satisfied with for a long time.\n\nI apologize for any inconvenience caused to users, but it is better to endure short-term pain than long-term pain. Since there are not many people using the project, it is better to decisively abandon historical burdens and make changes.\n\nSome old practices can still be used in 4.0.0, but IDE will prompt that they are invalid and they will be completely removed in future versions. It is recommended to update to the new practices as soon as possible.\n\nThis section provides a brief overview of the functional changes. For specific usage methods, please refer to the corresponding chapters.\n\n## ✅️ New packet capturing function\n\nIn 3.2, the packet capturing function was mainly provided by FlowViewer and `wait.data_packets()`.\n\nFlowViewer was a personal project of mine. It was written in a relatively casual manner, and the technology was not yet mature. There were issues such as missed capturing, incomplete information, and inadequate APIs.\n\nIn 4.0, each page object has built-in listeners with upgraded capabilities and more reasonable APIs.\n\n### 📌 Changes to old APIs\n\n- FlowViewer is deprecated and will no longer be updated in the future\n- `wait.set_targets()` is removed\n- `wait.stop_listening()` method is removed\n- `wait.data_packets()` method is removed\n- `FlowViewer` path is removed from `DrissionPage.common`\n\n---\n\n### 📌 New APIs\n\n- Each tab object (including `ChromiumFrame`) has a new `listen` attribute for built-in listening functionality\n- Use `listen.start()` and `listen.stop()` to start and stop listening\n- Use `listen.wait()` to block and wait for data packets\n- Use `listen.steps()` to synchronously get listening results\n- Added `listen.wait_silent()` to wait for all requests to complete (including targets)\n- Optimized listening result structure to separately store request and response data\n\n---\n\n### 📌 Example\n\nThe following example can be executed directly to view the results. This example uses timing and is used for comparison with the next example.\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom TimePinner import Pinner\nfrom pprint import pprint\n\npage = ChromiumPage()\npage.listen.start('api/getkeydata')  # Specify the listening target and start listening\npinner = Pinner(True, False)\npage.get('http://www.hao123.com/')  # Visit website\npacket = page.listen.wait()  # Wait for data packet\npprint(packet.response.body)  # Print the body of the data packet\npinner.pin('Time taken', True)\n```\n\n**Output:**\n\n```shell\n{'hao123.new.shishi.bangdan.recom': [{'index': '1',\n                                      'pure_title': 'The first detained persons are transferred by Israel and Hamas'},\n                                     {'index': '2',\n                                      'pure_title': 'The French Foreign Minister laughed when he heard about the visa-free policy'},\n                                     ......\nTime taken: 3.3114853000151925\n```\n\n---\n\n## ✅️ New page access logic\n\nIn 3.x, there were the following main issues with connections:\n\n- The `timeout` parameter of the `get()` method of the browser page object only applies during the loading phase and cannot cover the connection phase.\n- The `none` mode of the loading strategy has no practical use.\n\nBoth of these issues are addressed in 4.0 and enable users to control when to terminate the connection. In addition, the connection logic has been optimized to avoid deadlock situations.\n\n### 📌 Changes in APIs\n\n- The `page_load_strategy` attribute of the page object is renamed to `load_mode`\n- `set.load_strategy` is changed to `set.load_mode`\n\n---\n\n### 📌 Behavioral changes\n\n- The `timeout` parameter of the `get()` method now covers the entire process\n- The `timeout` parameter also takes effect on non-`get()` method triggers of loading (such as clicking on links)\n- In `SessionPage` and `WebPage` mode, if empty data is received, it will retry\n- The `get()` method of `SessionPage` can point to local files\n\n---\n\n### 📌 New `none` loading mode\n\nIn the old version, the `none` loading strategy would stop loading as soon as the page is successfully connected, which has no practical meaning in actual usage.\n\nIn the new version, this mode has been changed: Unless the loading is completed, the program will not actively stop it (even if it has timed out). At the same time, the connection state no longer blocks the program, allowing users to make state judgments and actively stop loading.\n\nThis provides users with a great deal of freedom and allows them to stop the page loading when key data packets or elements appear, greatly improving performance.\n\n---\n\n### 📌 Example\n\nWe will continue to use the code from the previous example, but set the loading mode to `none` and actively stop loading when data is obtained.\n\n```python\nfrom DrissionPage import ChromiumPage\nfrom TimePinner import Pinner\nfrom pprint import pprint\n\n## 翻译 private_upload\\default_user\\2024-01-24-20-54-58\\4_0.md.part-1.md\n\n​```python\npage = ChromiumPage()\npage.set_load_mode.none()  # Set the load mode to none\npage.listen.start('api/getkeydata')  # Specify the target to listen to and start listening\npinner = Pinner(True, False)\npage.get('http://www.hao123.com/')  # Access the website\npacket = page.listen.wait()  # Wait for the packet\npage.stop_loading()  # Stop loading actively\npprint(packet.response.body)  # Print the packet body\npinner.pin('用时', True)\n```\n\n**Output:**\n\n```shell\n{'hao123.new.shishi.bangdan.recom': [{'index': '1',\n                                      'pure_title': '以色列和哈马斯移交首批被扣押人员'},\n                                     {'index': '2',\n                                      'pure_title': '听到免签政策法国外长笑了'},\n                                     ......\n用时：1.2575092000188306\n```\n\nIt can be seen that 2 seconds of time is saved.\nWhen a website needs to access some unstable resources, the time saved is quite significant and can also improve the stability of the program.\n\n---\n\n## ✅️ New Download Management Feature\n\nIn the old version, the download management feature had the following issues:\n\n- The configuration of the browser download management and the built-in downloader `DownloadKit` both used the `download_set` attribute, which easily caused confusion.\n- The browser download task cannot specify the filename before downloading.\n- There is a risk of this feature becoming invalid with browser version updates.\n\nIn version 4.0, the browser download management feature has been completely restructured, with a more reasonable structure and more features.\nAt the same time, the settings for the built-in downloader and the browser download task have been separated.\n\n### 📌 API Changes\n\n- The `download_set` attribute has been removed from the page object.\n- The `set.download_path()` method has been added.\n- The `set.download_file_name()` method has been added.\n\n---\n\n### 📌 New Features\n\n- The `download()` method is also supported by `Tab` objects and `Frame` objects.\n- Each `Tab` object can set its own download path and rename the filename.\n- Browser download tasks can be intercepted and their information obtained.\n- Browser download tasks can be cancelled, download progress can be obtained, and waiting for task completion is supported.\n- Handling of existing folders can be set.\n\n---\n\n### 📌 Behavior Changes\n\nBy default, browser download task management is not enabled in version 4.0. It is only enabled when the download path is set in startup parameters or when the `set.download_path()` method is called.\n\nWhen task management functionality is not enabled, download behavior is the same as usual.\n\n---\n\n### 📌 Example\n\nThe following example can be run directly.\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.get('https://office.qq.com/download.html')\npage.set.download_path('tmp')  # Set the file save path\npage.set.download_file_name('qq')  # Set the filename\npage('#downloadWin').click()  # Click to trigger the download\nmission = page.wait.download_begin()  # Wait for the download to begin and get the task object\nmission.wait()  # Wait for the download task to complete\n```\n\n**Output:**\n\n```shell\nurl：https://dldir1.qq.com/qqfile/qq/TIM3.4.8/TIM3.4.8.22124.exe\n文件名：qq.exe\n目标路径：D:\\coding\\projects\\DrissionPge\\tmp\n100.0% 下载完成 D:\\coding\\projects\\DrissionPage\\tmp\\qq.exe\n```\n\n---\n\n## ✅️ Page Objects\n\nHere, page objects refer to Page objects (`ChromiumPage`, `WebPage`), Tab objects (`ChromiumTab`, `WebPageTab`), and `ChromiumFrame` objects.\n\n### 📌 Changes in Startup Parameters\n\nIn version 4.0, when creating `WebPage` and `ChromiumPage` objects, the `ChromiumDriver` object is no longer accepted.\n\nThis means that creating page objects by passing control is no longer supported.\n\nBecause it supports multiple page objects controlling the same tab, if you need multiple page objects to work together, just use `get_tab()` to create a new object, which can be used in parallel with the original object.\n\nMoreover, passing control itself has stability risks, so it has been removed in the new version.\n\nCorrespondingly, the names of the startup parameters have also changed:\n\n- The `driver_or_options` parameter of the `WebPage` object has been renamed to `chromium_options`.\n- The `addr_driver_opts` parameter of the `ChromiumPage` object has been renamed to `addr_or_opts`.\n\nIn addition, the `addr_or_opts` startup parameter of `ChromiumPage` can now accept `int` data, directly passing in the port number.\n\n---\n\n### 📌 Built-in Action Chains\n\nIn version 4.0, each page object has a built-in `actions` attribute, which is the action chain.\n\nThe difference between the built-in action chain and directly creating an action chain object is that each operation will wait for the page to finish loading before executing.\n\n**Example:**\n\n```python\npage.actions.hold(ele).move(50).release()\n```\n\n---\n\n### 📌 State Information\n\nIn the old version, page objects had the `ready_state`, `is_loading`, and `is_alive` attributes, which are now merged into the `states` attribute.\n\n```python\n# ------ Old Version Code ------\nprint(page.is_loading)\n\n# ------ New Version Code ------\nprint(page.states.is_loading)\n```\n\n---\n\n### 📌 Others\n\n- `ChromiumPage` and `WebPage` are changed to fixed singletons.\n- The `get_tab()` function now returns a default singleton Tab object, which can be set to allow multiple instances using `Settings`.\n- The `raw_data` parameter is added to page objects, which returns raw data in \"s\" mode.\n- All page objects now have a `close()` method. For the `SessionPage`, it is used to close the connection, while for the browser page objects, it is used to close the tab.\n- The browser page objects now have a `wait()` method for waiting for a specified number of seconds.\n- The browser page objects now have a `wait.ele_loaded()` method for waiting for an element to be loaded in the DOM.\n- The browser page objects now have `wait.title_change()` and `wait.url_change()` methods for waiting for the title and URL to change.\n- The browser page objects now have a `wait.alert_closed()` method for waiting for a pop-up window to be manually closed.\n- The browser page objects now have a `set.cookie()` method for setting a single cookie.\n- The browser page objects now have a `set.blocked_urls()` method for setting ignored connections.\n- The Tab and Page objects now have a `disconnect()` method for disconnecting from the web page.\n- The Tab and Page objects now have a `reconnect()` method for disconnecting and reconnecting to the web page.\n- The Tab and Page objects now have a `save()` method for saving the web page as MHTML.\n- The Tab and Page objects now have `add_init_js()` and `remove_init_js()` methods.\n- The `quit()` method now has a `force` parameter for forcefully closing the browser process.\n- The `ChromiumFrame` now has a `ract` attribute.\n- The `frame_size` attribute of `ChromiumFrame` is now renamed to `rect.size`.\n- The `wait.ele_delete()` method is now renamed to `wait.ele_deleted()`.\n- The `wait.ele_display()` method is now renamed to `wait.ele_displayed()`.\n- The `wait.load_complete()` method is now renamed to `wait.doc_loaded()`.\n- Improved the speed of accessing `SessionPage` and `WebPage` in \"s\" mode.\n- When in \"d\" mode, the `WebPage` now returns a `Response` object for the `post()` method.\n\n---\n\n## ✅️ Cookie settings\n\n- The `set.cookies()` method now accepts a single cookie.\n- Added the `set.cookies.clear()` method for clearing cookies.\n- Added the `set.cookies.remove()` method for deleting a cookie item.\n\n---\n\n## ✅️ Tab management\n\n### 📌 No longer supporting the `to_tab()` function\n\nThe `to_tab()` function was originally designed based on Selenium to switch program focus between multiple tabs.\n\nSelenium does not have a tab object, so the driver can only operate on one tab at a time. When using multiple tabs, the program needs to switch back and forth between different tabs, losing previously obtained elements in the process, resulting in low efficiency and inconvenience.\n\nStarting from DrissionPage 3.x, it supports the coexistence of multiple tab objects, where the objects do not affect each other, and the tab can be operated without activation. Therefore, there is no need to switch between tabs.\n\nMoreover, when switching focus and the page is still loading, the implementation logic becomes complex and stability issues may arise.\n\nBased on these considerations, the `to_tab()` method is removed and replaced with the `get_tab()` method.\n\n**API modifications involved:**\n\n- Removed the `to_tab()` method.\n- Removed the `to_main_tab()` and `set.main_tab()` methods.\n- Removed the `main_tab` attribute.\n- Removed the `switch_to` parameter from the `new_tab()` method.\n\n**Creating a new tab and switching to the new tab:**\n\n```python\n# ------ Old code ------\ntab = page.new_tab(switch_to=True)\n\n# ------ New code ------\ntab = page.new_tab()\n```\n\n**Operating on another tab:**\n\n```python\n# ------ Old code ------\npage.to_tab(page.tabs[1])\n\n# ------ New code ------\ntab = page.get_tab(1)  # Create a tab object that can be used in parallel with the page object\n```\n\n---\n\n### 📌 New features of `new_tab()`\n\nThe `new_tab()` method of the Page object has added 3 parameters:\n\n- `new_window`: Whether to create a new window. The new window and the old tab belong to the same browser but are independent windows.\n- `background`: Whether the newly created tab is in an inactive state (can still be operated even if not activated).\n- `new_context`: Whether to create an independent incognito window. This window has independent cookies from the old tab.\n\nNow, `new_tab()` returns the newly created tab object instead of its tab_id.\n\n**Example:**\n\n```python\ntab = page.new_tab()\ntab.get('https://g1879.gitee.io/drissionpagedocs')\n```\n\n---\n\n### 📌 Tab position and size\n\nPreviously, only the `ChromiumPage` or `WebPage` objects could set the window position, size, and state.\n\nAlthough the Tab objects (`ChromiumTab`, `WebPageTab`) could retrieve the window information, they only retrieved the information controlled by the Page.\n\nIn version 4.0, even for independent window tabs, they can also set and retrieve these properties.\n\nThe following properties and method names have been modified:\n\n- `rect.borwser_size` is now `rect.window_size`.\n- `rect.borwser_location` is now `rect.window_location`.\n- `set.window.maximized()` is now `set.window.max()`.\n- `set.window.minimized()` is now `set.window.mini()`.\n- `set.window.fullscreen()` is now `set.window.full()`.\n\n**Example:**\n\n```python\ntab = page.get_tab(1)\nprint(tab.rect.window_state)  # Get the window state\nprint(tab.rect.window_location)  # Get the window location\nprint(tab.rect.window_size)  # Get the window size\n```\n\n```python\ntab.set.window.size(500, 500)  # Set window size\ntab.set.window.location(500, 500)  # Set window location\ntab.set.window.max()  # Maximize window\n# For more details, refer to the relevant documentation...\n\n---\n\n### 📌 Tab behavior\n\nIn the previous version, the Page object managed the switching and activation of tabs. Only the Page object could handle JavaScript pop-ups.\n\nIn version 4.0, the Tab object can activate and close itself, and also handle its own pop-up dialogs.\n\n- `tab.set.activate()`: Activate the Tab object\n- `tab.close()`: Close the Tab object\n- `tab.handle_alert()`: Handle the Tab object's pop-up dialog\n- `tab.states.has_alert`: This property indicates whether there is a pop-up dialog in the Tab object\n\n---\n\n### 📌 Changes in the `get_tab()` function parameter\n\nIn the current version, the `get_tab()` method can accept the tab index (starting from 0) as a parameter. The `tab_id` parameter has been replaced with `id_or_num`.\n\nHowever, it should be noted that the index may not correspond to the visual order of the tabs, but is based on the activation order.\n\n**Example:**\n\n​```python\ntab = page.get_tab(1)  # Get the object of the second tab in the list\n```\n\n---\n\n## ✅️ Element-related\n\n### 📌 Changes in the locator syntax\n\nIn the previous version, `@@-` or `@|-` was used in the locator syntax to indicate negation, but the visual effect was not clear and the meaning was not very clear.\n\nTherefore, it has been changed to `@!` to better reflect the negation. \n\n`@!` can be used together with `@@` or `@|`, depending on whether it is an \"and\" or \"or\" relationship.\n\nIt can also be used individually to negate a specific attribute.\n\n**Example:**\n\n```python\n# ------ Old syntax ------\npage.ele('@@arg1=abc@@-arg2=def')\n# ------ New syntax ------\npage.ele('@@arg1=abc@!arg2=def')\n\n# ------ Old syntax ------\npage.ele('t:div@|arg1=abc@|-arg2=def')\n# ------ New syntax ------\npage.ele('t:div@|arg1=abc@!arg2=def')\n\n# ------ Old syntax ------\npage.ele('@@-arg1=abc')\n# ------ New syntax ------\npage.ele('@!arg1=abc')\n```\n\n---\n\n### 📌 Optimization of relative locator parameters\n\nIn the previous version, if you wanted to use an index to retrieve an element using relative locator, you had to write it as `ele.next(index=1)`.\n\nBut I want to simplify this statement a bit, so that it can be written as `ele.next(1)` to represent retrieving the next sibling element.\n\nThis is supported in version 4.0. When the `filter_loc` parameter receives an `int` type, it can be used as the `index` parameter.\n\nThe `parent()` method adds an `index` parameter, which is used to select the Nth result when `level_or_loc` is a locator.\n\n---\n\n### 📌 Position and size\n\nIn the previous version, the size and position information of an element was provided by the `location`, `locations`, and `size` attributes.\n\nTo make the logic clearer and consistent with the Page object logic, these attributes have been unified and included in the `rect` attribute.\n\n- Delete the `size`, `location`, and `locations` attributes and add the `rect` attribute\n- Change the `locations.xxxx` attribute in the previous version to `rect.xxxx`\n- Change the size and position information from `int` type to `float` type\n- Add the `states.has_rect` attribute to indicate whether the element has a size and position\n- Add the `states.is_whole_in_viewport` attribute to indicate whether the element is completely within the viewport\n\n```python\n# ------ Old code ------\nele.size\nele.location\nele.locations.midpoint\n\n# ------ New code ------\nele.rect.size\nele.rect.location\nele.rect.midpoint\n```\n\n---\n\n### 📌 Clicking\n\n- The `click()` method adds a `wait_stop` parameter, which waits for the element to stop moving before clicking by default.\n- The `click()` method now waits for the element to stop moving before executing the click by default.\n- The `click.twice()` method is changed to `click.multiple()`.\n\n---\n\n### 📌 More detailed error message for element not found\n\nIn the previous version, when finding elements in a chain, if one of the finds fails, it was not easy to see which statement failed.\n\nIn version 4.0, the failed find statement and the locator statement can be displayed in the error message.\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage(timeout=1)\npage.get('https://baidu.com')\nprint(page('#wrapper')('#s_tab')('#abcd').text)  # The element with ('#abcd') does not exist\n```\n\nOutput:\n\n```shell\nDrissionPage.errors.ElementNotFoundError: \nElement not found.\nmethod: ele()\nargs: {'locator': '#abcd'}\n```\n\n### 📌 Setting Default Value for Failed Element Searching\n\nIf you need to retrieve an attribute from an element after searching for it, but this element may not exist, or if one of the nodes in the chain search is not found, you can set the value to be returned when the search fails instead of throwing an exception.\n\nThis can simplify some data collection logic.\n\n**Example**\n\nFor example, when traversing multiple objects in a list on a webpage, but some elements may lack a certain sub-element, in the old version, you had to write like this:\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\nfor li in page.eles('t:li'):\n    ele = li('.name')\n    name = ele.text if ele else None\n    ele = li('.age')\n    age = ele.text if ele else None\n    ele = li('.phone')\n    phone = ele.text if ele else None\n```\n\nIn the new version, you can write like this:\n\n```python\nfrom DrissionPage import ChromiumPage\n\npage = ChromiumPage()\npage.set.NoneElement_value('没找到')\nfor li in page.eles('t:li'):\n    name = li('.name').text\n    age = li('.age').text\n    phone = li('.phone').text\n```\n\nThis way, if a certain sub-element does not exist, it will not throw an exception, but return the string `'没找到'` instead.\n\n---\n\n### 📌 More\n\n- The `ele()` and `s_ele()` methods have added an `index` parameter, which can be used to specify which one to retrieve.\n- Added the `wait.stop_moving()` method, which can wait for the movement to end.\n- Added the `wait()` method, used to wait for a certain number of seconds.\n- Added the `check()` method, which can select or deselect an element.\n- Added the `wait.has_rect()` method, used to wait for an element to have size and position.\n- Added the `to_center()` method for scrolling, which can scroll to the center of the viewport.\n- Added the `select.by_option()` and `select.cancel_by_option()` methods, which can select list item elements.\n- Added the `states.has_rect` attribute.\n- When an element is covered, the `states.is_covered` attribute returns the id of the covering element.\n- Added the `states.is_whole_in_viewport` attribute, used to determine if the entire element is within the viewport.\n- The `input()` method has added the `by_js` parameter.\n- The `rename` parameter of `save()` has been changed to `name`.\n- `get_src()` now supports blob type.\n- The path obtained by `css_path` is more accurate.\n- The `timeout` parameter for relative positioning is now default to `None`.\n- The `wait.delete()` method has been changed to `wait.deleted()`.\n- The `wait.disabled_or_delete()` method has been changed to `wait.disabled_or_deleted()`.\n- The `wait.display()` method has been changed to `wait.displayed()`.\n- Two elements can be compared using `==`.\n- Faster element searching speed.\n\n---\n\n## ✅️ Startup Configuration\n\n### 📌 Deleting the easy_set Method\n\nThe original purpose of easy_set was to conveniently modify the ini file settings.\n\nThe idea was to set it once and not have to call it again. However, perhaps due to unclear documentation, many people wrote it into their actual code.\n\nBecause modifying the ini file affects other projects, this is not a recommended practice by the author.\n\nMoreover, in practical use, it was found that easy_set did not become more convenient, and its functionality can be replaced with the `save()` method of the `ChromiumOptions` object.\n\nTherefore, it has been decided to delete it, which also eliminates the need to maintain an additional codebase.\n\n---\n\n### 📌 Support for Setting Experiment Flags\n\nVersion 4.0 adds support for setting experiment flags when launching the browser, i.e. the flags found in `'chrome://flags'`.\n\nUse the newly added `set_flag()` method to set the flags. Use `clear_flags_in_file()` to clear the flags already set in the configuration file.\n\nYou can find out which experiment flags are available by checking `'chrome://flags'`.\n\n**Example:**\n\n```python\nfrom DrissionPage import ChromiumOptions\n\nco = ChromiumOptions()\nco.set_flag('temporary-unexpire-flags-m118', '1')\nco.set_flag('disable-accelerated-2d-canvas') \n```\n\n---\n\n### 📌 Changes in the ini File\n\n- The `chrome_options` class has been changed to `chromium_options`.\n- The `binary_location` option has been changed to `browser_path`.\n- The `page_load_strategy` option has been changed to `load_mode`.\n- The `debugger_address` option has been changed to `address`.\n- The `arguments` option has removed the `'--remote-allow-origins=*'` parameter.\n- The `arguments` option has added the `'--no-default-browser-check'`, `'--disable-suggestions-ui'`, `'--disable-popup-blocking'`, `'--hide-crash-restore-bubble'`, `'--disable-features=PrivacySandboxSettings4'` parameters.\n- The `paths` class has added the `tmp_paht` option.\n- The `experimental_options` option has been removed.\n- The `chrome_options` class has added the `prefs`, `flags`, and `existing_only` options.\n- The `others` class has been added, which includes the `retry_times` and `retry_interval` options.\n\n---\n\n### 📌 Modifications to `ChromiumOptions`\n\n- Add `set_flag()` and `clear_flags_in_file()` methods to set experimental items.\n- Add `existing_only()` method and `is_existing_only` attribute to specify to only take over the browser without automatically starting a new one.\n- Add `ignore_certificate_errors()` method to ignore certificate errors.\n- Add `retry_times` and `retry_interval` attributes and `set_retry()` method to set retry parameters.\n- Add `incognito()` method to set incognito mode.\n- Add `set_tmp_path()` method to specify the temporary folder path.\n- Add `tmp_path` and `is_auto_port` attributes.\n- Add `tmp_path` parameter to `auto_port()` method.\n- Split `set_paths()` method into `set_browser_path()`, `set_local_port()`, `set_address()`, `set_download_path()`, `set_user_data_path()`, and `set_cache_path()` methods.\n- Change `set_page_load_strategy()` to `set_load_mode()`.\n- Change `set_headless()` to `headless()`.\n- Change `set_no_imgs()` to `no_imgs()`.\n- Change `set_no_js()` to `no_js()`.\n- Change `set_mute()` to `mute()`.\n- Change `debugger_address` to `address`.\n\n### 📌 Modification to `SessionOptions`\n\n- Change `set_paths()` method to `set_download_path()`.\n- Add `retry_times` and `retry_interval` attributes and `set_retry()` method to set retry parameters.\n\n---\n\n### 📌 Other\n\n- Automatically close the privacy statement when starting or taking over the browser.\n- Automatically use headless mode when starting the browser in a headless system, can be disabled with `set_headless(False)`.\n- When `set_headless(False)` but taking over a headless browser, close it and start a new headed browser.\n- `auto_port()` method supports multithreading.\n\n---\n\n## ✅️ Others\n\n### 📌 Delete 2.x code\n\n`MixPage` is the predecessor of `WebPage` and is based on Selenium.\n\nWith the iteration of version 3.x, our self-developed underlying technology has become mature and completely surpasses the old version. The old version has come to retirement.\n\nTo streamline the project and avoid the new code being restricted by old features, the old code has been deleted.\n\nDelete the following classes: `MixPage`, `DriverPage`, `DriverOptions`, `Drission`.\n\nThe old version has made great contributions to the author's growth, so it has been separated into a separate library to continue its life and commemorate the achievements it has made.\n\nYou can install the old version with the following command:\n\n```shell\npip install MixPage\n```\n\n---\n\n### 📌 Exception changes\n\n- Change `CallMethodError` to `CDPError`.\n- Change `ElementLossError` to `ElementLostError`.\n- Change `ContextLossError` to `ContextLostError`.\n- Change `TabClosedError` to `PageDisconnectedError`.\n- Add `WaitTimeoutError`.\n- Add `GetDocumentError`.\n- Add `WrongURLError`.\n- Add `StorageError`.\n- Add `CookieFormatError`.\n- Add `TargetNotFoundError`.\n\n---\n\n### 📌 Changes to Settings\n\n- Add `singleton_tab_obj` to set whether Tab objects allow multiple instances.\n- Change `raise_ele_not_found` to `raise_when_ele_not_found`.\n- Change `raise_click_failed` to `raise_when_click_failed`.\n\n---\n\n### 📌 More\n\n- Add `next_one` parameter to `handle_alert()` method to handle the next appearing alert.\n- `set.auto_handle_alert()` method added to browser page objects to automatically handle alerts.\n- Add `set.encoding()` method and `encoding` attribute to `SessionPage`.\n- `<option>` elements can now be clicked and operated as expected.\n- Add `timeout` parameter to `run_js()`, `run_js_loaded()`, and `run_async_js()` methods.\n- Remove `timeout` parameter from `run_async_js()` method.\n- Change `implicit` to `base` in `timeouts`.\n- Rename `ActionChains` to `Actions`.\n- Add `duration` parameter to movement methods of `Actions`.\n- Add `input()` method to `Actions`.\n- Allow `key_down()` and `key_up()` methods of `Actions` to receive key names as text.\n- Change `text` parameter to `keys` in `type()` method of `Actions`.\n- Add `name` attribute to `get_screenshot()` method to specify the file name.\n- Add `scroll_to_center` parameter to `get_screenshot()` method of elements to scroll to the center of the page before taking the screenshot.\n- `wait.new_tab()` method now returns the ID of the new tab upon success.\n- `tabs` does not include windows with F12.\n- `DrissionPage.common` path added `wait_until()` method to support custom combination of waiting conditions.\n- `DrissionPage.common` path added `get_blob()` method to get the specified blob content.\n\n---\n\n## ✅️ Next Steps\n\n- Supports monitoring data packets across tabs.\n- Simulates a mobile environment and supports touch operations.\n- Allows for changing the browser proxy at any time.\n- Researching remote control browser solutions.\n- Supports capturing WebSocket packets.\n- Supports operating plugin windows.\n- Displays mouse position when moving or clicking.\n\n---\n\n## ✅️ Speed up development progress\n\nThe advancement of this open-source project relies on your support. Donations are welcome to accelerate it.\n\n![](../imgs/code.jpg)\n\n"
  },
  {
    "path": "requirements.txt",
    "content": "requests\r\nlxml\r\ncssselect\r\nDownloadKit>=2.0.7\r\nwebsocket-client\r\nclick\r\ntldextract>=3.4.4\r\npsutil"
  },
  {
    "path": "setup.py",
    "content": "# -*- coding:utf-8 -*-\r\nfrom setuptools import setup, find_packages\r\nfrom DrissionPage import __version__\r\n\r\nwith open(\"README.md\", \"r\", encoding='utf-8') as fh:\r\n    long_description = fh.read()\r\n\r\nsetup(\r\n    name=\"DrissionPage\",\r\n    version=__version__,\r\n    author=\"g1879\",\r\n    author_email=\"g1879@qq.com\",\r\n    description=\"Python based web automation tool. It can control the browser and send and receive data packets.\",\r\n    long_description=long_description,\r\n    long_description_content_type=\"text/markdown\",\r\n    # license=\"BSD\",\r\n    keywords=\"DrissionPage\",\r\n    url=\"https://DrissionPage.cn\",\r\n    include_package_data=True,\r\n    packages=find_packages(),\r\n    zip_safe=False,\r\n    install_requires=[\r\n        'lxml',\r\n        'requests',\r\n        'cssselect',\r\n        'DownloadKit>=2.0.7',\r\n        'websocket-client',\r\n        'click',\r\n        'tldextract>=3.4.4',\r\n        'psutil'\r\n    ],\r\n    classifiers=[\r\n        \"Programming Language :: Python :: 3.6\",\r\n        \"Development Status :: 4 - Beta\",\r\n        \"Topic :: Utilities\",\r\n        # \"License :: OSI Approved :: BSD License\",\r\n    ],\r\n    python_requires='>=3.6',\r\n    entry_points={\r\n        'console_scripts': [\r\n            'dp = DrissionPage._functions.cli:main',\r\n        ],\r\n    },\r\n)\r\n"
  }
]