python web自动化

437 阅读13分钟

课程目录

  • selenium webdriver环境安装、原理
  • 前端页面:html\dom对象
  • 8大元素定位、xpath详解
  • web常用元素操作
  • PageObject模式应有、自动化用例设计
  • 深入分层设计
  • basepage页面提取
  • pytest框架
  • jenkins集成
  • allure报告集成

web自动化环境安装

  • 安装selenium

    • 命令行安装pip2 install --user selenium
  • 安装浏览器和浏览器驱动

    • chrome -- chromedriver :本次课程使用
    • ie -- ieserverdriver
    • firefox -- geckodriver
    • chromedriver国内镜像网站:npm.taobao.org/mirrors/chr…
    • chromedriver官方(需要梯子):sites.google.com/a/chromium.…
    • web端:人、浏览器进行交互--> 代码通过驱动程序和浏览器进行交互
  • chromedriver存放的位置

    • 安装在python安装目录下Scripts,则代码中写为:

      driver=webdriver.Chrome()

    • 安装在其他目录下,则代码中写为:

      driver=webdriver.Chrome("chromedriver的路径")

  • 启动浏览器

    from selenium import webdriver
    
    # 启动浏览器
    driver=webdriver.Chrome("/Users/xxx/python_web_demo/chrome_driver/chromedriver")
    

    run一下这个.py文件,即可启动谷歌浏览器,如图:

web前端知识

自己有一些前端知识的基础,这里简单回顾,没学过html的还是要系统的看看

web页面组成

  • HTML 定义页面呈现的内容,html是一门标记语言,不是变成语言
  • CSS 控制页面如何布局、显示
  • JavaScript 控制页面的行为

HTML标签对

  • HTML标记标签通常被称为HTML标签(HTML tag)
  • HTML标签由尖括号包围的关键字组成,如<body>
  • HTML标签通常成对出现,如<html>``</html>
  • 标签对中的第一个标签是开始标签,第二个标签是结束标签
  • 开始和结束标签也叫开放标签和闭合标签

input标签

  • 用于收集用户信息
  • 根据不同的type属性值,输入字段拥有很多形式
  • 输入字段可以是文本字段、复选框、单选框、复选框、提交按钮、掩码后的文本框等

HTML属性

  • placeholder:text 规定帮助用户填写输入的字段
  • readonly:规定输入字段为只读
  • type:button/checkbox/file/password/radio/sumbit/text 规定input元素的类型
  • checked:checked 规定此input元素首次加载时应当被选中
  • disabled:disabled 当input元素加载时禁用此元素

示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>python web自动化学习</title>
</head>
    这是body标签的内容啦~~~
    <p>这里会是一个段落</p>
    输入框:<input></input><br>
    按钮:<input type="submit"></input><br>
    密码:<input type="password"></input><br>
    爱好:<input type="checkbox">摄影</input><input type="checkbox">音乐</input><input type="checkbox">爬山</input>
    <br>
    单选:<input type="radio" name="sex"></input><input type="radio" name="sex"></input>
    <br>
    上传文件:
    <input type="file">请上传图片</input>
    <br>
    按钮不可点击状态:
    <input type="submit" disabled="disabled"></input><br>
    百度地图:
    <a href="map.baidu.com">百度地图点击一下</a>
    <br>
    百度logo:
    <img src="https://www.baidu.com/img/bd_logo1.png?where=super" height="129" width="270">
    <br>
    下拉列表:
    <select>
        <option>---请选择---</option>
        <option>周杰伦</option>
        <option>林俊杰</option>
    </select>
    <br>
    表单:
    <form>
        <table>
            <tr >
                <td>1</td>
                <td>2</td>
            </tr>
            <tr>
                <td>3</td>
                <td>4</td>
            </tr>
        </table>
    </form>
    <br>
    iframe框:<br>
    <iframe src="https://www.baidu.com" width="500" height="300"></iframe>
    <br>
    多文本域
    <textarea>

    </textarea>
    <h1>hello world</h1>
    <h2>hello world</h2>
    <h3>hello world</h3>
    <h4>hello world</h4>
    <h5>hello world</h5>
    <h6>hello world</h6>
</body>
</html>

JS-DOM对象

