DrissionPage/DrissionPage/driver_element.py
2020-05-22 00:34:50 +08:00

252 lines
8.2 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.

# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
@File : driver_element.py
"""
from html import unescape
from pathlib import Path
from time import sleep
from typing import Union, List, Any
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait
from .common import DrissionElement, get_loc_from_str, translate_loc_to_xpath
from .config import OptionsManager
class DriverElement(DrissionElement):
"""driver模式的元素对象包装了一个WebElement对象并封装了常用功能"""
def __init__(self, ele: WebElement, timeout: float = 10):
super().__init__(ele)
self.timeout = timeout
def __repr__(self):
attrs = [f"{attr}='{self.attrs[attr]}'" for attr in self.attrs]
return f'<DriverElement {self.tag} {" ".join(attrs)}>'
@property
def attrs(self) -> dict:
"""返回元素所有属性及值"""
js = '''
var dom=arguments[0];
var names="{";
var len = dom.attributes.length;
for(var i=0;i<len;i++){
let it = dom.attributes[i];
let localName = it.localName;
let value = it.value;
names += "'" + localName + "':'" + value.replace(/'/g,"\\\\'") + "', ";
}
names+="}"
return names;
'''
return eval(self.run_script(js))
@property
def text(self) -> str:
"""元素内文本"""
return unescape(self.attr('innerText')).replace('\xa0', ' ')
@property
def html(self) -> str:
"""元素innerHTML"""
return unescape(self.attr('innerHTML')).replace('\xa0', ' ')
@property
def tag(self) -> str:
"""元素类型"""
return self._inner_ele.tag_name
@property
def parent(self):
"""父级元素"""
loc = 'xpath', './..'
return self.ele(loc, timeout=1, show_errmsg=False)
@property
def next(self):
"""下一个兄弟元素"""
loc = 'xpath', './following-sibling::*[1]'
return self.ele(loc, timeout=1, show_errmsg=False)
@property
def prev(self):
"""上一个兄弟元素"""
loc = 'xpath', './preceding-sibling::*[1]'
return self.ele(loc, timeout=1, show_errmsg=False)
def attr(self, attr: str) -> str:
"""获取属性值"""
if attr == 'text':
return self.text
else:
# return self.attrs[attr]
return self.inner_ele.get_attribute(attr)
def ele(self, loc_or_str: Union[tuple, str], mode: str = None, show_errmsg: bool = False, timeout: float = None):
"""根据loc获取元素或列表可用用字符串控制获取方式可选'id','class','name','tagName'
ele.find('id:ele_id')
"""
if isinstance(loc_or_str, str):
loc_or_str = get_loc_from_str(loc_or_str)
elif isinstance(loc_or_str, tuple) and len(loc_or_str) == 2:
loc_or_str = translate_loc_to_xpath(loc_or_str)
else:
raise ValueError('loc_or_str must be tuple or str.')
if loc_or_str[0] == 'xpath':
# 确保查询语句最前面是.
loc_str = f'.{loc_or_str[1]}' if not loc_or_str[1].startswith('.') else loc_or_str[1]
loc_or_str = loc_or_str[0], loc_str
timeout = timeout or self.timeout
return execute_driver_find(self.inner_ele, loc_or_str, mode, show_errmsg, timeout)
def eles(self, loc_or_str: Union[tuple, str], show_errmsg: bool = False, timeout: float = None):
"""根据loc获取子元素列表"""
return self.ele(loc_or_str, mode='all', show_errmsg=show_errmsg, timeout=timeout)
# -----------------以下为driver独占-------------------
def click(self, by_js=False) -> bool:
"""点击"""
if not by_js:
for _ in range(10):
try:
self.inner_ele.click()
return True
except Exception as e:
# print(e)
sleep(0.2)
# 若点击失败用js方式点击
# print('Click by JS.')
try:
self.run_script('arguments[0].click()')
return True
except:
raise
def input(self, value, clear: bool = True) -> bool:
"""输入文本"""
try:
if clear:
self.clear()
self.inner_ele.send_keys(value)
return True
except:
raise
def run_script(self, script: str) -> Any:
"""运行js"""
return self.inner_ele.parent.execute_script(script, self.inner_ele)
def submit(self) -> None:
"""提交表单"""
self.inner_ele.submit()
def clear(self) -> None:
"""清空元素"""
self.run_script("arguments[0].value=''")
# self.ele.clear()
def is_selected(self) -> bool:
"""是否选中"""
return self.inner_ele.is_selected()
def is_enabled(self) -> bool:
"""是否可用"""
return self.inner_ele.is_enabled()
def is_displayed(self) -> bool:
"""是否可见"""
return self.inner_ele.is_displayed()
def is_valid(self) -> bool:
"""用于判断元素是否还能用,应对页面跳转元素不能用的情况"""
try:
self.is_enabled()
return True
except:
return False
@property
def size(self) -> dict:
"""元素大小"""
return self.inner_ele.size
@property
def location(self) -> dict:
"""元素坐标"""
return self.inner_ele.location
def screenshot(self, path: str = None, filename: str = None) -> str:
"""元素截图"""
name = filename or self.tag
path = path or OptionsManager().get_value('paths', 'global_tmp_path')
if not path:
raise IOError('No path specified.')
Path(path).mkdir(parents=True, exist_ok=True)
# 等待元素加载完成
if self.tag == 'img':
js = 'return arguments[0].complete && typeof arguments[0].naturalWidth != "undefined" ' \
'&& arguments[0].naturalWidth > 0'
while not self.run_script(js):
pass
img_path = f'{path}\\{name}.png'
self.inner_ele.screenshot(img_path)
return img_path
def select(self, text: str) -> bool:
"""在下拉列表中选择"""
ele = Select(self.inner_ele)
try:
ele.select_by_visible_text(text)
return True
except:
return False
def set_attr(self, attr: str, value: str) -> bool:
"""设置元素属性"""
try:
self.run_script(f"arguments[0].{attr} = '{value}';")
return True
except:
raise
def execute_driver_find(page_or_ele: Union[WebElement, WebDriver], loc: tuple, mode: str = 'single',
show_errmsg: bool = False, timeout: float = 10) -> Union[DriverElement, List[DriverElement]]:
"""执行driver模式元素的查找
页面查找元素及元素查找下级元素皆使用此方法
:param page_or_ele: driver模式页面或元素
:param loc: 元素定位语句
:param mode: 'single''all'
:param show_errmsg: 是否显示错误信息
:param timeout: 查找元素超时时间
:return: 返回DriverElement元素或列表
"""
mode = mode or 'single'
if mode not in ['single', 'all']:
raise ValueError("mode must be 'single' or 'all'.")
msg = result = None
try:
wait = WebDriverWait(page_or_ele, timeout=timeout)
if mode == 'single':
msg = 'Element not found.'
result = DriverElement(wait.until(ec.presence_of_element_located(loc)))
elif mode == 'all':
msg = 'Elements not found.'
eles = wait.until(ec.presence_of_all_elements_located(loc))
result = [DriverElement(ele) for ele in eles]
return result
except:
if show_errmsg:
print(msg, loc)
raise
return [] if mode == 'all' else None