web自动化测试入门篇04——selenium+python基础方法封装

Posted Austin_zhai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了web自动化测试入门篇04——selenium+python基础方法封装相关的知识,希望对你有一定的参考价值。

 
 

😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。
📡主页地址:【Austin_zhai】
🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。
💎声明:博主日常工作较为繁忙,文章会不定期更新,各类行业或职场问题欢迎大家私信,有空必回。

 
 

阅读目录

1. 目的

  web自动化测试作为软件自动化测试领域中绕不过去的一个“香饽饽”,通常都会作为广大测试从业者的首选学习对象,相较于C/S架构的自动化来说,B/S有着其无法忽视的诸多优势,从行业发展趋、研发模式特点、测试工具支持,其整体的完整生态已经远远超过了C/S架构方面的测试价值。
  接上一篇文章,我们将继续深入探讨在python中如何将Selenium的一些方法进行封装和二开,以便我们更高效的在自己的测试项目中灵活运用。

 
 

2. 封装

  既然选择了python这门语言来实现web自动化,那我们就不得不讲到一个重要的概念,那就是面向对象编程理念中的封装。就字面意思来理解的话,就像是把各种物品放入一个箱子内,日后需要使用的话就必须从这个箱子里拿才可以获取那个放入的物品。而这个比喻内的箱子就是python内的类,而各类物品则是我们自己根据需要自定义的各种属性和数据、方法,后续需要使用这些属性、数据、方法时,只要引入对应的类并实例化即可。

  那这时一定会有人要问,封装的好处是什么呢?其实这里面的优点也是显而易见的。第一,封装完的类你完全不需要关心里面的功能实现逻辑(除非你要二开),就比如time这个内置模块,你日常测开工作中正常使用其中的内置函数即可,完全不需要搞明白里面的逻辑是如何实现的。第二,方便复用,面向对象编程就是如此,万物皆对象,他不像面向过程,每一个流程都必须实现。只要是任何可以重复实现的逻辑都可以封装形成独立的类或方法,方便复用。第三,既然已经有复用了,那维护性的优势自然就不用再多提了,日常的业务需求修改、页面修改,原本多到数不胜数的维护工作会让你庆幸自己的脚本最初遵循了面向对象的理念。第四,如果你的代码封装完善并且比较健壮且无高耦合,其实用来单独给开发做单测也是一个不错的选择,当然这里更多的还是指接口测试,众所周知基本很少有开发会在紧迫的项目时间内再给自己的程序设计一套单测代码,那么对于测试的同学来说,在实现现有代码的同时,是否可以将代码提供给开发做单模块或功能的单测就显得十分的重要了,这也是测试左移中比较典型的一个例子。

 

2.1 基础功能封装

  我们就先从最基本的浏览器操作开始,这里会遵循一些简单的日常业务操作进行介绍,并且对类内的方法进行拆解,逐一介绍。文中的代码会比较简单,也是方便大家可以更顺利的根据这些功能封装进行二开,无论是健壮性加强或者业务判断都可以自由添加其中。

我们定义的类名为:BrowserDriver,构造函数传入browser。

class BrowserDriver:
    def __init__(self, browser):
        self.driver = self.open_browser(browser)

 

2.1.1 开启浏览器

def open_browser(self, browser):
    if browser == 'chrome':
        options = webdriver.ChromeOptions()
        exclude = ['enable-automation']
        options.add_experimental_option('excludeSwitches', exclude)
        driver = webdriver.Chrome(chrome_options=options)
    elif browser == 'firefox':
        profile = webdriver.FirefoxProfile()
        profile.set_preference('browser.download.dir', 'E:\\FireFox_DL\\\\')
        profile.set_preference('browser.download.folderList', 2)
        profile.set_preference('browser.helperApps.neverAsk.saveToDisk', 'application/zip')
        driver = webdriver.Firefox(firefox_profile=profile)
    elif browser == 'ie':
        driver = webdriver.Ie()
    else:
        driver = webdriver.Edge()
    return driver

  这里解释一下,ChromeOptions()这个方法是chrome浏览器的参数对象,用来配置浏览器启动是的一些参数与属性,这里添加的是浏览器启动后不显示“正受到自动测试软件的控制”的提示,用法比较简单,add_experimental_option这边是添加试验性质的参数,另外比较常用的还有add_argument,add_extension(添加启动项、添加扩展)等方法。
  FirefoxProfile()这个是用来指定火狐浏览器内用户设定档案,一般可以开启或关闭某些浏览器内的功能来达到我们的测试业务需求,如果你用selenium启动火狐的话都会默认新建一个这样的档案,那在代码中的话你可以指定档案的保存路径并在后续对其指定功能进行开启或关闭。

 