JavaScript
  • 在html页面中写js,代码是要放在<script></script>标签内

  • 变量:

    var 变量名 = 值

  • 函数

    function 函数名称(参数){
        //代码块
    }
    
  • 调用函数

    函数名(参数)

DOM对象
  • DOM(document object mode)是一套web标准:定义了访问html文档的一套属性、方法和事件
  • 本质:网页与脚本语言沟通的桥梁。脚本语言通过DOM对象来访问html页面,从而改变文档的结构、样式和内容。当浏览器载入html文档,它就成为document对象。
  • HTML DOM独立于平台和编程语言。它可以被任何编程语言诸如java、javascript和VBScript使用
DOM树

document:每个载入浏览器的 HTML 文档都会成为 Document 对象。

window:window对象表示浏览器中打开的窗口。Document 对象是 Window 对象的一部分,可通过 window.document 属性对其进行访问。

DOM对象查找元素
  • 元素id属性:document.getElementById("")
  • 元素的class属性:document.getELementsByClassName("")
  • 元素的标签名属性:document.getELementsByTagName("")
  • 元素的name属性:document.getELementsByName("")
  • css选择器:document.querySelector(css)

可以通过控制台console直接搜索结果:

元素属性
  • 改变元素属性值

    document.getByElementByXXX("").属性名=新属性值

    document.getByElementByXXX("").setAttribute("属性名","新的属性值")

  • 获取元素属性值

    document.getByElementByXXX("").getAttribute("属性名")

  • 改变元素的内容

    • 包含html元素标签 -- 有后代:

      document.getByElementByXXX("").innerHTML=new HTML

    • 不包含html元素标签,纯文本:

      document.getByElementByXXX("").innerText=new text

  • 改变样式

    document.getByElementByXXX("").style.样式名=样式值

    示例:

    document.getByElementByXXX("").style.visibility="hidden" document.getByElementByXXX("").style.color="red"

事件

浏览器和用户事件 - 触发 -执行js代码带来不同的页面响应。

例如:点击事件、输入事件、鼠标事件

  • 页面加载完成事件

    window.onload=function(){
        alert("everything is ready!")
    }
    
  • 点击事件

    document.findElementById("xxx").onclick=function(){
        alert("点了我一下")
    }
    

webdriver源码理解

  • webdriver库是客户端,chromedriver是服务端
  • 以http协议的形式约定了一套web自动化中的命令(command)

流程:

  • webdriver启动chromedriver
  • 发送http请求到chromedriver(server端),执行命令 (execute)
  • chromedriver驱动chrome浏览器

对浏览器的基本操作

# encoding:utf-8

from selenium import webdriver

# 启动浏览器,开启与浏览器之间的会话
driver=webdriver.Chrome("/Users/xxx/Documents/000_Inbox/python_web_demo/chrome_driver/chromedriver")

# 访问一个网页
driver.get("http://www.baidu.com")

# 窗口最大化
driver.maximize_window()

# 访问另一个网页
driver.get("http://www.taobao.com")

# 回退到上一页
driver.back()

# 回到下一页
driver.forward()

# 刷新
driver.refresh()

# 获取标题
print driver.title

# 获取网址
print driver.current_url

# 获取窗口的句柄
print driver.current_window_handle

# 关闭当前窗口
driver.close()

# 关闭会话 
driver.quit()

打印结果如下:

driver.close()driver.quit()的区别:

  • driver.close() 源码注释:Closes the current window. (关闭当前窗口)
  • driver.quit()源码注释:Closes the browser and shuts down the ChromeDriver executable that is started when starting the ChromeDriver.(关闭浏览器及杀掉对应的chromedriver进程)

八大元素定位方式及xpath详解

八大元素定位方式

  • id
  • classname
  • name
  • tagname
  • link_text
  • partial_link_text
  • xpath
  • css
from selenium import webdriver

# 启动浏览器,开启与浏览器之间的会话
driver=webdriver.Chrome("/Users/leitianxiao/Documents/000_Inbox/python_web_demo/chrome_driver/chromedriver")

# 访问百度
driver.get("https://www.baidu.com")

# 方式一:通过id定位
ele=driver.find_element_by_id("kw") # 返回的是一个WebElement对象
print type(ele) # ele的类型: <class 'selenium.webdriver.remote.webelement.WebElement'>
print ele.get_attribute("class")  # ele的class属性的值: s_ipt

