一、元素定位以及可能出现的问题
1、简单的元素定位
打开页面后,第二件事情就是定位到我们要操作的页面元素,定位单个页面元素有如下方法(下面的方法,如果没有定位到相应的元素,抛出NoSuchElementException异常):
- find_element_by_id:通过id属性定位元素(返回第一个匹配的)。
- find_element_by_name:通过name属性定位元素(返回第一个匹配的)。
- find_element_by_xpath:通过xpath定位元素(返回第一个匹配的)。
- find_element_by_link_text:通过超链接文本定位超链接元素,必须是完全匹配(返回第一个匹配的)。
- find_element_by_partial_link_text:通过超链接文本定位超链接元素,可以是部分匹配(返回第一个匹配的)。
- find_element_by_tag_name:通过标签名字定位元素(返回第一个匹配的)。
- find_element_by_class_name:通过class属性定位元素(返回第一个匹配的)。
- find_element_by_css_selector:使用CSS选择器语法定位元素(返回第一个匹配的)。
此外,webdriver还有find_element
及find_elements
方法定位元素:
from selenium.webdriver.common.by import By
driver.find_element(By.XPATH, '//button[text()="Some text"]')
driver.find_elements(By.XPATH, '//button')
ID :元素id属性。
XPATH:xpath。
LINK_TEXT :超链接文本。
PARTIAL_LINK_TEXT :部分超链接文本。
NAME :元素name属性。
TAG_NAME :元素标签名字。
CLASS_NAME :元素class属性。
CSS_SELECTOR :使用CSS元素选择器语法。
2、部分难以定位的元素
(1)frame/iframe表单嵌套
解决方法:driver.switch_to.frame()
driver.switch_to.parent_frame()
切回到父frame
driver.switch_to.default_content()
跳回最外层的页面
a = ff.find_element_by_tag_name("a")
# 保存原始窗口,window_handlers是目前wd打开的所有窗口的句柄列表
prev = ff.window_handles[-1]
# 点击超链接(targe="_blank")后,浏览器新窗口被激活
a.click()
# 保存新窗口
new = ff.window_handles[-1]
# 切换到原始窗口
ff.switch_to_window(prev)
print "Switch to prev success"
# 切换到新窗口
ff.switch_to_window(new)
print "Switch to new success"
(2)页面跳转到新的标签页(窗口切换)
解决方法:driver.switch_to.window(window_handle)
切换到新窗口。
windows = self.driver.window_handles
self.driver.switch_to.window(windows[1])
#或者for循环遍历
for handle in driver.window_handles:
# 先切换到该窗口
wb.switch_to.window(handle)
# 得到该窗口的标题栏字符串,判断是不是要操作的窗口
if '窗口标题' in driver.title:
# 如果是,那么WebDriver对象就是对应的该窗口,跳出循环
break
(3)弹出警告框(alert/confim/prompt)
解决方法:alert = driver.switch_to.alert,其中
alert窗口:提示用户信息仅有确认按钮
confirm窗口:有确认和取消按钮
prompt窗口:有输入框、确认和取消按钮
text: 返回(获取)alert/confirm/prompt中的文字信息
accept():接受现有警告框
dismiss(): 放弃现有警告框
send_keys(keys_ToSend):发送文本至警告框
#切换到alert
alert = self.driver.switch_to.alert
print(alert.text)
sleep(2)
alert.accept()
#confirm弹框
self.driver.find_element_by_id('confirm').click()
confirm = self.driver.switch_to.alert
print(confirm.text)
sleep(3)
#确定
# confirm.accept()
#取消
confirm.dismiss()
#prompt弹框
self.driver.find_element_by_id('prompt').click()
prompt = self.driver.switch_to.alert
print(prompt.text)
prompt.send_keys('20')
sleep(3)
prompt.accept()
(4)元素动态变化
解决方法:通过XPATH来定位,driver.find_element( By.XPATH, "" )
(5)页面元素失去焦点导致脚本运行不稳定
解决方法:driver.switch_to.active_element
(6)
页面没有加载出来,对页面元素操作
解决方式:三种等待
#1、WebDriverWait() 显示等待。等待单个的元素加载,通常配合until()、until_not()方法使用。
WebDriverWait(driver, 10,0.5).until(driver.find_element( By.CSS_SELECTOR,""))
#2、time.sleep( ) 强制等待。当执行下一句代码,这种等待方式时间到了就执行下一个语句,但不能保证在等待的时间内元素真正被加载了出来。
time.sleep(10) # 表示强行等待10s
#3、driver.implicitly_wait() 隐式等待。表示在规定的时间内页面的所有元素都加载完了就执行下一步,否则一直等到时间截止,然后再继续下一步。
driver.implicitly_wait(30) #等待30s
(7)点击 display:none元素解决方法
<div>
<div class="login_btn" style="background: url("/static/images_login/btn_sign_in_normal.png") 0% 0% / 100% 100%;"></div>
<div class="login_btn" style="background: url("/static/images_login/btn_sign_in_pressed.png") 0% 0% / 100% 100%; display: none;"></div>
</div>
登录按钮是一个图片,移动到元素上有效果,开发的代码逻辑是通过hover改变display的block和none来实现效果。
display:none——隐藏元素并脱离文档流,即隐藏时不占用空间。
在我用selenium去点击的时候就出现了点击不了的情况
通过键盘的enter点击。
这里使用的是键盘事件Key,ENTER,这种方法适用于绑定了ENTER事件的情况
#部分代码
from selenium.webdriver.common.keys import Keys
driver.find_element_by_xpath("//div[2][@class ='login_btn']").send_keys(Keys.ENTER)
通过执行JS代码让元素显示出来(上述问题通过本方法解决)
通过自带的方法execute_script来执行JS让元素显示出来
#部分代码
js="document.getElementsByClassName('login_btn')[1].style.display='block'"
driver.execute_script(js)
driver.find_element_by_xpath("//div[2][@class='login_btn']").click()
二、对定位的元素进行操作
1、一般定位的元素进行操作
文本框:input
- sendkeys();//输入内容
- clear();//清除内容
- getAttribute 获取元素属性
# 通过输入框的id定位输入框元素
input_box = driver.find_element(By.ID, 'inputBoxId')
# 向输入框输入内容
input_box.send_keys('Hello, World!')
# 清除输入框内容
input_box.clear()
# 获取输入框的value属性
value = input_box.get_attribute('value')
单选框 radio 复选框 checkbox
- click
- clear (清除选中状态)
- isSelected 查看是否被选中
# 单选框操作示例
radio_button = driver.find_element(By.ID, 'radioButtonId')
# 点击单选框
radio_button.click()
# 判断单选框是否被选中
is_selected = radio_button.is_selected()
print("Is radio button selected? " + str(is_selected))
# 复选框操作示例
checkbox = driver.find_element(By.ID, 'checkboxId')
# 点击复选框
checkbox.click()
# 判断复选框是否被选中
is_selected = checkbox.is_selected()
print("Is checkbox selected? " + str(is_selected))
按钮 button / 超链接 a
- click
- isEnabled 查看是否可以操作
# 按钮操作示例
button = driver.find_element(By.ID, 'buttonId')
# 判断按钮是否可操作
is_enabled = button.is_enabled()
print("Is button enabled? " + str(is_enabled))
# 点击按钮
button.click()
2、复杂定位的元素进行操作
组合键操作
一般使用click方法,如果是chrome浏览器,点击超链接 同时按ctrl会打开新标签,shift打开新的窗口。
Actions a = new Actions(driver);//点击按下
a.keyDown(Keys.CONTROL).perform();
driver.findElement(By.xpath("")).click();
a.keyUp(Keys.CONTROL).perform();//点解结束
组合鼠标操作
鼠标左键点击 (可以来打开二级菜单)
Actions action = new Actions(driver);action.click();// 鼠标左键在当前停留的位置做单击操作
action.click(element)// 鼠标左键点击指定的元素
action.contextclick(element)//;鼠标右键点击指定元素
action.doubleClick(element);//鼠标双击元素
鼠标移动操作 (可以来打开二级菜单)
action.moveToElement(element);// 将鼠标移到 element 元素中点
action.moveToElement(element,xOffset,yOffset)
//表示以当前element相对移动的位移量
鼠标拖拽动作
action.dragAndDrop(source,target); //将source元素拖拽到target
action.dragAndDrop(source,xOffset,yOffset);
//可以由以上简单操作组合
action.clickAndHold(source).moveToElement(target).perform(); action.release();
//或者
action.clickAndHold(source).moveToElement(target).release().build().perform();
alert对话框处理
- alert confirm相同
Alert alert = driver.switchTo().alert();
alert.accept();
alert.dismiss();
alert.getText(); //获取alert的内容
- prompt (有提示内容和输入框)
获取alert相同
通过sendKeys()输入内容
select菜单,选择项
定位下拉:element=driver.find_element(By....)
把找到的页面元素,转换为下拉框的类型Select
select = Select(element)
调用Select类中的select_by_*方法
.select_by_value(选项的value属性的值)
.select_by_index(第几个选项)
.select_by_visible_text(选项的文本值)
# 采用xpath获取第一个select元素(即使有多个也返回第一个)
element = ff.find_element_by_xpath("//select[@name='car']")
# 获取所有选项,打印选项值并点击
all_options = element.find_elements_by_tag_name("option")
for option in all_options:
print "Value is: %s" % option.get_attribute("value")
option.click()
from selenium.webdriver.support.ui import Select
select = Select(ff.find_element_by_xpath("//select[@name='car']"))
select.select_by_visible_text("infinity")
select.select_by_value("bmw")
# 按照option的索引选择,第一个option的index为0
select.select_by_index("0")
Window/Tag切换
driver.getWindowHandle();返会字符串,获取当前窗口的句柄
driver.getWindowHandles();返回Set<String> 获取所有的窗口
driver.switchTo().window(窗口的String); 来跳转窗口(无论是新的标签还是新的浏览器窗口都可以通过此方法跳转)。
注意窗口间切换,要关闭窗口时,不能使用 qiut(quit driver就释放了,不能使用driver,close只是关闭当前window 或者 标签 还可以使用driver,当然close最后一个窗口或者标签时就会关闭浏览器 此时driver也不会有效)
滚动定位到特定元素位置
JavascriptExecutor scroll = (JavascriptExecutor) driver;
scroll.executeScript("arguments[0].scrollIntoView();", 指定元素);
3、js操作
js的查找元素方法(ID-对此进行类推)
document.getElementById("id")
execute_script()
从上可以看出其实js的定位元素方法和selenium中的差不多,接下来我们就可以将需要执行的js语句放入到执行函数中使用。
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
# 定位后修改指定元素的value属性
js_script_exec = 'document.getElementById("form_motion").value="list_modify";'
browser.execute_script(js_script_exec)
4、注意点与小技巧
对于某些动态div标签(窗口),一般的方法不太奏效的情况下,可以尝试下switch_to_default_content()方法,跳转到最外层;
使用模拟键鼠操作的时候,无论是单独使用还是链式写法,记得在结尾加上perform()方法进行执行;
如果元素定位时报错element click intercepted,记得检查界面上是否有其他元素进行覆盖,元素有可能也是具有隐藏属性的;
元素过期报错element is not attached to the page document,可以尝试重新刷新页面,这里不推荐直接使用refresh方法,还是养成好习惯先怼上try…except…捕捉到异常后在进行刷新或重置操作;
对于属性值为动态的元素,墙裂推荐使用CSS selector或者xpath方法来进行元素定位,正则表达式也推荐大家最好能掌握;
如果前期对xpath的相对路径写法比较头疼,推荐使用F12调试工具自带的元素复制功能,在你想要复制的元素所在的标签对这行右键,选择copy —— Copy XPath选项即可;
输入框默认存在内容想要删除再输入信息的话,不推荐模拟键盘操作Ctrl+A,然后模拟退格键,试试clear()方法吧;
抓不到元素可以使用延时方法,输入文字也是一样的道理,业务场景中需要大量输入文字的,无论是从文件中还是提取又或者是遍历,出现少字漏字的话,同样也可以使用延时的方法,适当的放慢处理的速度;
在页面中总会有些不可见的元素,这种情况使用is_displayed()方法即可快速定位找到;
有些被测页面需要验证码,无论是手机的还是图片验证,和开发同学沟通一下,留个万能的就行了,其本身的功能手工回归一下即可,不必太过纠结;(之前用过的ddddocr库来解析验证码,还可以)
三方登录功能也是如此,不推荐直接使用web自动化去搞,三方的一般是不开源的,有这折腾的时间还不如跑跑接口和黑盒,自动化的话绕过去即可;
三、对代码进行封装(po模型)
1.POM设计模式
pom指的是page object module,po模式,也就是说会把每个页面当做一个对象来对待,而该页面下的动作都会封装在这个对象中。通过组装页面对象的操作来完成业务的封装。在编写测试用例时调用对应的业务完成测试即可。
2.搭建web框架结构
主要是围绕po模式来搭建结构
pages: 这是一个package,主要用来管理和存放页面对象的封装
common: 这是一个package,主要用来管理和存放底层代码,比如driver的封装、日志封装、文
件封装
logs: 这是一个目录,存放日志文件的
report: 这是一个目录,存放测试结果以及测试报告的
video:这是一个目录,是测试过程中临时存放截图的
actions:这是一个package,主要用来管理和存放业务的封装
testcases:这是一个package,主要用来管理和存放测试用例的封装
page层:把每个单独的页面独立出来,管理自己页面中的元素的基本操作
page里面的页面文件是随时都在增加的,也就是在做业务流程时都需要用到哪些页面就会把哪些页面封装在page里面,所以说它是随着业务层在增加的。
3、封装的示例代码
基础类封装
class BasePage:
def __init__(self,driver):
self.driver=driver
# 打开页面
def into_testin(self,url):
self.driver.get(url)
# 定位元素
def locate_element(self,args):
return self.driver.find_element(*args)
#定位一组元素
def locate_eles(self,args):
return self.driver.find_elements(*args)
#输入值
def input_(self,args,text):
self.locate_element(args).send_keys(text)
#点击按钮
def click_button(self,args):
self.locate_element(args).click()
页面对象类
页面对象层:pageobject,存放页面的元素定位和操作流程
登录模块代码实现:
from time import sleep
from basepage.base_page import BasePage
from selenium.webdriver.common.by import By
class LoginPage(BasePage):
'''定位页面元素'''
#url
testin_url='https://www.testin.cn/account/login.htm'
#输入用户名和密码
username_loc=(By.ID,'email')
password_loc=(By.ID,'pwd')
#点击登录按钮
login_button=(By.ID,"submitBtn")
#登录邮箱操作流程
def testin_login(self,username='18170710339',password='Quyun1230'):
self.into_testin(self.testin_url)
sleep(2)
self.locate_element(self.username_loc).send_keys(username)
sleep(1)
self.locate_element(self.password_loc).send_keys(password)
sleep(1)
self.click_button(self.login_button)
封装登录页login.py
注意:这里的login.py和业务层action里的login.py不是一个py文件,这是对登录页元素的封装,相当于是对整个页面的具体封装文件
class LoginPage(BuyerPage):
'''
首页 元素进行操作
定位元素 点击 输入操作 driver对象- driver.py 进行封装InitDriver类
'''
# def __init__(self,driver:InitDriver):
# # 后面很多方法都要用到这个driver,把他设置为属性
# self.driver = driver
# # 思路百度: 动态获取类名
# self.page_info = read_yaml('/pagefiles/buyer.yml').get('LoginPage')
def username_login(self):
ele_info = self.page_info.get('账号登录')
self.driver.click(ele_info)
def input_name(self, name):
ele_info = self.page_info.get('输入用户名')
self.driver.send_keys(ele_info, name)
def input_password(self, password):
ele_info = self.page_info.get('输入密码')
self.driver.send_keys(ele_info, password)
def input_valicode(self):
ele_info = self.page_info.get('输入验证码')
#这里把验证码写死了,真实环境中可以通过ddddocr来实现OCR识别获取验证码
self.driver.send_keys(ele_info, '1512')
def login_button(self):
ele_info = self.page_info.get('点击登录按钮')
self.driver.click(ele_info)
测试用例层
测试用例层:存放测试用例及数据
import pytest
from time import sleep
from selenium import webdriver
from common.excel import read_excel
from pageobject.login_page import LoginPage
from pageobject.select_phone import SelectPage
class TestTestIn:
#打开浏览器
def setup(self) -> None:
self.driver=webdriver.Chrome()
driver=self.driver
def teardown(self) -> None:
sleep(1)
self.driver.close()
'''利用excel导入登录测试数据'''
@pytest.mark.parametrize('case',read_excel('./data/data.xlsx','login'))
def test_01_login(self,case):
'''测试登录模块'''
xh,case_name,username,password,is_exc,result,bz=case
lp=LoginPage(self.driver)
lp.testin_login(username,password)
def test_02_select_iphone_12(self):
'''测试根据品牌选择手机'''
lp = LoginPage(self.driver)
lp.testin_login()
ps=SelectPage(self.driver)
ps.testin_select_01_iphone()
def test_03_select_androi_sys(self):
'''测试根据安卓系统选择手机'''
lp = LoginPage(self.driver)
lp.testin_login()
ps=SelectPage(self.driver)
ps.testin_select_02_android()
def test_04_select_online_time(self):
'''测试根据上市时间来选择手机'''
lp = LoginPage(self.driver)
lp.testin_login()
ps=SelectPage(self.driver)
ps.testin_select_03_onlin_time()
创建读取excel目录
创建一个common目录,再创建一个read_excel的文件:
import openpyxl
def read_excel(excel_dir,sheet_name):
'''读取excel'''
#加载目录
ex=openpyxl.load_workbook(excel_dir)
#获取sheet页
sheet=ex[sheet_name]
#打印表最大行和列
# print(sheet.max_row,sheet.max_column)
# print(sheet.cell(2,1).value)
#循环行和列
sheet_list=[]
for row in range(2,sheet.max_row+1):
row_list=[]
for col in range(1,sheet.max_column+1):
row_list.append(sheet.cell(row,col).value)
sheet_list.append(row_list)
return sheet_list
if __name__ == '__main__':
read_excel('../data/data.xlsx','login')
封装首页home_page.py
像淘宝或者京东这类的网上商城一样,首页有搜索框,登录,注册这类的按钮元素,业务场景中需要首页的哪些元素就对他进行响应的封装即可
class HomePage(BuyerPage):
'''
首页 元素进行操作
定位元素 点击 输入操作 driver对象- driver.py 进行封装InitDriver类
'''
def login_link(self):
# ele_info = {'type': name / xpath / id / css / classname / tagname / linktext / plinktext, 'value': 值}
ele_info = self.page_info.get('登录链接')
self.driver.click(ele_info)
return LoginPage(self.driver)
def perseon_center(self):
ele_info = self.page_info.get('进入个人中心')
self.driver.click(ele_info)
return PersonCenterPage(self.driver)
def input_search_text(self, text):
ele_info = self.page_info.get('搜索框')
self.driver.send_keys(ele_info, text)
def click_search_btn(self):
ele_info = self.page_info.get('搜索按钮')
self.driver.click(ele_info)
return SearchResultPage(self.driver)
封装个人中心页personcenter_page.py
一般都是登录之后会展示个人中心按钮
class PersonCenterPage(BuyerPage):
def cllick_acceptaddress(self):
# ele_info = {'type': name / xpath / id / css / classname / tagname / linktext / plinktext, 'value': 值}
ele_info =self.page_info.get('收货地址菜单')
self.driver.click(ele_info)
return AcceptAddressPage(self.driver)
封装添加地址页add_adderss_page.py
添加收货地址需要用到收货人姓名、联系地址、收货区域、详细地址以及地址别名(设为默认可以暂时不用管),需要对这些元素一一封装,特别注意的是收货地址未选择下拉框,需要用到鼠标悬停的动作,
class AcceptAddressPage(BuyerPage):
def add_address(self):
# ele_info = {'type': name / xpath / id / css / classname / tagname / linktext / plinktext, 'value': 值}
ele_info =self.page_info.get('添加地址')
self.driver.click(ele_info)
def acceptgoods_name(self,name):
ele_info = self.page_info.get('收货人姓名')
self.driver.send_keys(ele_info,name)
def acceptgoods_tel(self,tel):
ele_info = self.page_info.get('输入联系方式')
self.driver.send_keys(ele_info, tel)
def choose_receaddress(self,provice,city,area):
ele_info=self.page_info.get('收货地区')
# todo 鼠标悬浮-需要在driver.py进行增加
# 省
time.sleep(2)
ele_info = {'name':'省','type':'linktext','value':provice}
self.driver.click(ele_info)
time.sleep(2)
ele_info = {'name':'市','type':'linktext','value':city}
self.driver.click(ele_info)
time.sleep(2)
ele_info = {'name':'区','type':'linktext','value':area}
self.driver.click(ele_info)
如果网不好的话可以加死等(time.sleep())的方式解
运行用例:
创建一个run_test.py文件:
import os
import pytest
from time import sleep
if __name__ == '__main__':
pytest.main()
sleep(2)
os.system('allure generate ./temp -o ./reports --clean')
查看测试报告
查看reports目录中的html测试,用浏览器打开:
四、jenkins集成
启动jenkins
cmd中进入jenkins目录,执行命令:
java -jar jenkins.war
创建任务
进入jenkins,创建一个任务:
添加项目目录:
设置运行命令:
保存,创建成功。
任务创建成功后, 执行运行操作。
可以看到已经在运行了。
运行成功。测试报告,同上,就不再次看了。