2.1.2 检查URL

  封装的功能比较简单的,这里检查URL内是否含有http,大家可以根据自己的需求将判断逻辑这块加强,将错误之后抛异常的动作实现成自动添加http或https至url的开头处等都可。

def get_url(self, url):
    if self.driver != None:
        sleep(1)
        if 'http' in url:
            self.driver.get(url)
        else:
            session.add(err_message)
            session.commit()
    else:
        session.add(fail_message)
        session.commit()

  如果你的UI测试中不需要将用例的结果进行数据持久化,可以替换两个判断分支中的业务操作,打印到后台还是写入文件根据自己的测试流程要求来自定义即可。这里的数据库操作使用了sqlalchemy模块,我们定义db创建一个ORM基类,拿博主的脚本举例,我的ORM模型名(表名)为TestCaseResult,将各个测试场景(URL格式检查与浏览器对象检查)下的错误场景报错信息写入。我们的基本信息如下,执行后插入一条数据,包含错误代码,结果具体信息与一个复合用例的标识判断。err_message = TestCaseResult(status='100200', result="URL格式有误", is_composite='False')可以看出错误信息的内容还是比较简单的。我们只需在执行过后检查对应的自动化平台结果页面即可看到对应报错信息。
  另外使用sqlalchemy操作数据库前记得创建对应的数据库对象。

engine = create_engine("mysql://root:dEDsofe@19admv@172.20.30.241/rtz_fund_trade?charset=uft8",
            echo=True,
            pool_size=8,
            pool_recycle=60*30
            )

这里在连接参数后有三个选项,分别为:
echo: 当设置为True时会将orm语句转化为sql语句打印,一般debug的时候可用
pool_size: 连接池的大小,默认为5个,设置为0时表示连接无限制
pool_recycle: 设置时间以限制数据库多久没连接自动断开

当然我们本着易维护的思想理念,还是将数据库接连的动作进行常量设定。

HOST = '172.20.30.241'
PORT = '3306'
DATABASE = 'rtz_fund_trade'
UNAME = 'root'
PASSWD = 'dEDsofe@19admv'
DB_URL = 'mysql+pymysql://username:pwd@host:port/db?charset=utf8' \\
    .format(username=UNAME, pwd=PASSWD, host=HOST, port=PORT, db=DATABASE)
engine = create_engine(DB_URL)

  接下去使用engine = create_engine(DB_URI,echo=True)进行数据库的连接,因为操作数据库必须创建会话来进行控制,所以我们还需要使用session = sessionmaker(engine)()创建一个会话。之后就如最初的代码中所进行的操作来进行数据库的数据写入。当然以上说的这些操作大家应该将其也封装为一个或者多个类。

 

2.1.3 浏览器窗口操作

  窗口操作也是比较常用的基础功能之一,以下将基本的最大化、最小化、前进、后退、刷新、设定尺寸大小封装起来。之后会判断可变参数的长度,根据传入的长度不同进行对应的窗口操作。

def browser_handle(self, *args):
    param = len(args)
    if param == 1:
        if args[0] == 'max':
            self.driver.maximize_window()
        elif args[0] == 'min':
            self.driver.minimize_window()
        elif args[0] == 'forward':
            self.driver.forward()
        elif args[0] == 'back':
            self.driver.back()
        else:
            self.driver.refresh()
    elif param == 2:
        self.driver.set_window_size(args[0], args[1])
    else:
        session.add(fail_message)
		session.commit()
    sleep(2)

 

2.1.4 切换窗口

  另一个日常较为频繁的业务操作就是切换窗口,也就是我们的标签页,我们可以使用遍历的方式获得一个当前所有的窗口列表,通过传递默认参数title来进行当前窗口的切换,直到匹配到与title相同的窗口。

def switch_windows(self, title=None):
    windows_list = self.driver.window_handles
    current_window = self.driver.current_window_handle
    for i in windows_list:
        if i != current_window:
            time.sleep(1)
            self.driver.switch_to.window(i)
            if self.assert_title(title):
                break

 

2.1.5 获取页面元素

  元素定位自然不必多说了,web自动化中的基础操作,也是日常接触的最多的功能,封装的功能只需传两个参数,定位方式与元素对应的属性值。这里可以改造的地方还是有很多的,比如不手动指定,通过持久化或者文件指定对应要查找的元素,需要定位的元素属性也可以通过其他方式进行抽出,总之二开的话大家可以根据业务需求进行灵活多变的定制。另外elements的定位就不演示了,大家举一反三即可。