# 方式二:通过classname定位
eles=driver.find_elements_by_class_name("s_ipt")

driver.find_element_by_class_name("s_ipt")
# 区别:通过classname定位可能不止一个元素,find_element返回第一个元素,find_elements返回所有元素

# 方式三:通过name定位
driver.find_element_by_name("wd")
driver.find_elements_by_name("wd")

# 方式四:通过tagname定位
driver.find_element_by_tag_name("input")
driver.find_elements_by_tag_name("input")

# 方式五、方式六:通过链接文本定位,针对链接,完全匹配和模糊匹配
# 完全匹配
driver.find_element_by_link_text("更多产品")
# 模糊匹配
driver.find_element_by_partial_link_text("产品")

# 方式七:通过xpath定位

# 绝对定位:
# 由树状根节点开始,以/开头,如: "/html/body/div[2]/div[2]/div[5]/div[1]/div/form/span[1]/input"
# 非常依赖页面的顺序和位置,开发稍微调整一下页面顺序,定位就失效了,一般不建议使用
driver.find_element_by_xpath("/html/body/div[2]/div[2]/div[5]/div[1]/div/form/span[1]/input")

# 相对定位
# 以//开头,不依赖于页面顺序和位置,只看有没有符合表达式的元素
driver.find_element_by_xpath("//input[@name='phone']") # "//标签名[@属性='属性值']",属性中可以使用逻辑运算


# 方式八:通过css定位


driver.quit()

xpath详解

  • 利用标签内的属性进行定位

    xpath="//标签名[@属性='属性值']",如://input[@name='phone']

    • 属性中可以进行逻辑运算,如//input[@name='wd' and @class='s_ipt']
    • F12-检查元素时,快捷ctrl+f呼出搜索框,用来检测这个xpath表达式结果是否是唯一
    • 如果有一个完全一模一样的元素,从本级中无法区分,使用层级定位,从父级或者爷爷级开始定位,如://div[@id='u1']/a[@name='tj-login']
  • 利用text()方法定位

    xpath="//标签名[text()='文本内容']",如://div[@id='u_sp']/a[text()='贴吧']

  • 利用contains()方法定位,也叫模糊定位

    xpath = "//标签名[contains(@属性,'属性值')]",如//div[@id='u_sp']/a[contains(@href, 'tieba')]

    xpath = "//标签名[contains(text(),'文本内容')]"

  • 轴定位

    示例:如蛋卷基金https://danjuanapp.com/,它的每个“查看组合详情”的代码都一模一样,较难定位。

    使用轴定位:

    //h1[text()='银行螺丝钉组合']/ancestor::a/descendant::a

    //h1[text()='九雾组合']/ancestor::a/descendant::a

    //h1[text()='久聪定投']/ancestor::a/descendant::a

等待方式

在实际项目中,经常会报错,noSucElement,可能存在问题的原因是:

  1. 定位表达式有问题
  2. 没有设置等待时间,元素还没有加载出来
  3. 切换问题,可能是在另外一个html中,如iframe

解决第二个问题,需要使用到等待时间。有三种等待方式:(app通用)

