读后笔记 -- Python 全栈测试开发 Chapter3:Selenium
3.1 HTML
3.1.2 HTML 元素
HTML 元素分为三种类型:
类型 | 常用的元素 | ||
块状元素 | 该元素的内容相对于前后元素内容另起一行 |
可实现自定义高度和宽度,常作为其他元素的容器, 可实现内联元素和其他块状元素的包含操作 |
div, dl, menu, dt, dd, ol, ul, h1-h6, p, form, hr, table, tr, td 等 |
内联元素 | 该元素的内容与其前后元素的内容在一行显示 | 以行内的形式逐个显示,没有自己的形状, |
a, br, select, u, span, i, em, strong, img, input, b 等 |
可变元素 | 需要根据上下文关系来确定是 块状元素 还是 内联元素 |
applet, button, script, object, map, iframe, del, ins 等 |
<!-- ul 和 ol,其中 ol 标签表示的是有序列表,ul 表示的是无序列表 -->
<!-- span 是用于组合行内元素,便于格式化 -->
3.1.7 HTML 页面
<html> <head> <title>Combine all elements in an entire HTML Page</title> </header>
<body> <!-- Title --> <div id="header"> <h1>Welcome to Wood Programming</h1> </div> <p>This is wood programming's QR code</p> <!-- This is wood programming's QR code --> <div id="image"> <img src="wood.jpg" height="200" width="200"> </div> <!-- embed iframe frame --> <p>You can baidu if you want to learn more</p> <iframe src="https://www.bing.com" name="iframe_1" width="600" height="300"></iframe> <!-- Do a simple investigation --> <form action="success" method="post"> <!-- input, password --> <div> Username: <br/><input type="text" /><br/> Age: <br/><input type="text" /><br/> <!-- link has some problem, can't connect --> 验证码:<input type="text" /><img src="http://123.57.71.195:8787/index.php/home/seccode/makecode.html?1591430678772" /><br/> <input type="text"/><br/> </div> </form> <div> which language do you like: <br/> Java<input type="checkbox" /><br/> Python<input type="checkbox" /><br/> Other<input type="checkbox" /><br/> sex: Male<input type="radio" name="sex" /> Female<input type="radio" name="sex" /><br/> Graduation: <select> <option>Not Selected</option> <option>Doctor</option> <option>Bachlor</option> <option>Colleage</option> </select><br/><br/> Note:<textarea clos="10" rows="6"></textarea><br/> <input type="submit" value="submit" onclick="javascript:alert('Confirm to submit?')"/> <input type="reset" value="reset"/> <input type="button" value="confirm"/> </div> </body> </html>
3.2 CSS
1. HTML5体系主要由 HTML5、CSS3 和 JS 等技术构成。
2. CSS:表现 HTML 或 XML 等文件样式。可以控制页面的整体样式,HTML 标签设计 只能用于定义文档内容,过多的属性都是通过 css 进行完成。简单来说,CSS 使用一系列标签完成 HTML 中文档内容的格式化操作。
css 的作用:1)实现 html 样式表的定义;2)解决了内容与表现分离问题;3)外部样式表可以极大的提高工作效率(所有的样式表都存储在外部的css文件中)
3. CSS 将样式层叠为一个,样式不仅可以定义在单个 HTML 元素中,也可以定义在一个外部 CSS 文件中,甚至可以在同一 HTML 文档中引用多个外部样式表。
4. CSS 语法结构:选择器和声明。如:p {text-align:center; color:yellow;}
5. CSS 对空格无影响,大小写不敏感。但涉及 HTML 融合,class 和 id 对大小写敏感。
6. CSS 高级语法:
备注:相关的 css 定义放在 <style> </style> 内
选择器的分组 |
h1,h2,h3 { color: red; } |
分组选择器用 “,” 隔开 |
选择器的继承 |
body { font-family: arial, sans-serif; } p { font-family: Times, "Times New Roman", serif; } |
1. 通过继承,子元素将 继承 body 的所有属性 2. 如使用其他属性,可额外定义一个,如 p |
派生选择器 |
li strong { font-style: italic; font-weight: normal; } |
<p><strong>我是粗体,因为我不在列表中,规则无效</strong></p> <li><strong>我是斜体,因为strong在li 中</strong></li> # 第二行将是斜体,因为 strong 在 li 元素内 |
后代选择器 |
语法: 祖先元素 后代元素 { } 如: h1 em {color: red;} |
作用于 N 代元素 |
子元素选择器 |
语法: 父元素>子代元素 { } 如: div>ul {color: red;} |
仅作用于第 1 代元素 |
3.3 Selenium 基础
1. Selenium 框架的构成
Selenium IDE Firefox 的一个插件,可录制生成脚本。脚本可回放或转成其他语言 |
Selenium RC -- Client Libraries 通过编写测试脚本来控制 Selenium Server 库 -- Selenium Server 主要负责控制浏览器的行为 -- Launcher 启动浏览器,然后将 Selenium Core 加载到浏览器页面 -- Http Proxy 完成对应浏览器的代理设置 -- Core 一系列 JS 函数的集合,只有通过 JS 才可以实现用程序对浏览器进行相应的操作 |
Selenium WebDriver Selenium RC 的升级版,基于 RC 的再次封装,可直接发送命令给浏览器 |
Selenium Grid 用于不同机器、浏览器的并行测试 |
selenium 的基础知识(selenium探秘【SeleniumRC的组成和SeleniumRC的有优缺点】)link: https://blog.csdn.net/Liuyanan990830/article/details/124691851
2. Firefox 安装 Selenium IDE
- Firefox browser: key in "about:addons" in web
- search "selenium" after 'Find more add-ons'
- click the find out IDE to the detail then 'Add to Firefox'
- after installation, there is a
icon on Firefox
selenium IDE 的主要用途:可以辅助 selenium 脚本的开发,录制时会录制相关的元素定位。
3.4 Selenium 元素定位
定位类别 | 元素 | 示例 |
1. 8种基本元素定位 |
1) id |
# 百度搜索框 |
2) name | ||
3) class_name | ||
4) tag_name | ||
5) link_text | ||
6) partial_link_text | ||
7) xpath |
7.1)绝对路径 /html/body/div[1]/div/div/div/div/div/ul/li[1]/input 7.2)相对路径 - 7.2.1)属性定位: //标签名[@属性名=属性值] //input[@name='uname'] - 7.2.2)多属性定位+逻辑运算符(and / or /not)://标签名[@属性名=属性值] //input[@name='uname' and @pwd='upasswd'] 一般属性不超过2个 - 7.2.3)嵌入函数 a) text 函数: //标签名[text()=文本内容] b) contains 函数: //标签名[contains(@属性名, 对应属性名的部分值)] c) starts-with 函数: //标签名[starts-with(@属性名, 对应属性名的前面部分值)] d) ends-with 函数: //标签名[ends-with(@属性名, 对应属性名的后面部分值)] |
|
8) css |
通过选择器(具体看书 P68-69)如: driver.find_element_by_css_selector("div>h3>a") 常使用的有:.class(class定位)/ #id (id定位)/ :first-child / :nth-child(n) / :last-child |
|
2. 复数定位 |
find_elements_by_id() / name() / class_name() / tag_name() / link_text() / partial_link_text() / xpath() / css_selector() |
1)返回的是列表数据类型,可通过索引取出具体的元素; 2)pop() / pop(-1):获取最后一个元素, pop(2):获取第三个元素 如: driver.find_elements("css selector", ".manv").pop().click() |
3. By 定位 |
find_element(By.ID, "属性值") / By.NAME / By. CLASS_NAME By.TAG_NAME / By.LINK_TEXT / By.PARTIAL_LINK_TEXT / By.XPATH / By.CSS_SELECTOR |
|
4. 父子定位、二次定位 |
父子定位:往上找可定位的父元素 二次定位:先定位到可定位的一级标签,作为基准再次定位 |
父子定位: driver.find_element(By.XPATH, "//div[@class='total_bodyer']/div[3]/p") 二次定位: driver.find_element_by_id("s-top-left").find_element_by_link_text('贴吧').click() |
5. JS 定位 |
应用于:基本方式无法定位,如 Windows 窗口、浏览器滚动条等 常用的定位方式:
*document 表示当前 HTML 对象 API: https://developer.mozilla.org/zh-CN/docs/Web/API |
# 百度左上角 hao123 |
6. jQuery 定位 |
应用于较复杂的页面,前面几种定位方式都无法处理。 1. 两种方式: 1)通过 jQuery 选择器完成元素的选择操作,可获取一个或一组元素; 2)通过 jQuery 遍历选择元素(常用于较复杂的层级元素) *手册:https://www.w3school.com.cn/jquery/jquery_ref_selectors.asp 2. 文本赋值使用的该方法的value属性,如是按钮,直接使用click()方法; 3. 如已定位到对应的对象,但无法完成某些事件的操作,则可通过该对象调用 标签声明的操作对象的属性值(js脚本),然后execute_script该js脚本 |
# 百度输入框输入 ‘jQuery test’
element_jquery_search_input = """$('input[name="wd"]').val('jQuery test')""" ![]() |
7. 隐藏元素的操作 |
1)通过 JS 定位到元素,获取对象; 2)removeAttribute 和 setAttribute 使其处于显示状态 |
# 修改属性值 driver.get("http://192.168.2.211:30020/index.html#/") element_checkbox = "document.getElementsByClassName('el-checkbox__original')[0].setAttribute('aria-hidden', 'true')" driver.execute_script(element_checkbox) |
3.5 Selenium 操作浏览器
浏览器操作 | 语法 |
前进 | driver.forward() / driver.back() |
窗口 |
最大:driver.maximize_window() 最小:driver.minimize_window() 全屏:driver.fullscreen_window() |
关闭 | driver.close() (关闭当前) / driver.quit() (关闭所有) |
获取属性 |
当前 url:driver.current_url 当前对象的句柄: driver.curent_window_handle 当前对象的标题: driver.title 当前对象的所有句柄(选项卡): driver.window_handles # 列表形式 切换到某个窗口(通过句柄):driver.switch_to.window(句柄名) recommendated: set a name for window handle handle_baidu = driver.window_handles[0] driver.switch_to.window(handle_baidu) 截图:driver.get_screenshot_as_file(r"e:\test.png") |
alert 框 |
无法直接定位,需要: 1)switch_to.alert 切换到 alert 对象 2)调用对应方法或属性: accept() / dismiss() # 取消 / text |
滚动条 |
滚动条无法直接定位,需要借助 js 脚本实现。 1)指定上下滚动高度 js = 'var browser=document.documentElement.scrollTop=100' 3)滚动到指定元素位置(最常用) |
3.6 WebDriver API 及对象识别技术(一)
3.6.1. 鼠标键盘事件
1)鼠标事件:from selenium.webdriver.common.action_chains import ActionChains相关事件: context_click(),double_clcik(),drag_and_drop(),move_to_element(),click_and_hold()
2)键盘事件:from selenium.webdriver.common.keys import Keys
相关事件:Keys.BACKSPACE;Keys.SPACE;Keys.CONTROL, 'a';Keys.CONTROL, 'c';Keys.CONTROL, 'v';
3.6.2. 多个 frame 间切换
1)切换 frame 及 切换回主文档
from selenium import webdriver driver = webdriver.Chrome() driver.get("https://mail.163.com/") driver.implicitly_wait(30) # swith to iframe, b/c input text of login is in another frame, need to swtich to it firstly, or it can't be located in default frame. # driver.switch_to.frame(0) # workable driver.switch_to.frame(driver.find_element_by_xpath("//iframe[contains(@id, 'x-URS-iframe')]")) driver.find_element_by_name("email").send_keys("456") driver.find_element_by_name("password").send_keys("456") #back to main page driver.switch_to.default_content() # fail to locate driver.find_element_by_name("password").send_keys("789")
2)嵌套 frame 的操作
** iframe1 -> iframe2:需要进行2步
3.6.3 下拉列表框
# ----- 以宽客猫后台的用户管理界面的下拉框为例 ------
# 1. 直接定位 driver.find_element_by_xpath("//div[@class='user_sele']/ul[1]/select[1]/option[2]").click() sleep(3) # 2. 二次定位 get_select = driver.find_element_by_xpath("//div[@class='user_sele']/ul[1]/select[1]") get_select.find_element_by_css_selector("option[value='2']").click() sleep(3) # 3. Select 模块(需要导入下面的模块) from selenium.webdriver.support.select import Select select_object = Select(driver.find_element_by_xpath("//div[@class='user_sele']/ul[1]/select[1]")) # 3.1) 通过索引定位 select_object.select_by_index(4) sleep(3) # 3.2)通过 value 值定位 select_object.select_by_value("-1") sleep(3) # 3.3)通过文本值定位 select_object.select_by_visible_text("已禁用")
3.7 WebDriver API 及对象识别技术(二)
3.7.1 三种等待方式
等待方式 | 说明 | 实现 | 作用域 |
强制等待 | 强制等待xx秒 |
from time import sleep sleep(2) |
当前语句 |
隐式等待 (常用) |
等待整个页面加载完毕,才继续下一步 如提前加载完毕则不等待,直接执行下一步 一般 5~30 s 页面响应时间+网络传输+机器性能,总体不超过 30s * 页面的响应:2s 最佳,5s 良好,8s 一般 |
driver.implicitly_wait(30) |
整个脚本的生命周期, 故只设置一次 |
显式等待 (常用) |
针对单个元素设置一定的频率,刷新当前页面,检测是否存在该元素 常与 try...except 一起使用 |
from selenium.webdriver.support.wait import WebDriverWait or from selenium.webdriver.support.ui import WebDriverWait |
当前语句 |
element_search_input = WebDriverWait(driver, 5, 1).until(lambda x:x.find_element_by_id("kw")) element_search_input.send_keys("Hello World")
# WebDriverWait 对象只存在两个方法: until 和 until_not;
# until 和 until_not 需要传入一个参数 method
# method 传入的对象只能是两种:1)匿名函数 lambda;2)预置条件对象 expected_condition (from selenium.webdriver.support import expected_condition as EC);
''' 用例测试 until 方法的入参一: 匿名函数 lambda lambda x:x.find_element_by_id("kw") 实际上就是在执行:driver.find_element_by_id("kw") 通过点击 WebDriverWait的 until 方法,可以看到 value = method(self._driver) 是通过传入驱动到 method 方法的 ''' from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait driver = webdriver.Chrome() driver.get("http://www.baidu.com") element_search_input = WebDriverWait(driver, 5, 1).until(lambda x:x.find_element_by_id("kw")) element_search_input.send_keys("Hello World")
''' 用例测试:用来了解python 类的内置函数 __call__ 方法,通过直接引用类的对象,通过函数的形式,就可以直接调用 call 方法,不需要进行调用具体的方法(相对简单方便);
如果没有声明该方法,对象不可直接使用函数式调用 class title_is(object): def __init__(self, title): self.title = title def __call__(self, driver): return self.title == driver.title 简单用例: class A: def __init__(self): print("initialize") def __call__(self): print("common method") # 创建一个类A 的对象 a = A() # 直接通过 a() 就可以调用类A 的 call 方法,结果是:initialize (下一行) commn method a() ''' from selenium import webdriver from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() driver.get("http://www.baidu.com") try: # EC.title_is("百度一下,你就知道") 创建一个 title_is 的对象,后面是 __init__ 方法所需的参数 # 再通过 (driver) 传入 __call__ 方法所需的 driver, # 上面2步结合起来,就是执行完整的 调用了 title_is的 call 方法 print(EC.title_is("百度一下,你就知道")(driver)) except: print("Can not located")
from selenium.webdriver.support.wait import WebDriverWait # from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium import webdriver driver = webdriver.Chrome() driver.get("http://www.baidu.com") ''' 1)通过查看 EC.presence_of_element_located 的源码 def __call__(self, driver): return _find_element(driver, self.locator) 2)再查看 _find_element def _find_element(driver, by): try: return driver.find_element(*by) # 由上面一句代码可知,传入的参数 by,在下面执行的时候被拆分成类似于 (By.ID, 'kw'),因为 find_element 的参数是两个参数 由此可知,_find_element(driver, by)的参数 by 是一个元组 ''' try: # element_search_input = EC.presence_of_element_located((By.ID, 'kw'))(driver) # -> 上一步最终转换为 element_search_input = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.ID, 'kw1'))) element_search_input.send_keys("awesome") except: print("element can't be located")
# EC 常用的几个: element_search_input = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.ID, 'kw'))) # 传 locator element_search_input = WebDriverWait(driver, 5).until(EC.visibility_of_element_located((By.ID, 'kw'))) # 传 locator element_search_input = WebDriverWait(driver, 5).until(EC.visibility_of(driver.find_element(by=By.ID, value='kw'))) # 传 element
element_search_input.send_keys("awesome")
3.7.2 文件上传与下载
1. 文件上传
1)针对上传文件控件,其 type 属性为 file 的,可以定位到该元素,然后直接通过 send_keys(filePath) 的方式,上传文件
def upload_file(self, filePath): # 上传文件控件,"type=file"的,上传文件定位到元素后,直接通过 send_keys 即可 self.driver.find_element_by_id('file_v').send_keys(filePath)
2)非普通:windows 下 需要借助第三方工具 AutoIT 来实现
官网: https://www.autoitscript.com/site,下载 AutoIT
组件:SciTE Scipt Editor:编辑器; AutoIt Window Info:windows 弹框等窗口元素定位; Compile Script to .exe:将 1)编辑器保存的 .au3 文件转成 .exe
# step1:AutoIT 脚本,然后保存成 .au3 文件 ; 聚焦一个 Windows 窗口,传入的参数中 ControlID 对应 ClassNameNN ControlFocus("打开", "", "Edit1") ; 设置窗口的等待时间 WinWait("打开","", 5) ; 将文件路径写入指定的文本框 ControlSetText("打开", "", "Edit1", $CmdLine[1]) ; 点击打开 ControlClick("打开","打开(&O)", "Button1") 注: AutoIT 脚本中以 “;” 符合为注释符
不同的浏览器的定位的元素不同【特别注意】 # step2:使用 Compile Script to .exe,将文件转成 upfile.exe # step3:在 python 中调用 get_path = os.path.dirname(os.path.abspath(__file__)) + "img\python.png" driver.find_element_by_name('file').click() # 单击上传文件按钮 os.system("upfile.exe %s" %get_path) # 上传本地文件
2. 文件下载
# 通过 firefox 的 about:config 查看浏览器相关配置 fp = webdriver.FirefoxProfile() #指定文件下载路径 # set to customized folder, 0: default directory fp.set_preference("browser.download.folderList", 2) fp.set_preference("browser.download.dir", "E:\Temp") #设置下载文件的类型 fp.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream") driver = webdriver.Firefox(firefox_profile=fp) driver.get("https://www.python.org/downloads/") element_download_href = driver.find_element_by_xpath("//div[@class='download-for-current-os']/div[3]/p/a") download_href = element_download_href.get_attribute('href') # download the file from href driver.get(download_href)
# 配置 chrome,通过 chrome://about 可查看链接 options = webdriver.ChromeOptions() prefs = {"profile.default_content_settings.popups": 0, "download.default_directory": "E:\\Temp", "safebrowsing.enabled": True } options.add_experimental_option('prefs', prefs) driver = webdriver.Chrome(chrome_options=options) driver.get("https://www.python.org/downloads/") element_download_href = driver.find_element_by_xpath("//div[@class='download-for-current-os']/div[3]/p/a") download_href = element_download_href.get_attribute('href') # download the file from href driver.get(download_href)
3.7.3 验证码
1. 基本功能:当前访问页面的数据安全性;减少用户的并发数
2. 类型:1)纯数字、纯字母;2)汉字组合;3)数学运算题;4)滑动;5)图片;6)短信;7)语音;8)邮箱
3. 验证码的实现:开发实现时有对应的验证码资源库
- 本地资源:通常定义在某种容器列表中,如列表等;1)-5)
- 网络资源:相关的数据全部调用第三方接口或网络爬取的相关数据; 1)- 8)
4. 解决验证码:
- 1)屏蔽;
- 2)万能验证码;
- 3)如果是本地资源库,则只保留一张;
- 4)打码平台完成,如 斐斐、超人、图鉴等;
- 5)Python-tesseract 模块进行光学字符识别;
---------- Base_Class.py ---------- from selenium import webdriver class BaseClass(): def __init__(self, url, browserType): if browserType == "Chrome": self.get_driver = webdriver.Chrome() elif browserType == "Firefox": self.get_driver = webdriver.Firefox() else: print("not supported browser type %s" % browserType) self.get_driver.implicitly_wait(10) self.get_driver.get(url) ---------- Cloud_Code.py ---------- import json import requests import base64 from io import BytesIO from PIL import Image from sys import version_info def base64_api(uname, pwd, img): img = img.convert('RGB') buffered = BytesIO() img.save(buffered, format="JPEG") ''' version_info是sys模块中的一个函数,主要用于返回你当前所使用的Python版本号。 version_info是一个包含了版本号5个组成部分的元祖,这5个部分分别是主要版本号(major)、次要版本号(minor)、 微型版本号(micro)、发布级别(releaselevel)和序列号(serial)。 ''' if version_info.major >= 3: b64 = str(base64.b64encode(buffered.getvalue()), encoding='utf-8') else: b64 = str(base64.b64encode(buffered.getvalue())) data = {"username": uname, "password": pwd, "image": b64 } result = json.loads(requests.post("http://api.ttshitu.com/base64", json=data).text) if result['success']: return result['data']['result'] else: return result['message'] return "" if __name__ == '__main__': img_path = "code.png" img = Image.open(img_path) result = base64_api(uname="wood", pwd="wood1314", img=img) print(result) ---------- 3.7.3_example1.py ---------- from PIL import Image import time from Base_Class import BaseClass from Cloud_Code import base64_api class Dsmall_Login(BaseClass): def __init__(self, url, browserType): super().__init__(url, browserType) def get_code_image(self): # save whole web page as a picture self.get_driver.maximize_window() self.get_driver.get_screenshot_as_file("index.png") # get the verify code position time.sleep(5) get_code_element = self.get_driver.find_element_by_xpath("//img[@ms-click='@reloadcode']") get_left = get_code_element.location["x"] get_upper = get_code_element.location["y"] get_right = get_code_element.size["width"] + get_left get_lower = get_code_element.size["height"] + get_upper # create image object get_image = Image.open("index.png") get_new_image = get_image.crop((get_left, get_upper, get_right, get_lower)) get_new_image.save("code.png") def get_code(self, uname, pwd, img): get_code = base64_api(uname, pwd, img) print(get_code) if __name__ == '__main__': ds = Dsmall_Login("https://quantpt.gecenet.com/manage/index.html#/login/login", "Chrome") ds.get_code_image() ds.get_code(uname="wood", pwd="wood1314", img=Image.open("code.png"))