def get_element(self, by, ele):
    element = None
    try:
        if by == 'id':
            element = self.driver.find_element(By.ID, ele)
        elif by == 'name':
            element = self.driver.find_element(By.NAME, ele)
        elif by == 'css':
            element = self.driver.find_element(By.CSS_SELECTOR, ele)
        elif by == 'class':
            element = self.driver.find_element(By.CLASS_NAME, ele)
        else:
            element = self.driver.find_element(By.XPATH, ele)
    except:
        session.add(ele_err_msg)
        session.commit()
    return element

 

2.1.6 层级元素定位

  层级元素定位的实现逻辑其实就是根据链式写法产生的,原生的find_element()方法是可以从当前捕捉到的元素层级开始往下再次定位的,我们就利用这一特性,先使用上一个定位元素的方法get_element()来再一次进行find_element()方法,这样就可以实现层级元素的定位操作。这里有一点需要注意的是,虽然原理如此,但切不可偷懒,调用两次get_element(),因为这个方法本身含有driver对象,两次调用会使程序无法识别具体使用的是哪个对象,从而导致报错。

def get_level_element(self, by, ele, ch_by, ch_ele):
    element = self.get_element(by, ele)
    ch_element = None
    try:
        if ch_by == 'id':
            ch_element = element.find_element(By.ID, ch_ele)
        elif ch_by == 'name':
            ch_element = element.find_element(By.NAME, ch_ele)
        elif ch_by == 'css':
            ch_element = element.find_element(By.CSS_SELECTOR, ch_ele)
        elif ch_by == 'class':
            ch_element = element.find_element(By.CLASS_NAME, ch_ele)
        else:
            ch_element = element.find_element(By.XPATH, ch_ele)
    except:
        session.add(ele_err_msg)
        session.commit()
    return ch_element

 

2.1.7 信息输入操作

  输入操作的封装也是相对比较直白的,定位元素并传值即可。需要注意的点就是如果定位的元素本身出了问题的话,我们可以利用判断条件来规避一些异常的情况。

def send_info(self, by, ele, info):
    element = self.get_element(by, ele)
    if element is not None:
        element.send_keys(info)
    else:
        session.add(null_key_msg)
        session.commit()

 

2.1.8 点击操作

  逻辑同信息输入操作,大家自行体会。

def click_element(self, by, ele):
    element = self.get_element(by, ele)
    if element is not None:
        element.click()
    else:
        session.add(null_key_msg)
        session.commit()

 

2.1.9 控件操作

  控件的种类还是比较多的,这里就举个比较典型的例子。下面封装的是一个复选框(勾选框),这里的传参前两个就不介绍了,最后一个表示复选框目前的勾选状态,我这里定义的0为未勾选,1为已勾选状态。这里的实现逻辑大致为:判断对象是否为勾选状态,再判断是否需要勾选,结合两种状态一般就是有4个结果,勾选状态下勾选和不勾选、未勾选状态下勾选和不勾选。大家可以根据以下的判断逻辑解读一下。

def check_box(self, by, ele, selected=None):
    element = self.get_element(by, ele)
    flag = element.is_selected()
    if flag == True:
        if selected == 0:
            self.click_element(by, ele)
    else:
        if selected == 1:
            self.click_element(by, ele)

 

2.1.10 元素可见性操作

  对于页面上的某些元素是否可见,我们也可以封装一个方法,用来增强整体的元素定位方法的健壮性,该方法可以直接在元素定位时进行调用,将原有的返回对象进行预先判断。

def ele_display(self, ele):
    flag = ele.is_displayed()
    if flag == True:
        return ele
    else:
        session.add(ele_dis_msg)
        session.commit()

  以上就是一些日常工作中使用的比较频繁的selenium内置方法的封装示例。

 
 

3. 一些题外话

  博主最近也是比较的繁忙,各类测试项目加上宣讲了几场外部企业培训,实在是分身乏术,忙碌之余还在感慨2023年能有一份工作已是万幸😅。平时只能抽空回复回复大家的问题和一些简单互动,文章也是每天抽空写那么一点点,这里再次向大家说一声抱歉。后续有空的话会在原计划的基础上加更几篇关于selenium的自动化UI测试框架中涉及到的一些进阶知识点与编码实例、技巧。如果大家有任何想看的内容也可以私信给我,有时间就会穿插的安排上。

web自动化测试入门篇03——selenium使用教程

   

   

(阅读目录)

1. 目的

  web自动化测试作为软件自动化测试领域中绕不过去的一个“香饽饽”,通常都会作为广大测试从业者的首选学习对象,相较于C/S架构的自动化来说,B/S有着其无法忽视的诸多优势,从行业发展趋、研发模式特点、测试工具支持,其整体的完整生态已经远远超过了C/S架构方面的测试价值。接上一篇文章,我们将继续深入探讨Selenium的相关使用方法与技巧。

   