什么时候要等待元素加载:但凡操作引起了页面的变化,都要进行等待,如跳转到其他页面,弹出弹窗等....

  • 强制等待

    sleep(秒)

    固定等待

  • 隐式等待

    implicitly_wait(秒)

    设置最长等待时间,在这个时间内加载完成,则执行下一步。整个driver的会话周期内,设置一次即可,全局可用。这个时间内没有加载完成,抛出timeout异常

  • 显式等待

    明确等到某个条件满足之后,再去执行下一步。

    程序每隔xx秒看一眼,如果条件成立,则执行下一步,否则继续等待,直到超过设置的最长时间,然后抛出timeout异常。

    WebDriverWait类:显性等待类

      `WebDriverWait(driver,等待时长,轮循周期).until()/until_not()`
    

    expected_conditions模块:提供了一系列期望发生的条件。

    常用条件:

    • presence_of_element_located:元素存在,一个元素出现页面的dom中,但不一定可见。
    • visibility_of_element_located:元素可见,一个元素出现页面的dom中,且可见,height wight不为0.
    • element_to_be_clickable:元素可点击

    使用显式等待:

    • 引入相关的库

      from selenium.webdriver.support.wait import WebDriverWait
      from selenium.webdriver.support import expected_conditions as EC
      from selenium.webdriver.common by import By
      
    • 使用

      1. 先确定元素的定位表达式 web_locator='xxx'

      2. 调用WebDriverWait类设置等待总时长、轮询周期,默认检测频率为0.5s。并调用until、until_not方法 WebDriverWait(webdriver,等待总时长,轮询周期).until(判断条件)

      3. 使用expected_conditions对应的方法来生成判断条件 EC.类名((定位方式,定位表达式))

      示例:

      # encoding:utf-8
      
      from selenium import webdriver
      from selenium.webdriver.support.wait import WebDriverWait
      from selenium.webdriver.support import expected_conditions as EC
      from selenium.webdriver.common.by import By
      
      # 启动浏览器,开启与浏览器之间的会话
      driver=webdriver.Chrome("/Users/xxx/Documents/000_Inbox/python_web_demo/chrome_driver/chromedriver")
      
      # 设置隐式等待,全局等待,设置一次即可
      # driver.implicitly_wait(20)
      
      # 访问百度
      driver.get("https://www.baidu.com")
      
      # 等待 "登录" 元素存在
      locator=(By.XPATH,"//*[@id='u1']/a[@name='tj_login']")
      WebDriverWait(driver,20,1).until(EC.presence_of_element_located(locator))
      
      # 点击登录
      driver.find_element_by_xpath("//*[@id='u1']/a[@name='tj_login']").click()
      
      # 等待 "用户名登录" 元素存在
      WebDriverWait(driver,20,1).until(EC.presence_of_element_located((By.ID,"TANGRAM__PSP_10__footerULoginBtn")))
      
      # 点击 用户名登录
      driver.find_element_by_id("TANGRAM__PSP_10__footerULoginBtn").click()
      
      driver.quit()
              
      

切换方式

切换iframe

iframe是另一个html页面,要定位iframe框中的元素,需要切换到iframe。

怎么知道这个元素是不是在iframe框中,技巧:

  • 使用driver.switch_to.frame切换iframe框

    三种示例:

    driver.switch_to.frame('frame_name')

    driver.switch_to.frame(1)

    driver.switch_to.frame(driver.find_elements_by_tag_name("iframe")[0])

    以虫部落为例:

    from selenium import webdriver
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    
    # 启动浏览器,开启与浏览器之间的会话
    driver=webdriver.Chrome("/Users/xxx/Documents/000_Inbox/python_web_demo/chrome_driver/chromedriver")
    
    # 设置隐式等待,全局等待,设置一次即可
    driver.implicitly_wait(10)
    
    # 访问百度
    driver.get("https://search.chongbuluo.com/")
    
    # 等待 "百度" 元素存在
    locator=(By.XPATH,"//li[@id='baidu']")
    WebDriverWait(driver,20).until(EC.presence_of_element_located(locator))
    
    # 点击百度
    driver.find_element_by_xpath("//li[@id='baidu']").click()
    
    # 切换到iframe框,进入了另外一个html页面
    driver.switch_to.frame("engine")
    WebDriverWait(driver,20).until(EC.visibility_of_element_located((By.XPATH,"//input[@id='kw']")))
    
    s1="定位到了吗"
    s1=(unicode(s1, 'utf-8'))
    # 定位输入框
    driver.find_element_by_xpath("//input[@id='kw']").send_keys(s1)
    
    # 点击搜索
    driver.find_element_by_id("su").click()
    
    driver.quit()
    
  • 通过EC条件中的frame_to_be_available_and_switch_to_it切换iframe框

    EC.frame_to_be_available_and_switch_to_it iframe是否可用且切换进入iframe

    使用这个就不需要再使用switch_to了

    示例:

    WebDriverWait(driver,20).until(EC.frame_to_be_available_and_switch_to_it("engine"))
    
  • 从iframe框切换到默认主页面

    driver.switch_to.default_content()

    driver.switch_to.parent_frame() 切换到父级页面,多层iframe使用

切换窗口

通过driver.switch_to.window()切换窗口

# step1.获取窗口的总数以及句柄,新打开的窗口排在最后
handles=driver.window_handles
print handles

print driver.current_window_handle # 打印当前句柄

# step2.切换到最后一个句柄,也就是最新打开的句柄
driver.switch_to.window(handles[-1])

