赞
踩
selenium
是网页应用中最流行的自动化测试工具,可以用来做自动化测试或者浏览器爬虫等。官网地址为:相对于另外一款web自动化测试工具QTP来说有如下优点:
首先需要安装python(推荐3.7+)环境,然后直接用pip install selenium
安装依赖包即可。
另外还需要下载浏览器相应的webdriver
驱动程序,注意下载的驱动版本一定要匹配浏览器版本。
下载以后可以把驱动程序加到环境变量,这样使用时就不用手动指定驱动程序路径。
可以在python中使用下面的代码启动一个Chrome
浏览器,然后控制这个浏览器的行为或者读取数据。
- from selenium import webdriver
-
- # 启动Chrome浏览器,要求chromedriver驱动程序已经配置到环境变量
- # 将驱动程序和当前脚本放在同一个文件夹也可以
- driver = webdriver.Chrome()
-
- # 手动指定驱动程序路径
- driver = webdriver.Chrome(r'D:/uusama/tools/chromedriver.exe')
-
- driver = webdriver.Ie() # Internet Explorer浏览器
- driver = webdriver.Edge() # Edge浏览器
- driver = webdriver.Opera() # Opera浏览器
- driver = webdriver.PhantomJS() # PhantomJS
-
- driver.get('http://uusama.com') # 打开指定路径的页面
启动的时候还可以设置启动参数,比如下面的代码实现启动时添加代理,并且忽略https
证书校验。
- from selenium import webdriver
-
- # 创建chrome启动选项对象
- options = webdriver.ChromeOptions()
-
-
- options.add_argument("--proxy-server=127.0.0.1:16666") # 设置代理
- options.add_argument("---ignore-certificate-errors") # 设置忽略https证书校验
- options.add_experimental_option("excludeSwitches", ["enable-logging"]) # 启用日志
-
- # 设置浏览器下载文件时保存的默认路径
- prefs = {"download.default_directory": get_download_dir()}
- options.add_experimental_option("prefs", prefs)
- driver = webdriver.Chrome(options=options)
启动的时候还可以设置启动参数,比如下面的代码实现启动时添加代理,并且忽略https
证书校验。
- from selenium import webdriver
-
- # 创建chrome启动选项对象
- options = webdriver.ChromeOptions()
-
-
- options.add_argument("--proxy-server=127.0.0.1:16666") # 设置代理
- options.add_argument("---ignore-certificate-errors") # 设置忽略https证书校验
- options.add_experimental_option("excludeSwitches", ["enable-logging"]) # 启用日志
-
- # 设置浏览器下载文件时保存的默认路径
- prefs = {"download.default_directory": get_download_dir()}
- options.add_experimental_option("prefs", prefs)
- driver = webdriver.Chrome(options=options)
一些非常有用的启动选项,下面使用的options = webdriver.ChromeOptions()
:
使用selenium打开页面以后,还不能立刻操作,需要等到待处理页面元素加载完成,这时就需要检测和等待页面元素加载。
time.sleep()
等待最简单的方法就是打开页面以后,使用time.sleep()
强制等待一定时间,该方法只能设置一个固定时间等待,如果页面提前加载完成,则会空等阻塞。
- from time import sleep
- from selenium import webdriver
-
- driver = webdriver.Chrome()
- driver.get('http://uusama.con')
- time.sleep(10)
- print('load finish')
另外还可以使用implicitly_wait设置最长等待时间,如果在给定时间内页面加载完成或者已经超时,才会执行下一步。该方法会等到所有资源全部加载完成,也就是浏览器标签栏的loading图表不再转才会执行下一步。有可能页面元素已经加载完成,但是js或者图片等资源还未加载完成,此时还需要等待。
另需注意使用implicitly_wait
只需设置一次,且对整个driver
生命周期都起作用,凡是遇到页面正在加载都会阻塞。
示例如下:
- from selenium import webdriver
-
- driver = webdriver.Chrome()
- driver.implicitly_wait(30) # 设置最长等30秒
- driver.get('http://uusama.com')
- print(driver.current_url)
-
- driver.get('http://baidu.com')
- print(driver.current_url)
使用WebDriverWait(selenium.webdriver.support.wait.WebDriverWait)能够更加精确灵活地设置等待时间,WebDriverWait可在设定时间内每隔一段时间检测是否满足某个条件,如果满足条件则进行下一步操作,如果超过设置时间还不满足,则抛出TimeoutException异常,其方法声明如下:
WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)
其中各参数含义如下:
driver
:浏览器驱动timeout
:最长超时时间,默认以秒为单位poll_frequency
:检测的间隔(步长)时间,默认为0.5秒
ignored_exceptions
:忽略的异常,即使在调用until()
或until_not()
的过程中抛出给定异常也不中断WebDriverWait()
一般配合until()
或until_not()
方法使用,表示等待阻塞直到返回值为True
或者False
,需要注意这两个方法的参数都需是可调用对象,即方法名称,可以使用expected_conditions
模块中的方法或者自己封装的方法。
- from selenium import webdriver
- from selenium.webdriver.common.by import By
- from selenium.webdriver.support.ui import WebDriverWait
- from selenium.webdriver.support import expected_conditions
-
- driver = webdriver.Chrome()
- driver.get("http://baidu.com")
-
- # 判断id为`input`的元素是否被加到了dom树里,并不代表该元素一定可见,如果定位到就返回WebElement
- element = WebDriverWait(driver, 5, 0.5).until(expected_conditions.presence_of_element_located((By.ID, "s_btn_wr")))
-
- # implicitly_wait和WebDriverWait都设置时,取二者中最大的等待时间
- driver.implicitly_wait(5)
-
- # 判断某个元素是否被添加到了dom里并且可见,可见代表元素可显示且宽和高都大于0
- WebDriverWait(driver,10).until(EC.visibility_of_element_located((By.ID, 'su')))
-
- # 判断元素是否可见,如果可见就返回这个元素
- WebDriverWait(driver,10).until(EC.visibility_of(driver.find_element(by=By.ID, value='kw')))
下面列出expected_conditions
常用的一些方法:
document
是否加载完成另外还可以使用driver.execute_script('return document.readyState;') == 'complete'
来检测document
是否加载完成。
注意document
加载完成,是不包括那种异步加载ajax请求动态渲染dom的,这种需要使用WebDriverWait
检测某个元素是否渲染完成。
selenium提供了一系列api方便获取chrome中的元素,这些API都返回WebElement
对象或其列表,如:
其实可以看WebDriver类里面的实现源码,其核心实现都是调用两个基本函数:
其中by
参数可以是ID
, CSS_SELECTOR
, CLASS_NAME
, XPATH
等。下面举几个简单的例子:
上面介绍的元素查找结果WebElement
对象,常用的api有:
如果找不到指定元素,则会抛出NoSuchElementException
异常,而且需要注意,display=none
的元素是可以获取到的,凡是在dom
节点中的元素都可以获取到。
而且实际使用的时候要注意一些js代码动态创建的元素,可能需要轮询获取或者监控。
一个检查是否存在指定元素的方法如下:
- def check_element_exists(xpath):
- try:
- driver.find_element_by_xpath(xpath)
- except NoSuchElementException:
- return False
- return True
ActionChains
动作链webdriver通过ActionChains
对象来模拟用户操作,该对象表示一个动作链路队列,所有操作会依次进入队列但不会立即执行,直到调用perform()
方法时才会执行。其常用方法如下:
下面代码模拟鼠标移动,点击,拖拽等操作,注意操作时需要等待一定时间,否则页面还来不及渲染。
- from time import sleep
- from selenium import webdriver
- # 引入 ActionChains 类
- from selenium.webdriver.common.action_chains import ActionChains
-
- driver = webdriver.Chrome()
- driver.get("https://www.baidu.cn")
- action_chains = ActionChains(driver)
-
- target = driver.find_element_by_link_text("搜索")
- # 移动鼠标到指定元素然后点击
- action_chains.move_to_element(target).click(target).perform()
- time.sleep(2)
-
- # 也可以直接调用元素的点击方法
- target.click()
- time.sleep(2)
-
- # 鼠标移动到(10, 50)坐标处
- action_chains.move_by_offset(10, 50).perform()
- time.sleep(2)
-
- # 鼠标移动到距离元素target(10, 50)处
- action_chains.move_to_element_with_offset(target, 10, 50).perform()
- time.sleep(2)
-
- # 鼠标拖拽,将一个元素拖动到另一个元素
- dragger = driver.find_element_by_id('dragger')
- action.drag_and_drop(dragger, target).perform()
- time.sleep(2)
-
- # 也可以使用点击 -> 移动来实现拖拽
- action.click_and_hold(dragger).release(target).perform()
- time.sleep(2)
- action.click_and_hold(dragger).move_to_element(target).release().perform()
通过send_keys
模拟键盘事件,常用有:
示例:定位到输入框,然后输入内容
- # 输入框输入内容
- driver.find_element_by_id("kw").send_keys("uusamaa")
-
- # 模拟回车删除多输入的一个字符a
- driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)
用于处理调用alert
弹出的警告框。
下面列出一些非常实用的浏览器控制api:
使用get_cookies
和add_cookie
可以实现将cookie缓存到本地,然后启动时加载,这样可以保留登录态。实现如下
- import os
- import json
- from selenium import webdriver
-
- driver = webdriver.Chrome()
- driver.get("https://www.baidu.cn")
-
- # 读取所有cookie并保存到文件
- cookies = driver.get_cookies()
- cookie_save_path = 'cookie.json'
- with open(cookie_save_path, 'w', encoding='utf-8') as file_handle:
- json.dump(cookies, file_handle, ensure_ascii=False, indent=4)
-
- # 从文件读取cookie并加载到浏览器
- with open(cookie_save_path, 'r', encoding='utf-8') as file_handle:
- cookies = json.load(file_handle)
- for cookie in cookies:
- driver.add_cookie(cookie)
使用driver.get(url)
会默认在第一个标签窗口打开指定连接,点击页面中的_blank
的链接时也会打开一个新的标签窗口。
还可以用下面的方式手动打开一个指定页面的标签窗口,需要注意打开新窗口或者关闭以后,还需要手动调用switch_to.window
切换当前活动的标签窗口,否则会抛出NoSuchWindowException
异常。
- from selenium import webdriver
-
- driver = webdriver.Chrome()
- driver.get("https://www.baidu.cn")
-
- new_tab_url = 'http://uusama.com'
- driver.execute_script(f'window.open("{new_tab_url}", "_blank");')
- time.sleep(1)
-
- # 注意:必须调用switch_to.window手动切换window,否则会找不到tab view
- # 聚焦到新打开的tab页面,然后关闭
- driver.switch_to.window(driver.window_handles[1])
- time.sleep(2)
- driver.close() # 关闭当前窗口
-
- # 手动回到原来的tab页面
- driver.switch_to.window(driver.window_handles[0])
- time.sleep(1)
除了使用execute_script外,还可以使用模拟打开新tab页按键的方式新建一个标签页窗口:
如果一个元素是隐藏的,即display=none
,虽然可以通过find_element
查找到该元素,但是用element.text
属性是获取不到该元素文本内容的,其值是空字符串,这时可以用下面的方式获取:
- element = driver.find_element_by_id('uusama')
- driver.execute_script("return arguments[0].textContent", element)
- driver.execute_script("return arguments[0].innerHTML", element)
-
- # 相应的也可以把隐藏的元素设置为非隐藏
- driver.execute_script("arguments[0].style.display = 'block';", element)
WebDriverException
异常处理比如在Chrome
中长时间运行一个页面会出现Out Of Memory
内存不足的错误,此时WebDriver
会抛出WebDriverException
异常,基本所有api都会抛出这个异常,这个时候需要捕获并进行特殊处理。
我的处理方式是记录页面的一些基本信息,比如url,cookie等,并定期写入到文件中,如果检测到该异常,则重启浏览器并且加载url和cookie等数据。
网上有通过driver.requests
或者通过解析日志来获取页面请求的方式,但是我感觉都不是很好使。最后使用mitmproxy
代理进行抓包处理,然后启动selenium
时填入代理来实现。
proxy.py
为在mitmproxy
基础上封装的自定义代理请求处理,其代码如下:
- import os
- import gzip
- from mitmproxy.options import Options
- from mitmproxy.proxy.config import ProxyConfig
- from mitmproxy.proxy.server import ProxyServer
- from mitmproxy.tools.dump import DumpMaster
- from mitmproxy.http import HTTPFlow
- from mitmproxy.websocket import WebSocketFlow
-
-
- class ProxyMaster(DumpMaster):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- def run(self, func=None):
- try:
- DumpMaster.run(self, func)
- except KeyboardInterrupt:
- self.shutdown()
-
-
- def process(url: str, request_body: str, response_content: str):
- # 抓包请求处理,可以在这儿转存和解析数据
- pass
-
-
- class Addon(object):
- def websocket_message(self, flow: WebSocketFlow):
- # 监听websockt请求
- pass
-
- def response(self, flow: HTTPFlow):
- # 避免一直保存flow流,导致内存占用飙升
- # flow.request.headers["Connection"] = "close"
- # 监听http请求响应,并获取请求体和响应内容
- url = flow.request.url
- request_body = flow.request
- response_content = flow.response
-
- # 如果返回值是压缩的内容需要进行解压缩
- if response_content.data.content.startswith(b'\x1f\x8b\x08'):
- response_content = gzip.decompress(response_content.data.content).decode('utf-8')
- Addon.EXECUTOR.submit(process, url, request_body, response_content)
-
-
- def run_proxy_server():
- options = Options(listen_host='0.0.0.0', listen_port=16666)
- config = ProxyConfig(options)
- master = ProxyMaster(options, with_termlog=False, with_dumper=False)
- master.server = ProxyServer(config)
- master.addons.add(Addon())
- master.run()
-
-
- if __name__ == '__main__':
- with open('proxy.pid', mode='w') as fin:
- fin.write(os.getpid().__str__())
- run_proxy_server()
在使用mitmproxy过程中,随着时间推移proxy.py会出现占用内存飙升的问题,在github的issue区有人也遇到过,有说是因为http连接keep-alive=true请求会一直保存不会释放,导致请求越多越占用内存,然后通过添加flow.request.headers["Connection"] = "close"来手动关闭连接,我加了以后有一定缓解,但还是不能从根本上解决。
最后通过写入proxy.pid记录代理程序进程,然后用另外一个程序定时重启proxy.py来解决内存泄漏的问题。
感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。