2. 说明

  此篇中所用的技术栈为Selenium+Python,因其本身编程难度不高,总体思想都是基于面向对象的编程理念,故只要大家的编码语言基础不弱,就完全可以做到平替。

   

3. 关于解答上一篇的问题

  在正式启动浏览器之前,这里还需要说说上一篇没有提及的一个问题,后台有同学私信说在对应驱动网站上完全找不到自己浏览器对应版本的驱动,关于浏览器驱动的版本,大家都知道驱动的版本应该是要与浏览器的版本完全对应上的。但往往日常工作中因为环境或者其他客观因素会导致浏览器的版本五花八门,也会出现驱动网站上完全没有你目前工作环境中对应的浏览器版本,这里我们大可使用中版本号一致的驱动来进行尝试,没有必要将版本进行完全的匹配。比如你的Chrome浏览器的版本号为107.0.5304.107,如果这时对应网站只有107.0.5304.62108.0.5359.22的前后两个版本的驱动比较相近,这里就有两个选择,第一就是卸掉原有的浏览器,安装对应的版本;第二则是选择107.0.5304.62来进行尝试;相信大家在成本耗时的判断下,一般都会选择第二个选项来进行尝试对吧?那么我可以很负责任的告诉你,第二个判断是完全行的通的。不必太过纠结驱动的小版本号是否完全匹配。

   

4. 启动

  我们将一切准备就绪后就可以开始我们的selenium之旅了,首先我们需要将使用selenium进行浏览器的启动(访问百度)。

from selenium import webdriver


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

 

  如果对应的浏览器驱动没有进行环境变量的设置,可以在代码中直接进行程序的路径指定来进行后续的浏览器操作。

from selenium import webdriver


path = rC:\\Program Files\\Google\\Chrome\\Application\\chromedriver.exe
browser = webdriver.Chrome(path)
browser.get(https://www.baidu.com)

 

  使用Selenium打开后的浏览器都会标识出正在受到自动化测试软件控制的相关提示。

   

5. 浏览器基础操作

  Selenium本身提供给了我们非常多的各类浏览器与页面的操作函数,方便我们根据所需测试业务来进行自由搭配使用甚至是二开。

 

5.1 浏览器窗口操作

  对于浏览器窗口的尺寸进行控制与获取,如最大化、最小化、指定窗口大小等。

 

窗口全屏

browser.fullscreen_window()

  窗口最大化

browser.maximize_window()

  窗口最小化

browser.minimize_window()

  自定义窗口大小(宽X高)

browser.set_window_size(1080, 720)

  自定义窗口坐标位置与大小(x坐标,y坐标,宽X高)

browser.set_window_rect(100, 200, 1080, 720)

  获取窗口的大小(宽X高)

browser.get_window_size()

  获取窗口的坐标位置,返回一个字典对象

browser.get_window_position()

  获取窗口的坐标与大小(x坐标,y坐标,宽X高)

browser.get_window_rect()

  获取当前窗口的句柄

browser.current_window_handle

  获取当前所有窗口的句柄

browser.window_handles

   

5.2 页面基础操作

  对于浏览器当前页面的一些操作,如前进、后退、刷新等。

  前进(下一页面)

browser.forward()

  后退(上一页面)

browser.back()

  刷新(当前页面)

browser.refresh()

  截图并保存为test.png(当前页面)

browser.save_screenshot(test.png)

  截图并保存为png文件(当前页面)

browser.get_screenshot_as_file(test_02.png)

  截图并将信息转为base64编码的字符串

browser.get_screenshot_as_base64()

   

5.3 信息操作

  对于浏览器当前一些信息的获取与操作。

  获取页面URL(当前页面)

browser.current_url

  获取日志类型,会返回一个列表对象

browser.log_types

  获取浏览器操作日志,注意函数内的参数为固定值类型browser

browser.get_log(browser)

  获取设备操作日志,参数原理同上

browser.get_log(driver)

  获取当前页面标题

browser.title

  获取当前浏览器的名字

browser.name

   

5.4 元素操作

  Selenium中最基础也是最重要的一环,基本上对于页面的业务操作大多数都集中与此。另外需要注意的是元素定位所使用的find_element_by的方法在很早之前就已经被废弃,这里同样也会使用最新的find_element方法进行讲解。   如何查看页面中的元素与其相关属性,这里以Chrome为例,我们只需按F12或者右键页面选择“检查”,再点击调试窗口的左上角的箭头标志或者使用快捷键Ctrl+Shift+C来进行元素的选取,此时Elements标签页中会将焦点对应跳转至该元素的html代码行中,接下来我们就可以针对不同的元素和不同的属性来进行定位操作。

 

 

5.4.1 name定位

通过一个元素的name属性来进行定位。

 

比如定位百度中的搜索栏,我们通过name属性来进行定位。该元素的html构造如下:

<input type="text" class="s_ipt" name="wd" id="kw" maxlength="100" autocomplete="off">

  我们只需将name属性后面的值拿出,赋予给find_element方法即可。新的By方法我们只需要导入selenium.webdriver.common.by下的By方法即可。

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)

