DrissionPage/DrissionPage/shadow_root_element.py

290 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from re import split as re_SPLIT
from typing import Union, Any, Tuple
from selenium.webdriver.remote.webelement import WebElement
from .common import format_html, DrissionElement
from .driver_element import execute_driver_find, DriverElement
class ShadowRootElement(DrissionElement):
def __init__(self, inner_ele: WebElement, parent_ele: DriverElement):
super().__init__(inner_ele, parent_ele.page)
self.parent_ele = parent_ele
def __repr__(self):
return f'<ShadowRootElement in {self.parent_ele} >'
def __call__(self,
loc_or_str: Union[Tuple[str, str], str],
mode: str = 'single',
timeout: float = None):
"""实现查找元素的简化写法 \n
ele2 = ele1('@id=ele_id') \n
:param loc_or_str: 元素的定位信息可以是loc元组或查询字符串
:param mode: 'single''all',对应查找一个或全部
:param timeout: 超时时间
:return: DriverElement对象
"""
return self.ele(loc_or_str, mode, timeout)
@property
def tag(self):
"""元素标签名"""
return 'shadow-root'
@property
def html(self):
"""内部html文本"""
return format_html(self.inner_ele.get_attribute('innerHTML'))
@property
def parent(self):
"""shadow-root所依赖的父元素"""
return self.parent_ele
def parents(self, num: int = 1):
"""返回上面第num级父元素 \n
:param num: 第几级父元素
:return: DriverElement对象
"""
loc = 'xpath', f'.{"/.." * (num - 1)}'
return self.parent_ele.ele(loc, timeout=0.1)
@property
def next(self):
"""返回后一个兄弟元素"""
return self.nexts()
def nexts(self, num: int = 1):
"""返回后面第num个兄弟元素 \n
:param num: 后面第几个兄弟元素
:return: DriverElement对象
"""
loc = 'css selector', f':nth-child({num})'
return self.parent_ele.ele(loc, timeout=0.1)
def ele(self,
loc_or_str: Union[Tuple[str, str], str],
mode: str = 'single',
timeout: float = None):
"""返回当前元素下级符合条件的子元素,默认返回第一个 \n
示例: \n
- 用loc元组查找 \n
ele.ele((By.CLASS_NAME, 'ele_class')) - 返回第一个class为ele_class的子元素 \n
- 用查询字符串查找: \n
查找方式属性、tag name和属性、文本、css selector \n
@表示属性,.表示class#表示id=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n
ele.ele('.ele_class') - 返回所有 class 为 ele_class 的子元素 \n
ele.ele('.:ele_class') - 返回所有 class 中含有 ele_class 的子元素 \n
ele.ele('#ele_id') - 返回所有 id 为 ele_id 的子元素 \n
ele.ele('#:ele_id') - 返回所有 id 中含有 ele_id 的子元素 \n
ele.ele('@class:ele_class') - 返回第一个class含有ele_class的子元素 \n
ele.ele('@name=ele_name') - 返回第一个name等于ele_name的子元素 \n
ele.ele('@placeholder') - 返回第一个带placeholder属性的子元素 \n
ele.ele('tag:p') - 返回第一个<p>子元素 \n
ele.ele('tag:div@class:ele_class') - 返回第一个class含有ele_class的div子元素 \n
ele.ele('tag:div@class=ele_class') - 返回第一个class等于ele_class的div子元素 \n
ele.ele('tag:div@text():some_text') - 返回第一个文本含有some_text的div子元素 \n
ele.ele('tag:div@text()=some_text') - 返回第一个文本等于some_text的div子元素 \n
ele.ele('text:some_text') - 返回第一个文本含有some_text的子元素 \n
ele.ele('some_text') - 返回第一个文本含有some_text的子元素等价于上一行 \n
ele.ele('text=some_text') - 返回第一个文本等于some_text的子元素 \n
ele.ele('css:div.ele_class') - 返回第一个符合css selector的子元素 \n
:param loc_or_str: 元素的定位信息可以是loc元组或查询字符串
:param mode: 'single''all',对应查找一个或全部
:param timeout: 查找元素超时时间
:return: DriverElement对象
"""
if isinstance(loc_or_str, str):
loc_or_str = str_to_css_loc(loc_or_str)
elif isinstance(loc_or_str, tuple) and len(loc_or_str) == 2:
if loc_or_str[0] == 'xpath':
raise ValueError('不支持xpath')
else:
raise ValueError('Argument loc_or_str can only be tuple or str.')
if loc_or_str[0] == 'css selector':
return execute_driver_find(self, loc_or_str, mode, timeout)
elif loc_or_str[0] == 'text':
return self._find_eles_by_text(loc_or_str[1], loc_or_str[2], loc_or_str[3], mode)
def eles(self,
loc_or_str: Union[Tuple[str, str], str],
timeout: float = None):
"""返回当前元素下级所有符合条件的子元素 \n
示例: \n
- 用loc元组查找 \n
ele.eles((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n
- 用查询字符串查找: \n
查找方式属性、tag name和属性、文本、css selector \n
@表示属性,.表示class#表示id=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n
ele.eles('.ele_class') - 返回所有 class 为 ele_class 的子元素 \n
ele.eles('.:ele_class') - 返回所有 class 中含有 ele_class 的子元素 \n
ele.eles('#ele_id') - 返回所有 id 为 ele_id 的子元素 \n
ele.eles('#:ele_id') - 返回所有 id 中含有 ele_id 的子元素 \n
ele.eles('@class:ele_class') - 返回所有class含有ele_class的子元素 \n
ele.eles('@name=ele_name') - 返回所有name等于ele_name的子元素 \n
ele.eles('@placeholder') - 返回所有带placeholder属性的子元素 \n
ele.eles('tag:p') - 返回所有<p>子元素 \n
ele.eles('tag:div@class:ele_class') - 返回所有class含有ele_class的div子元素 \n
ele.eles('tag:div@class=ele_class') - 返回所有class等于ele_class的div子元素 \n
ele.eles('tag:div@text():some_text') - 返回所有文本含有some_text的div子元素 \n
ele.eles('tag:div@text()=some_text') - 返回所有文本等于some_text的div子元素 \n
ele.eles('text:some_text') - 返回所有文本含有some_text的子元素 \n
ele.eles('some_text') - 返回所有文本含有some_text的子元素等价于上一行 \n
ele.eles('text=some_text') - 返回所有文本等于some_text的子元素 \n
ele.eles('css:div.ele_class') - 返回所有符合css selector的子元素 \n
:param loc_or_str: 元素的定位信息可以是loc元组或查询字符串
:param timeout: 查找元素超时时间
:return: DriverElement对象组成的列表
"""
return self.ele(loc_or_str, mode='all', timeout=timeout)
def run_script(self, script: str, *args) -> Any:
"""执行js代码传入自己为第一个参数 \n
:param script: js文本
:param args: 传入的参数
:return: js执行结果
"""
return self.inner_ele.parent.execute_script(script, self.inner_ele, *args)
def is_enabled(self) -> bool:
"""是否可用"""
return self.inner_ele.is_enabled()
def is_valid(self) -> bool:
"""用于判断元素是否还能用,应对页面跳转元素不能用的情况"""
try:
self.is_enabled()
return True
except:
return False
def _find_eles_by_text(self, text: str, tag: str = '', match: str = 'exact', mode: str = 'single'):
"""根据文本获取页面元素 \n
:param text: 文本字符串
:param tag: tag name
:param match: 'exact''fuzzy',对应精确或模糊匹配
:param mode: 'single''all',对应匹配一个或全部
:return: 返回DriverElement对象或组成的列表
"""
# 获取所有元素
eles = self.run_script('return arguments[0].querySelectorAll("*")')
results = []
# 遍历所有元素,找到符合条件的
for ele in eles:
if tag and tag != ele.tag_name:
continue
txt = self.page.driver.execute_script(
'if(arguments[0].firstChild!=null){return arguments[0].firstChild.nodeValue}', ele)
txt = txt or ''
# 匹配没有文本的元素或精确匹配
if text == '' or match == 'exact':
if text == txt:
if mode == 'single':
return DriverElement(ele, self.page)
elif mode == 'all':
results.append(DriverElement(ele, self.page))
# 模糊匹配
elif match == 'fuzzy':
if text in txt:
if mode == 'single':
return DriverElement(ele, self.page)
elif mode == 'all':
results.append(DriverElement(ele, self.page))
return None if mode == 'single' else results
def str_to_css_loc(loc: str) -> tuple:
"""处理元素查找语句 \n
查找方式属性、tag name及属性、文本、css selector \n
@表示属性,.表示class#表示id=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n
示例: \n
.ele_class - class为ele_class的元素 \n
.:ele_class - class含有ele_class的元素 \n
#ele_id - id为ele_id的元素 \n
#:ele_id - id含有ele_id的元素 \n
@class:ele_class - class含有ele_class的元素 \n
@class=ele_class - class等于ele_class的元素 \n
@class - 带class属性的元素 \n
tag:div - div元素 \n
tag:div@class:ele_class - class含有ele_class的div元素 \n
tag:div@class=ele_class - class等于ele_class的div元素 \n
tag:div@text():search_text - 文本含有search_text的div元素 \n
tag:div@text()=search_text - 文本等于search_text的div元素 \n
text:search_text - 文本含有search_text的元素 \n
text=search_text - 文本等于search_text的元素 \n
css:div.ele_class - 符合css selector的元素 \n
"""
loc_by = 'css selector'
# .和#替换为class和id查找
if loc.startswith('.'):
if loc.startswith(('.=', '.:',)):
loc = loc.replace('.', '@class', 1)
else:
loc = loc.replace('.', '@class=', 1)
if loc.startswith('#'):
if loc.startswith(('#=', '#:',)):
loc = loc.replace('#', '@id', 1)
else:
loc = loc.replace('#', '@id=', 1)
# 根据属性查找
if loc.startswith('@'):
r = re_SPLIT(r'([:=])', loc[1:], maxsplit=1)
if len(r) == 3:
mode = '=' if r[1] == '=' else '*='
loc_str = f'*[{r[0]}{mode}{r[2]}]'
else:
loc_str = f'*[{loc[1:]}]'
# 根据tag name查找
elif loc.startswith(('tag=', 'tag:')):
if '@' not in loc[4:]:
loc_str = f'{loc[4:]}'
else:
at_lst = loc[4:].split('@', maxsplit=1)
r = re_SPLIT(r'([:=])', at_lst[1], maxsplit=1)
if len(r) == 3:
if r[0] == 'text()':
match = 'exact' if r[1] == '=' else 'fuzzy'
return 'text', r[2], at_lst[0], match
mode = '=' if r[1] == '=' else '*='
loc_str = f'{at_lst[0]}[{r[0]}{mode}"{r[2]}"]'
else:
loc_str = f'{at_lst[0]}[{r[0]}]'
# 用css selector查找
elif loc.startswith(('css=', 'css:')):
loc_str = loc[4:]
# 用xpath查找
elif loc.startswith(('xpath=', 'xpath:')):
raise ValueError('不支持xpath')
# 根据文本查找
elif loc.startswith(('text=', 'text:')):
match = 'exact' if loc[4] == '=' else 'fuzzy'
return 'text', loc[5:], '', match
# 根据文本模糊查找
else:
return 'text', loc, '', 'fuzzy'
return loc_by, loc_str