规避监测
Selenium + Chromedriver很多网站会识别到不是用正常浏览器执行的,请求会被拒绝,打开浏览器的开发者模式,在Console选项卡输入window.navigator.webdriver
普通的浏览器返回undefined
,通过selenium+chromedriver打开的浏览器返回true
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# 第一步,使用chrome开发者模式
options = Options()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
driver = webdriver.Chrome(options=options)
# 第三步,Selenium执行cdp命令
# 再次覆盖window.navigator.webdriver的值
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
复用浏览器
windows
设置浏览器的路径配置到path
启动命令chrome --remote-debugging-port=9222
mac
可以直接启动
启动命令/APPlications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote--debugging-port=9222
注意使用tab键,尽量不要手动输入
option = Options()
option.debugger_address = '127.0.0.1:9223' # 加入调试地址
driver1 = webdriver.Chrome(options=option) # 实例化driver对象,打开浏览器
多浏览器选择
第一种方式:
# mac和Linux执行:browser=firefox pytest pytest test_demo3.py
# windows执行:set browser=chrome pytest xxx.py,分行执行
browser = os.getenv("browser").lower()
if browser == 'headless':
self.deriver = webdriver.PhantomJS()
if browser == 'chrome':
self.deriver = webdriver.Chrome(options=option)
elif browser == 'firefox':
self.deriver = webdriver.Firefox(options=option)
第二种方式
driver = getattr(webdriver, 'chrome')(os.getcwd() + r'\chromedriver.exe')
设置静默模式
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
option = Options() # 实例化Options对象
option.add_argument('--headless') # 设置静默模式
option.add_argument('--disable-gpu') # 禁止打印日志
option.add_argument('log-level=3')
driver = webdriver.Chrome(os.getcwd() + r'\chromedriver.exe', options=option) # 设置浏览器为谷歌,并开启静默模式
等待
隐式等待
driver.implicitly_wait(20)
显示等待
显示等待有两种操作方式,一种是配合expected_conditions做判断,如果符合预期则执行后面的语句,否则报错,还有一种是使用lambda匿名函数
"""
title_is:判断当前页面的title是否完全等于(==)预期字符串,返回是布尔值
title_contains 判断当前页面的title是否包含预期字符串,返回布尔值
presence_of_element_located:判断某个元素是否被加到了dom树里,并不代表该元素一定可见
visibility_of_element_located : 判断某个元素是否可见. 可见代表元素非隐藏,并且元素的宽和高都不等于0
visibility_of :跟上面的方法做一样的事情,只是上面的方法要传入locator,这个方法直接传定位到的element就好了
presence_of_all_elements_located : 判断是否至少有1个元素存在于dom树中。举个例子,如果页面上有n个元素的class都是'column-md-3',那么只要有1个元素存在,这个方法就返回True
text_to_be_present_in_element : 判断某个元素中的text是否 包含 了预期的字符串
text_to_be_present_in_element_value:判断某个元素中的value属性是否 包含 了预期的字符串
frame_to_be_available_and_switch_to_it : 判断该frame是否可以switch进去,如果可以的话,返回True并且switch进去,否则返回False
invisibility_of_element_located : 判断某个元素中是否不存在于dom树或不可见
element_to_be_clickable : 判断某个元素中是否可见并且是enable的,这样的话才叫clickable
staleness_of :等某个元素从dom树中移除,注意,这个方法也是返回True或False
element_to_be_selected:判断某个元素是否被选中了,一般用在下拉列表
element_selection_state_to_be:判断某个元素的选中状态是否符合预期
element_located_selection_state_to_be:跟上面的方法作用一样,只是上面的方法传入定位到的element,而这个方法传入locator
alert_is_present : 判断页面上是否存在alert
"""
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions
WebDriverWait(driver, 10).until(expected_conditions.title_contains('龙珠'), message='元素未找到')
# 使用lambda匿名函数
WebDriverWait(driver, 10).until(lambda x: x.find_element(*('id', 'kw')), message='元素未找到').click()
对浏览器的基本操作
# 打开网址
driver.get('https://www.baidu.com')
# 浏览器最大化
driver.maximize_window()
#浏览器最小化
driver.minimize_window()
# 设置浏览器大小
driver.set_window_size(1200, 700)
# 刷新浏览器
driver.refresh()
# 回退
driver.back()
# 前进
driver.forward()
# 截图
driver.get_screenshot_as_file('./截图.png')
# 生成当前页面快照并保存
driver.save_screenshot("baidu.png")
# 退出当前窗口
driver.exit()
# 退出浏览器
driver.quit()
元素定位
基本元素定位
driver.find_element_by_id() # 采用标签内id属性进行定位
driver.find_element_by_name() # 采用标签内name属性进行定位
driver.find_element_by_class_name() # 采用标签内class属性进行定位
driver.find_element_by_xpath() # 采用xpath语法定位
driver.find_element_by_css_selector() # CSS属性定位可以比较灵活地选择控件的任意属性,定位方式也会比xpath快
driver.find_element_by_tag_name() # 通过标签名去定位
driver.find_element_by_link_text() # 通过完整的超链接文字定位
driver.find_element_by_partial_link_text() # 通过超链接部分文字定位
By定位
前提是需要导入By类:from selenium.webdriver.common.by import By
driver.find_element(By.ID,"kw")
driver.find_element(By.NAME,"wd")
driver.find_element(By.CLASS_NAME,"s_ipt")
driver.find_element(By.TAG_NAME,"input")
driver.find_element(By.LINK_TEXT,u" 新闻 ")
driver.find_element(By.PARTIAL_LINK_TEXT,u" 新 ")
driver.find_element(By.XPATH,"//*[@class='bg s_btn']")
driver.find_element(By.CSS_SELECTOR,"span.bg s_btn_wr>input#su")
By定位简化版
driver.find_element("id","")
driver.find_element("xpath","")
driver.find_element("link text","")
driver.find_element("partial link text","")
driver.find_element("name","")
driver.find_element("tag name","")
driver.find_element("class name","")
driver.find_element("css selector","")
elements复数定位
在上面的例举的八中基本定位方式种,都有对应的复数形式,这些复数定位方式每次取到的都是具有相同类型属性的一组元素,所以返回的是一个list队列
driver.find_elements_by_id()
driver.find_elements_by_name()
driver.find_elements_by_class_name()
driver.find_elements_by_tag_name()
driver.find_elements_by_link_text()
driver.find_elements_by_partial_link_text()
driver.find_elements_by_xpath()
driver.find_elements_by_css_selector()
JS的5种定位
上面的定位方式应该就基本够用了,但是有的时候就是会出现一些诡异的定位失效或者定位到了点击失效的问题,这个时候如果用js进行直接执行该事件,往往就可以解决那些诡异的事情
document.getElementById()
document.getElementsByName()
document.getElementsByTagName()
document.getElementsByClassName()
document.querySelectorAll()
只有id对象用的是Element返回是单个对象,其他都是Elements返回的是一个list这点千万要注意,具体用法和上面的webdriver基础定位一样。先写好对应的js语句,可以先赋值给一个变量,然后后调用execute_script进行执行一下js就好了
# 定义JS语句
search_js = "document.getElementsByName('wd')[0].value='selenium';"
search_js2 = "document.querySelectorAll('.s_ipt')[0].value='selenium';"
button_js = "document.getElementById('su').click();"
button_js2 = "document.getElementsByClassName('s_btn')[0].click()"
# 执行JS语句
driver.execute_script(search_js2)
driver.execute_script(button_js2)
jQuery定位
jQuery语法是为HTML元素的选取编制的,可以对元素执行一些具体的操作,基础语法是 ( s e l e c t o r ) . a c t i o n ( ) , (selector).action(), (selector).action(),符号定义jQuery,selector选择器用来查询具体的HTML元素,通过action()来执行对元素的具体操作。其中我们经常用到的action()在jq中有这么几种:
$(selector).val('input_value') # 其中input_value表示要输入的文本的值
$(selector).val('') # 如果为空,则执行后是清空的意思
$(selector).click() # 行为也是肯定有的
jQuery实际上可以说是JS原生语言的封装,简洁很多,在selenium中也是和JS一样调用
search_jq = "$('#kw').val('selenium')"
button_jq = "$('.s_btn').click()"
driver.execute_script(search_jq)
driver.execute_script(button_jq)
获取页面内容
# 获取浏览器当前url
url = driver.current_url
# 获取浏览器标题
title = driver.title
# 获取页面源码
page = driver.page_source
# 获取标签文本内容
txt = ele.text
# 如果text获取内容有问题,可以使用以下两种
txt = ele.get_attribute('innerText')
txt = ele.get_attribute('textContent')
# 获取元素标签
div = ele.tag_name
# 获取元素尺寸
size = ele.size
# 获取元素左上角坐标
loc = ele.location
# 获取标签属性值
css = ele.get_attribute('class')
# 获取输入框里面的文字
css = ele.get_attribute('value')
# 获取整个元素对应的HTML
css = ele.get_attribute('outerHTML')
# 获取指定元素内部的HTML,不包含本身的内容
css = ele.get_attribute('innerHTML')
选择框
from selenium.webdriver.support.ui import Select
se = Select(driver.find_element('id', '属性值')) # 创建Select类
se.select_by_index() # 根据排列顺序选择
se.select_by_value() # 根据value的属性值选择
se.select_by_visible_text() # 根据标签文本内容选择
se.deselect_all() # 清除所有选择
se.deselect_by_index() # 根据排列顺序清除选择
se.deselect_by_value() # 根据value的属性值清除选择
se.deselect_by_visible_text() # 根据标签文本内容清除选择
时间控件
js = document.getElementById('train_date').removeAttribute('readonly')
driver.execute_script(js)
鼠标操作ActionChains
from selenium.webdriver.common.action_chains import ActionChains
"""
只支持pc
click(on_element=None) ——单击鼠标左键
click_and_hold(on_element=None) ——点击鼠标左键,不松开
context_click(on_element=None) ——点击鼠标右键
double_click(on_element=None) ——双击鼠标左键
drag_and_drop(source, target) ——拖拽到某个元素然后松开
drag_and_drop_by_offset(source, xoffset, yoffset) ——拖拽到某个坐标然后松开
key_down(value, element=None) ——按下某个键盘上的键
key_up(value, element=None) ——松开某个键
move_by_offset(xoffset, yoffset) ——鼠标从当前位置移动到某个坐标
move_to_element(to_element) ——鼠标移动到某个元素
move_to_element_with_offset(to_element, xoffset, yoffset) ——移动到距某个元素(左上角坐标)多少距离的位置
perform() ——执行链中的所有动作
release(on_element=None) ——在某个元素位置松开鼠标左键
send_keys(*keys_to_send) ——发送某个键到当前焦点的元素
send_keys_to_element(element, *keys_to_send) ——发送某个键到指定元素
"""
ac = ActionChains(driver) # 生成动作链对象
ac.click(driver.find_element('id', 'none')).perform() # 鼠标左键单击
ac.context_click(driver.find_element('id', 'none')).perform() # 鼠标右键单击
ac.double_click(driver.find_element('id', 'none')).perform() # 鼠标左键双击
ac.click_and_hold(driver.find_element('id', 'none')).perform() # 鼠标长按
ac.move_to_element(driver.find_element('id', 'none')).perform() # 鼠标悬浮,按元素定位
d = driver.find_element('id', 'none')
e = driver.find_element('id', 'none')
ac.move_by_offset(d.location['x'], d.location['y']).perform() # 鼠标悬浮,按元素坐标定位
ac.drag_and_drop(d, e).perform() # 将元素d,拖拽到目标元素e处,一般只能拖到目标元素的中间
ac.drag_and_drop_by_offset(d,
e.location['x'] - e.size['width'],
e.location['y']).perform() # 将元素d,拖到目标元素e的终点坐标,需要窗口最大化
# 拖拽元素
ac.click_and_hold(d) # 点击长按指定的标签
for i in range(5): # 将动作链用5次动作到终点,每个动作中间间隔0.5秒
ac.move_by_offset(17, 0).perform() # move_by_offset(x,y):x水平方向 y竖直方向
time.sleep(0.5)
ac.release() # 释放动作链
鼠标操作TouchActions
使用该方法,需要绕过w3c验证,否则会报错
from selenium.webdriver import TouchActions
"""
支持app和pc
double_tap 双击
flick 滑动
flick_element 从某个元素位置开始滑动
long_press 长按
move 手势移动指定偏移
Perform 执行
release 释放手势
scroll 点击并滚动
scroll_from_element 从某个元素位置开始手势点击并滚动(向下滑动为负数,向上滑动为正数)
flick_element——从某个元素位置开始手势滑动(负数:向上滑动,正数:向下滑动)
tap 在指定元素上点击
tap_and_hold 在指定元素上点击但不释放
"""
# 让过w3c验证
option = Options()
option.add_experimental_option('w3c', False)
browser = os.getenv("browser").lower()
deriver = webdriver.Chrome(options=option)
driver.get("http://www.baidu.com")
input = driver.find_element_by_id("kw")
search = driver.find_element_by_id("su")
input.send_keys("test")
action = TouchActions(driver)
action.tap(search).perform() # 点击元素,立即执行
action.scroll_from_element(input, 0, 10000).perform() # 滚动条滚动到底部
next = driver.find_element_by_link_text("下一页 >").click()
键盘操作
from selenium.webdriver.common.keys import Keys
ac.send_keys(Keys.ENTER).perform() # 回车键
d.send_keys(Keys.CONTROL, 'c') # 复制
d.send_keys(Keys.CONTROL, 'v') # 粘贴
弹出框
driver.switch_to.alert.accept() # 点击弹出框的确定按钮
txt = driver.switch_to.alert.text # 获取弹出框的文本
driver.switch_to.alert.dismiss() # 点击弹出框的关闭按钮
driver.switch_to.alert.send_keys('') # 在弹出框内输入内容
iframe操作
driver.switch_to.frame('iframe定位') # 切进iframe
driver.switch_to.default_content() # 切换到默认的,一般用来操作切出iframe
driver.switch_to.parent_frame('父级定位') # 切换有父级iframe的页面
driver.switch_to.parent_frame('子级定位')
浏览器多窗口切换
cur = driver.current_window_handle # 获取当前所在窗口
all = driver.window_handles # 获取当前浏览器所有窗口
e = driver.switch_to.window(all[1]) # 第一种切换方式,切换到指定窗口
for h in all: # 第二种切换方式,通过循环所有的窗口句柄,判断如果不是当前窗口,就切换到新窗口
if cur != h:
driver.switch_to.window(h)
cookie绕过验证码
driver.get_cookies() # 获取所有的cookies信息,返回列表
driver.add_cookie({"": ""}) # 登录时添加cookie,可以绕过验证码
执行JS语句
# 滚动条操作
driver.execute_script('window.scrollTo(0,document.body.scrollHeight)') # 滚动到底部
driver.execute_script('window.scrollTo(0,0,document.body.scrollTop=0)') # 滚动到顶部
# 处理只读的场景
js = "document.getElementById('username').removeAttribute('disabled')" # 定位到元素后删除只读设置
driver.execute_script(js)
# 处理隐藏文本的输入
js = "document.getElementById('password').style.display='block'" # 定位隐藏框的元素,并将隐藏改为显示
driver.execute_script(js)
# 自动化执行步骤高亮显示
js = "document.getElementById('password').setAttribute('style','border:3px solid red')" # 执行前将该元素添加红色边框
driver.execute_script(js)
# 操作滚动条,第一个横向滚动条,第二个纵向滚动条,值为像素
driver.execute_script('window.scrollTo(0,500)')
判断是否禁用、可见、选中
driver.find_element().is_enabled() # 是否可读
driver.find_element().is_displayed() # 是否可见
driver.find_element().is_selected() # 是否可选中
文件上传
# input元素的标签直接使用send_keys()上传
driver.find_element(By.ID, 'id值').send_keys('文件路径')
# 非input标签,需要先安装pywinauto
from selenium import webdriver
import time
import pywinauto
driver = webdriver.Chrome()
driver.get('https://www.jq22.com/yanshi17984')
driver.switch_to.frame("iframe")
driver.find_element_by_class_name('addImg').click()
time.sleep(2)
# 通过窗口打开
app = pywinauto.Desktop()
# 通过弹框名称进入控件中
win = app['打开']
# 输入上传图片的地址
win['Edit'].type_keys(r'E:\web\123.jpg')
#点击打开按钮
win['Button'].click()
分布式应用
hub配置
配置Java环境
安装需要运行的浏览器
chromedriver 配置到环境变量
配置Python和安装selenium
官网下载grid:https://www.selenium.dev/downloads/
执行命令
配置JSON文件
java -jar selenium-server-standalone.jar -role hub -hubConfig hubconfig.json
未配置JSON文件
java -jar selenium-server-standalone-3.141.59.jar -role hub
JSON文件内容
{
"port": 4444,
"newSessionWaitTimeout": -1,
"servlets" : [],
"withoutServlets": [],
"custom": {},
"capabilityMatcher": "org.openqa.grid.internal.utils.DefaultCapabilityMatcher",
"registry": "org.openqa.grid.internal.DefaultGridRegistry",
"throwOnCapabilityNotPresent": true,
"cleanUpCycle": 5000,
"role": "hub",
"debug": false,
"browserTimeout": 0,
"timeout": 1800
}
node配置
配置Java环境
安装需要运行的浏览器
下载正确版本的driver 配置到环境变量
安装Python和selenium
官网下载grid:https://www.selenium.dev/downloads/
配置JSON文件
将节点机注册到hub控制机
java -jar selenium-server-standalone-3.141.59.jar -role node -nodeConfig node.json
JSON文件内容
{
"capabilities":
[
{
"browserName": "firefox",
"marionette": true,
"maxInstances": 5,
"seleniumProtocol": "WebDriver"
},
{
"browserName": "chrome",
"maxInstances": 5,
"seleniumProtocol": "WebDriver"
},
{
"browserName": "internet explorer",
"platform": "WINDOWS",
"maxInstances": 1,
"seleniumProtocol": "WebDriver"
},
{
"browserName": "safari",
"technologyPreview": false,
"platform": "MAC",
"maxInstances": 1,
"seleniumProtocol": "WebDriver"
}
],
"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
"maxSession": 5,
"port": -1,
"register": true,
"registerCycle": 5000,
"hub": "http://localhost:4444",
"nodeStatusCheckTimeout": 5000,
"nodePolling": 5000,
"role": "node",
"unregisterIfStillDownAfter": 60000,
"downPollingLimit": 2,
"debug": false,
"servlets" : [],
"withoutServlets": [],
"custom": {}
}
编写脚本并执行
from selenium.webdriver import DesiredCapabilities
from selenium.webdriver import Remote
class TestGrid:
def test_grid(self):
hub_url = 'http://127.0.0.1:4444/wd/hub' # 指定hub控制机的地址
capa = DesiredCapabilities.CHROME.copy() # 指定浏览器
driver = Remote(command_executor=hub_url, desired_capabilities=capa) # 初始化,创建实例,远程连接hub
driver.get('https://www.baidu.com') # 打开网页
PO模式
- 方法意义
- 用公共方法代表UI所提供的功能
- 方法应该返回其他的PageObject或者返回用于断言的数据
- 同样的行为不同的结果可以建模为不同的方法
- 不要在方法内加断言
- 字段意义
- 不要暴露页面内部的元素给外部
- 不需要建模UI内的所有元素