browser.find_element(By.NAME, wd)

 

5.4.2 class name定位

通过一个元素的class属性来进行定位。

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.CLASS_NAME, s_ipt)

 

5.4.3 id定位

通过一个元素的id属性来进行定位。

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.ID, kw)

 

5.4.4 css定位

css selector也被成为选择器定位,它通过页面内的元素的id、name、tag三个属性来进行定位,根据元素属性的重复程度,可以单独属性定位也可组合属性来进行定位。而且相较于xpath定位方式来说,博主更推荐使用此方法来进行定位,无论是易用度还是维护性来说比xpath定位好的多。

  单属性定位--tag属性

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.CSS_SELECTOR, input)

  单属性定位--id属性

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.CSS_SELECTOR, #kw)

  单属性定位--class属性

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.CSS_SELECTOR, .s_ipt)

  多属性定位--tag+id属性

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.CSS_SELECTOR, input#kw)

  多属性定位--tag+class属性

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.CSS_SELECTOR, input.s_ipt)

  同样的,其他的组合方式大家可以举一反三,不断尝试,比如模糊匹配input[class ~= "局部关键字"]、层级定位#form > span > input等等等等。

 

5.4.5 link text定位

这种定位方式适用于页面中带有超链接的元素,直接通过超链接标签内的文字进行元素定位。

我们以百度首页为例,可以看到该页面中有很多的超链接标签,如果我们想模拟点击跳转至新闻对应页面的操作,就可以用link text的元素定位方法来进行实现。

  使用超链接标签对中的“新闻”一词来进行定位。

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.LINK_TEXT, 新闻).click()

 

5.4.6 partial link text定位

这个定位方式与link text定位十分相像,实际上也就是link text的模糊查找定位方式,对象也是超链接内的文字,只不过他匹配的不是全部文字而是局部。

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.PARTIAL_LINK_TEXT, 新).click()

 

5.4.7 tag定位

tag定位的效率总体来说不高,也不太推荐单独使用,html页面中一般也是由很多相同或不同的标签对组成。就tag而言标签重复的越多,定位的效率其实也就越低。

比如我们想在百度的搜索栏中输入“selenium”关键字,那么光使用tag其实就很难达到我们的目的,甚至无法准确定位到我们想要的元素。如果运气好,搜索栏的input标签在html页面中排在第一位那还好,只要不是第一位,我们就需要编写其他的代码逻辑来辅助我们继续定位这个元素。  

所以下面的代码实在是不能称之为高效的执行代码

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.TAG_NAME, input).send_keys(selenium)

 

5.4.8 xpath定位

一般来说无法通过以上的这些元素定位方法定位的情况下,我们会使用xpath定位方法。但这里需要特别注意,xpath方法分为绝对路径和相对路径两种定位方式,博主只推荐如果真要使用xpath就使用相对路径+正则表达式的方式来进行元素定位。不推荐绝对路径的原因就不用博主多说了吧,只要你敢用,后期的脚本维护与复用绝对会让你抓狂的。

 

还是老样子,我们使用xpath的相对路径写法来定位百度首页的搜索栏。

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.XPATH, //*[@id="kw"]).send_keys(selenium)

  另外与find_element方法相对应的find_elements方法这里就不多做介绍了,该种方法是将当前页面中所有能匹配上对应元素定位方法的元素全部获取。大家可以根据自己的需求来进行选取和使用。

   

5.5 延时方式

  我们加载页面时通常会因为网络环境等各方面的客观因素而导致元素加载的速度各不相同,如果此时我们没有对业务操作进行一定的延时执行,那么大概率业务操作会出现各类的no such element报错。   那么我们就需要在页面元素加载完成之后再对相应的元素进行业务操作来规避上面说的这个问题。Selenium内可以使用三种延时的函数来进行对应的延时业务操作。

 

5.5.1 隐式等待