# 再对切换到的页面进行元素定位和操作
# ...

EC条件中EC.new_window_is_opened(handles)判断新窗口是否打开,此处的handles应该在引起窗口数量增加之前获取,要使用driver.switch_to.window()切换到新的句柄,需要再获取一次handles

切换alert (alert不是html元素)
  • 通过driver.switch_to.alert切换alert
alert=driver.switch_to.alert

alert.accept() # 确认
alert.dismiss() # 取消
alert.text # 获取alert中的文本

  • 通过EC类 EC.alert_is_present()判断alert是否出现

·第90课

PageObject 页面对象模型

PageObject,页面对象。UI自动化的操作,都是在各个页面上进行操作,不同的页面的功能串起来。页面是有限的,把所有页面封装成对象,每个页面封装为一个对象(类),把每个页面的功能进行封装。按业务逻辑调用各个页面对象中的功能(函数)

原理实现:

将页面的元素定位和元素行为封装成一个page类

类的属性:元素的定位

类的行为:元素的操作

实现页面对象和测试用例分离

测试用例: 调用所需页面对象中的行为,组成测试用例。

好处:

  1. 当某个页面发生改变时,只需要修改该页面对象中的代码即可,不需要修改测试用例。
  2. 提高代码重用率。结构清晰,维护代码更容易
  3. 测试用例发生变化时,不需要或者需要修改少数页面对象的代码即可。

以登录页面为例:

项目结构:

项目名
- page_object
    - login_page.py --登录页
    - index_page.py --首页
- test_case

page_object/login_page.py:

# page_object/login_page.py
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

class LoginPage:

    def __init__(self,driver):
        self.driver=driver

    '''
    测试用例是调用多个页面类,在用一个浏览器会话上面串行执行,不应该在页面对象中打开浏览器,
    否则每个页面对象都打开一个浏览器,而是在测试用例中打开一个浏览器会话。所以只能等待测试用例传参driver
    '''

    def login(self,phone,passwd,remeber_user=False):

        WebDriverWait(self.driver,20).until(EC.visibility_of_element_located((By.XPATH,"//input[@name='phone']")))
        # 输入手机号
        self.driver.find_element_by_xpath("//input[@name='phone']").send_keys(phone)
        # 输入密码
        self.driver.find_element_by_xpath("//input[@name='password']").send_keys(passwd)
        # 判断remeber_user的值,是否勾选记住密码
        
        # 点击登录
        self.driver.find_element_by_xpath("//button[text()='登录']").click()


    def register_enter(self):
        pass
        # 点击注册

    def forget_password(self):
        pass
        # 点击忘记密码

test_case/test_login.py

class TestLogin(unittest.TestCase):
    def setUp(self):
        self.driver=webdriver.Chrome("chromedriver路径")
        self.driver.get("测试环境地址")
        self.lg=LoginPage(self.driver)
        
    def tearDown(self):
        self.driver.quit() 


    # 正常用例:登录成功
    def test_login_success(self):
        
        # 前置: 访问登录页面 (使用def setUp(self))
        # 步骤: 输入手机号、密码、登录
        self.lg.login("18019230000","123456")
        # 断言: 首页中能否找到属于首页的某个元素
        # 等待10秒,判断首页的元素是否出现/存在,如果没有出现,可以说明没有跳转成功,相当于断言失败,但是不能直接写WebDriverWait(....)判断首页某个元素是否出现/存在,这样,当首页的元素进行修改或变动时,所有的测试用例都要改,没有真正做到po设计模式。
        self.assertTure(IndexPage(self.driver).isExist_logout_ele())
        pass
    
    # 异常用例:手机号错误
    def test_login_userphone_wrong(self):
        # 前置: 访问登录页面
        # 步骤: 输入手机号、密码、登录
        self.lg.login("18019230001","123456")
        # 断言: 登录页面提示:手机号错误
        pass
        
    # 异常用例:手机号为空
    def test_login_userphone_null(self):
        self.lg.login("","123456")
        pass

page_object/index_page.py

class IndexPage:

    def __init__(self.driver):
        self.driver=driver
    
    # 检查退出元素是否存在
    def isExist_logout_ele(self):
        # 如果存在返回Ture ,如果不存在返回False
        try:
            WebDriverWait(....).unitl
            return Ture
        except:
            return False