隐式等待的作用是在页面加载是隐性的进行特定时长的等待,如果在规定的等待时长内页面加载完毕,则会继续进入下一个业务操作,如果没有加载完毕,则会抛出一个超时的异常。这里其实有两个问题,第一,隐式等待是全局性质的,也就是说一旦你设置了个5秒,那整个程序都会使用这个等待时间类进行配置,灵活性较低;第二,如果碰到了有些页面中的元素是局部加载的话,那整个页面的加载是否完成也就没有了其意义,隐式加载无法针对这样的情况作出调整,智能度较低。所以一般来说只要是对于页面的整体加载要求不高或者元素的加载比较稳定的程序,都可以使用隐式等待来进行延时操作。

 

from selenium import webdriver
from selenium.webdriver.common.by import By


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
browser.implicitly_wait(5)
browser.find_element(By.XPATH, //*[@id="kw"]).send_keys(selenium)

 

5.5.2 显式等待

显式等待的作用则是使用特定的等待时长来进行某些业务逻辑判断,如果判断(比如元素是或否加被定位)在时间完成那继续执行下一个业务操作,如果判断失败也会抛出no such element的异常,而显式等待的默认检查元素周期为0.5秒。乍一看好像与隐式等待差不多,其实不然,首先显式等待是针对页面中某个或某组特定元素而执行的,隐式则是全局,对所有的元素都生效;其二,显式等待可以通过自定义条件来进行元素的定位和选取,隐式则不行。

 

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


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
ele = WebDriverWait(browser, 10, 0.5).until(EC.presence_of_element_located((By.XPATH, //*[@id="kw"])))
ele.send_keys(selenium)

 

5.5.3 强制等待

这个应该是平时大家代码中用的最多的等待方式了吧,sleep是针对线程进行挂起的一种等待方式,等待时长根据指定的参数来进行决定。最大的好处就是简单粗暴,无任何逻辑在里面,所以也被称为强制等待。

 

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep

browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
sleep(2)
browser.find_element(By.XPATH, //*[@id="kw"]).send_keys(selenium)

  那么以上的三种等待方式其实各有各的特点与缺点,三者之间没有绝对的好用和不好用,而在我们的日常工作场景中也希望大家可以根据实际的情况有选择性的使用。

   

5.6 超时等待

  元素加载超时我们可以使用以上三种延时方式来进行处理,那么页面超时了又该如何操作呢?selenium也为我们准备了两个函数来对应这样的局面。   页面加载超时

browser.set_page_load_timeout(30)

这里推荐将超时的时间有效的拉长,不宜过短。过短的超时时间容易导致整体页面出现未加载html代码情况下直接令驱动无法工作的情况。     页面异步js或ajax操作超时

browser.set_script_timeout(30)

这个函数是用于execute_async_script()相关的异步js操作超时报错,由于是异步操作,等待时间同理也不易过短。

   

5.7 键鼠操作

  浏览器中键盘与鼠标的操作也是不可或缺的重要一环,在被测对象的业务要求中往往占有不少的戏份。

  文字输入

browser.find_element(By.ID, kw).send_keys(selenium)

  点击

browser.find_element(By.ID, kw).click()

  点击并按住不放(左键长按),这些模拟鼠标操作需要导入ActionChains包

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw)
act.click_and_hold(ele).perform()

  右键点击

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw)
act.context_click(ele).perform()

  双击

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw)
act.double_click(ele).perform()

  拖拽元素至另一个元素处,ele_a 为source,ele_b 为target

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele_a = browser.find_element(By.ID, btn_a)
ele_b = browser.find_element(By.ID, btn_b)
act.drag_and_drop(ele_a, ele_b).perform()

  拖拽元素至指定位置后松开,元素后为x,y坐标值

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, btn_a)
act.drag_and_drop_by_offset(ele, 200, 100).perform()

  鼠标移动至指定元素

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, btn_a)
act.move_to_element(ele).perform()

  按下指定的键位(示例代码中是回车键)

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw).send_keys(selenium)
act.key_down(Keys.ENTER).perform()

  松开指定的键位,这里也可以用链式写法

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw).send_keys(selenium)
act.key_down(Keys.ENTER)
act.key_up(Keys.ENTER)
# 链式写法
act.key_down(Keys.ENTER).act.key_up(Keys.ENTER).perform()

  移动鼠标到指定坐标位置

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw).send_keys(selenium)
act.move_by_offset(100, 200).perform()

  移动到距离指定元素多少距离的位置(从左上角0, 0开始计算)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw).send_keys(selenium)
act.move_to_element_with_offset(ele, 100, 200).perform()

  在指定元素位置松开鼠标

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw).send_keys(selenium)
act.click_and_hold(ele).release(ele).perform()

  发送指定的键或者内容至指定元素

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw).send_keys(selenium)
act.send_keys_to_element(ele, selenium).perform()

  暂停所有操作,默认单位为秒

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw).send_keys(selenium)
act.context_click(ele).pause(5).double_click(ele).perform()

   

5.8 组件操作

  页面中也存在着很多不同种类的组件,比如单选框、多选框、下拉列表、选项卡等。这些操作也可以通过selenium提供的函数进行实现。

  清除指定元素中的内容(输入框等)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw)
ele.send_keys(selenium)
sleep(2)
ele.clear()

  提交确认(类似于Keys.ENTER的效果)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from time import sleep


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

act = ActionChains(browser)
ele = browser.find_element(By.ID, kw)
ele.send_keys(selenium)
sleep(2)
ele.submit()

  下拉列表,我们就可以使用Select方法来实现选取操作 使用Select方法需要从selenium.webdriver.support.select导入该方法 例如下图中某网站的下拉列表对象

  html构造如下

  select_by_index()方法

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select


browser = webdriver.Chrome()
browser.get(https://www.xxxx.com)

ele = browser.find_element(By.ID, input_factor_gj_count)
# 需要注意下标要从0开始,选择1%那一项
Select(ele).select_by_index(0)

  select_by_value()方法

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select


browser = webdriver.Chrome()
browser.get(https://www.xxxx.com)

ele = browser.find_element(By.ID, input_factor_gj_count)
# 指定元素的value属性值,选择1%那一项
Select(ele).select_by_value(0.01)

  select_by_value()方法

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select


browser = webdriver.Chrome()
browser.get(https://www.xxxx.com)

ele = browser.find_element(By.ID, input_factor_gj_count)
# 指定元素的文本值,选择1%那一项
Select(ele).select_by_visible_text(1%)

  框架切换(Frame) 如果在页面中定位某一个元素失败并检查其属性并没有问题时,就该考虑是否在祖先节点中是否存在frame或者iframe标签。这样的页面就表名存在多层框架嵌套,这时我们就需要进行框架切换的操作,来准确定位到指定元素。

例如某页面存在两层frame嵌套,内部框架的xpath为://*[@id="mainDiv"]/iframe,此时如果定位某个输入框失败之后,我们就应该转而跳入该frame内进行元素定位。  

from selenium import webdriver
from selenium.webdriver.common.by import By


browser = webdriver.Chrome()
browser.get(https://www.xxxx.com)
# 定位到指定的frame
path = browser.find_element(By.XPATH, //*[@id="mainDiv"]/iframe)
# 切换至该frame内
browser.switch_to_frame(path)

ele = browser.find_element(By.CLASS_NAME, input)
ele.send_keys(selenium)

  标签页切换 我们浏览器在使用中一般会打开多个浏览窗口,也就是多个标签页。此时我们就可以通过每个标签页的句柄来进行定位和互相切换。

switch_to_window() 我们利用浏览器窗口的句柄来进行标签页的切换

from selenium import webdriver
from selenium.webdriver.common.by import By


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)
# 第一个窗口的句柄下标为0,打开第二个就是1
browser.switch_to_window(browser.window_handles[1])
# 此时就会使用第二个标签页去访问淘宝
browser.get(https://www.taobao.com)

  弹窗处理 页面中时常也存在着各类的弹窗,比如警告、确认、提示等等,那么对于这些弹窗我们也有着相应的业务处理方法。   首先我们需要明确的是每种类型的弹窗中元素也是各不相同,所以我们针对不同的弹窗使用不同的属性来定位和操作。

  警告弹窗 一般来说就是告知到使用者执行某些操作与页面之后将要注意的事项,这种窗口只需确认。

from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By


browser = webdriver.Chrome()
browser.get(https://www.xxxx.com)
browser.find_element(By.ID, btn_tg_title).click()
sleep(1)
# 在警告弹窗中点击确认按钮
browser.switch_to_alert().accept()

  确认弹窗 多用于在用户执行提交操作后的动作确认,有确认和取消两个选项。

from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By


browser = webdriver.Chrome()
browser.get(https://www.xxxx.com)
browser.find_element(By.ID, btn_submit).click()
sleep(1)
#确认二选一
browser.switch_to_alert().accept()
#取消二选一
browser.switch_to_alert().dismiss()

  提示弹窗 这个通常用来处理用户信息交互的场景,用户可以通过弹窗输入一些文字信息,来传递与进行后续的业务处理。

from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By


browser = webdriver.Chrome()
browser.get(https://www.xxxx.com)
browser.find_element(By.ID, btn_info_inquiry).click()
sleep(1)
# 这里传递信息到提示弹窗,警告与确认两类弹窗不可使用,会报错
browser.switch_to_alert().send_keys(20100909)

   

5.9 鉴权操作

  既然是web自动化测试,那我们又怎么能少了Cookie的相关操作呢?用户的状态和业务请求都的需要用他来进行鉴权。在selenium中对Cookie进行操作其实也很简单,首先我们需要手动的登录被测网站一次,待网站Cookie存入本地后即可使用Selenium自带的函数对齐进行业务操作。

  首先我们获取对应网站的Cookie值

import json
from selenium import webdriver


browser = webdriver.Chrome()
browser.get(https://www.baidu.com)

# 写入保存为文件还是直接打印至控制台,各取所需
with open(cookies.txt, w) as f:
    f.write(json.dumps(browser.get_cookies()))

cookies = browser.get_cookies()
print(cookies)

  接下来我们简单的对获得的Cookie进行处理

# 删除所有的cookies
browser.delete_all_cookies()
# 循环打印cookie中的name和value
for c in cookies:
	# 查看打印出的cookie对应的键值对
    print("%s : %s" % (c[name], c[value]))
# 根据自己的业务需求进行cookie的增删改
cookie = "name": "xxxx", "value": "xxxx"
# 最后添加即可
driver.add_cookie(cookie)

   

5.9 js操作

  Selenium也为我们提供了可执行js脚本相关的函数,他们的作用是在某些页面中模拟一些业务动作(画面滑动,选择日期等)。在一些仅靠webdriver无法实现的业务场景中,我们就可以依靠此函数来辅助测试目的的达成。

 

js的查找元素方法(ID)

document.getElementById("id")

  js的查找元素方法(name)

document.getElementsByName(name)

  js的查找元素方法(class)

document.getElementsByClassName("class_name")

  js的查找元素方法(tag)

document.getElementsByTagName(tag_name)

  js的查找元素方法(css)

document.querySelector("css selector")

  js的查找元素方法(css_list)

document.querySelectorAll("css selector")

  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)

   

6. 注意点与小技巧

  1. 对于某些动态div标签(窗口),一般的方法不太奏效的情况下,可以尝试下switch_to_default_content()方法,跳转到最外层;

  2. 使用模拟键鼠操作的时候,无论是单独使用还是链式写法,记得在结尾加上perform()方法进行执行;

  3. 如果元素定位时报错element click intercepted,记得检查界面上是否有其他元素进行覆盖,元素有可能也是具有隐藏属性的;

  4. 元素过期报错element is not attached to the page document,可以尝试重新刷新页面,这里不推荐直接使用refresh方法,还是养成好习惯先怼上try...except...捕捉到异常后在进行刷新或重置操作;

  5. 对于属性值为动态的元素,墙裂推荐使用CSS selector或者xpath方法来进行元素定位,正则表达式也推荐大家最好能掌握;

  6. 如果前期对xpath的相对路径写法比较头疼,推荐使用F12调试工具自带的元素复制功能,在你想要复制的元素所在的标签对这行右键,选择copy —— Copy XPath选项即可;

  7. 输入框默认存在内容想要删除再输入信息的话,不推荐模拟键盘操作Ctrl+A,然后模拟退格键,试试clear()方法吧;

  8. 抓不到元素可以使用延时方法,输入文字也是一样的道理,业务场景中需要大量输入文字的,无论是从文件中还是提取又或者是遍历,出现少字漏字的话,同样也可以使用延时的方法,适当的放慢处理的速度;

  9. 在页面中总会有些不可见的元素,这种情况使用is_displayed()方法即可快速定位找到;

  10. 有些被测页面需要验证码,无论是手机的还是图片验证,和开发同学沟通一下,留个万能的就行了,其本身的功能手工回归一下即可,不必太过纠结;

  11. 三方登录功能也是如此,不推荐直接使用web自动化去搞,三方的一般是不开源的,有这折腾的时间还不如跑跑接口和黑盒,自动化的话绕过去即可;

  12. 自动化测试用例这块的话博主还是推荐不要有依赖,和功能测试用例同理,每次的执行顺序或者数量都会根据需求与业务场景发生变化,一旦依赖了会大大增加测试同学的维护成本,后面有空我会单独出一期自动化测试用例的文章来为大家介绍。

以上是关于web自动化测试入门篇04——selenium+python基础方法封装的主要内容,如果未能解决你的问题,请参考以下文章

web自动化测试入门篇03——selenium使用教程

web自动化测试入门篇02——selenium安装教程

web自动化-selenium 入门篇

selenium+python+unittest实现自动化测试(入门篇)

python + selenium + unittest 自动化测试框架 -- 入门篇

python自动化测试应用-第7篇(WEB测试)--Selenium进阶篇