一、 软件测试分类

1.1 根据项目流程阶段划分软件测试

1.1.1 单元测试

  单元测试(或模块测试)是对程序中的单个子程序或具有独立功能的代码段进行测试的过程。

1.1.2 集成测试

  集成测试是在单元测试的基础上,先通过单元模块组装成系统或子系统,再进行测试。重点是检查模块之间的接口是否正确。

1.1.3 系统测试

  系统测试是针对整个产品系统进行的测试,验证系统是否满足需求规格的定义,以及软件系统的正确性和性能等是否满足其需求规格的要求。

1.1.4 验收测试

  验收测试是部署软件之前的最后一个测试阶段。验收测试的目的是确保软件准备就绪,向软件购买者展示该软件系统能够满足用户的需求。

selenium2自动化测试实战--基于Python语言
    





            
WebDriver API

1.2 白盒测试、黑盒测试、灰盒测试

  白盒测试与黑盒测试,主要是根据软件测试工作中对软件代码的可见程度进行的划分。这也是软件测试领域中最基本的概念之一。

1.2.1 黑盒测试

  黑盒测试,指的是把被测的软件看做一个黑盒子,我们不去关心盒子里面的结构是什么样子的,只关心软件的输入数据和输出结果。

  它只检查程序呈现给用户的功能是否按照需求规格说明书的规定正常使用、程序是否能接受输入数据并产生正确的输出信息。黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功能进行测试。

1.2.2 白盒测试

  白盒测试,指的是把盒子打开,去研究里面的源代码和程序执行结果。

  它是按照程序内部的结构测试程序,通过测试来检验产品内部动作是否按照设计规格说明书的规定正常进行,检验程序中的每条逻辑路径是否都能按预定要求正确工作。

1.2.3 灰盒测试

  灰盒测试介于黑盒测试和白盒测试之间。

  可以这样理解,灰盒测试既关注输出对于输入的正确性,同时也关注内部表现。但这种关注不像白盒测试那样详细、完整,它只是通过一些表征性的现象、事件、标志来判断内部的运行状态。有时候输出是正确的,但内部其实已经错误了,这种情况非常多。如果每次都通过白盒测试来操作,效率会很低,因此需要采取灰盒测试的方法。

 

1.3 功能测试与性能测试

  从软件的不同测试面可以划分为功能测试与性能测试

1.3.1 功能测试

  功能测试主要检查世纪功能是否符合用户的需求,因此测试的大部分工作也是围绕软件的功能进行。设计软件的目的就是满足用户对其功能的需求,如果偏离了这个目的,则任何测试工作都是没有意义的。

功能测试又可以细分为很多种:逻辑功能测试,界面测试、易用性测试、安装测试、兼容性测试等。

1.3.2 性能测试

  性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行的测试。

  软件的性能包括很多方面,主要有时间性能和空间性能两种。

  • 时间性能:主要是指软件的一个具体的响应时间。例如一个登陆所需要的时间,一个商品交易所需要的时间等。当然,抛开具体的测试环境,来分析一次事务的响应时间是没有任何意义的,它需要在搭建好的一个具体且独立的测试环境下进行。
  • 空间性能:主要指软件运行时所消耗的系统资源,例如硬件资源,CPU、内存、网络宽带消耗等。

 

1.4 手工测试与自动化测试

从对软件测试工作的自动化程度可以划分为手工测试与自动化测试。

1.4.1 手工测试

手工测试就是由测试人员一个一个地去执行测试用例,通过键盘鼠标等输入一些参数,并查看返回结果是否符合预期结果。

手工测试并非专业术语,手工测试通常是指我们在系统测试阶段所进行的功能测试,为了更明显地与自动化测试进行区分,这里使用了手工测试这种说法。

1.4.2 自动化测试

  自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。通常,在设计测试用例并通过评审之后,由测试人员根据测试用例中描述的规则流程一步步执行测试,把得到的世纪结果与期望结果进行比较。在此过程中,为了节省人力、时间和硬件资源,提高测试效率,便引入了自动化测试的概念。

自动化测试又可分为:功能自动化测试与性能自动化测试。

  • 功能自动化测试:是把以人为驱动的测试行为转化为机器执行的一种过程。通过测试工具(或框架)录制/编写测试脚本,对软件的功能进行测试,并验证测试结果是否正确,从而代替部分的手工测试工作,达到节约人力成本和时间成本的目的。
  • 性能自动化测试:通过性能功能来模拟成千上万的虚拟用户向系统发送请求,从而验证系统的处理能力。

 

1.5 冒烟测试、回归测试、随机测试、探索性测试和安全测试

这几种测试出现在软件测试的周期中,既不算具体明确的测试阶段,也不是具体的测试方法。

1.5.1 冒烟测试

  是指在对一个新版本进行大规模的系统测试之前,先验证一下软件的基本功能是否实现,是否具备可测性。

  引入到软件测试中,就是指测试小组在正是测试一个新版本之前,先投入较少的人力和时间验证一个软件的主要功能,如果主要功能都没有运行通过,则打回开发组重新开发。这样做的好处是可以节省时间和人力投入到不可测的项目中。

1.5.2 回归测试

  回归测试是指修改了旧代码后,重新进行测试以确认修改后没有引入新的错误或导致其他代码产生错误。
  回归测试一般是在进行第二轮软件测试时开始的,验证第一轮软件测试中发现的问题是否得到修复。当然,回归也是一个循环的过程,如果回归的问题通不过,则需要开发人员修改后再次进行回归,直到所有问题回归通过为止。

1.5.3 随机测试

  是指测试中的所有输入数据都是随机生成的,其目的是模拟用户的真实操作,并发现一些边缘性的错误。

  随机测试可以发现一些隐蔽的错误,但是也有很多缺点,例如测试不系统,无法统计代码覆盖率和需求覆盖率、发现的问题难以重现等。一般是放在测试的最后执行。随机测试更专业的升级版叫做探索性测试。

1.5.4 探索性测试

  探索性测试可以说是一种测试思维技术,它没有很多实际的测试方法、技术和工具,但却是所有测试人员多应该掌握的一种测试思维方式。探索性测试强调测试人员的主观能动性,抛弃繁杂的测试计划和测试用例设计过程,强调在碰到问题时及时改变测试策略。

1.5.5 安全测试

  安全测试在IT软件产品的生命周期中,特别是产品开发基本完成至发布阶段,对产品进行检验以验证产品符合安全需求定义和产品质量标准的过程。

  安全测试现在越来越受到企业的关注和重视,因为由于安全性问题造成的后果是不可估量的,尤其是互联网产品,最容易遭受各种安全攻击。

 

二、分层的自动化测试

  我们应该有更多的低级别的单元测试,而不仅仅是通过用户界面运行的高层的端到端的测试。

selenium2自动化测试实战--基于Python语言
    





            
WebDriver API

 

   传统的自动化测试我们可以理解为基于产品UI层的自动化测试,它是将黑盒功能测试转化为由程序或工具执行的一种自动化测试。

   但是在目前的大多数研发组织当中,都存在开发与测试团队割裂(部门墙)、质量职责错配(测试主要对质量负责)的问题,在这种状态下,测试团队的一个“正常”反应就是试图在测试团队能够掌握的黑盒测试环节进行尽可能全面的覆盖,甚至是尽可能全面的  UI 自动化测试。

  这可能会导致两个恶果:一是测试团队规模的急剧膨胀;二是所谓的全面UI自动化测试运动。因为UI是非常易变得,所以UI自动化测试维护成本相对高昂。

  分层自动化测试倡导的是从黑盒(UI)单层到黑白盒多层的自动化测试体系,从全面黑盒自动化测试到对系统的不同层次进行自动化测试。

selenium2自动化测试实战--基于Python语言
    





            
WebDriver API

 

2.1 单元自动化测试

  单元自动化测试是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判断其具体含义,如C语言中单元是指一个函数,Java中单元是指一个类,图形化的软件中单元是指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。规范的进行单元测试需要借助单元测试框架,如Java语言的Junit、TextNG,C#语言的NUnit,以及Python语言的unittest、pytest等,目前几乎所有的主流语言都有其相应的单元测试框架。
  Code Review中文翻译为代码评审或diamante审查,是指在软件开发过程中,通过对源代码进行系统性检查的过程。通常的目的是查找系统缺陷、保证软件总体质量以及提高开发者自身水平。与Code Review 相关的插件和工具有很多,例如Java语言中基于Eclipse的ReviewClipse和Jupiter、主要针对Python语言的Review Board等。

 

2.2 接口自动化测试

  Web应用的接口自动化测试大体分为两类:模块接口测试Web接口测试

2.2.1 模块接口测试

  主要测试模块之间的调用与返回。当然,我们也可以将其看做是单元测试的基础。它主要强调对一个类方法或函数的调用,并对返回结果的验证,所用到的测试工具与单元自动化测试相同。

 2.2.2 Web接口测试

  Web接口测试又可以分为两类:服务器接口测试和外部接口测试。

  • 服务器接口测试:指测试浏览器与服务器的接口。我们知道Web开发一般分前端和后端,前端开发人员用HTML/CSS/JavaScript等技术,后端开发人员用PHP/Java/C#/Python/Ruby等各种语言。用户的操作是在前端页面上,需要后端提供服务器接口,前端通过调用这些接口来获取需要的数据,通过HTTP协议实现前后端的数据传递。
  • 外部接口测试:指调用的接口由第三方系统提供。典型的例子就是第三方登录,例如新上线的产品为了免于新用户注册账号的麻烦会提供第三方登录,纳闷用户在登录的时候调用的就是第三方登录的接口,用户登录信息的验证由第三方完成,并返回给当前系统是否验证通过。

当然,接口测试也有相应的类库或工具,例如测试HTTP的有HttpUnit、Postman等

 

2.3 UI自动化测试

  UI层是用户使用该产品的入口,所有功能都通过这一层提供并展示给用户,所以测试工作大多集中在这一层进行。为了减轻这一层的测试人力和时间成本,早期的自动化测试工具主要针对该层设计。目前主流的测试工具有UFT、Watie、Robot Framework、Selenium等。

  除UI层所展示的功能外,前端代码同样需要进行测试。在前端开发中最主要的莫过于JavaScript脚本语言,而QUnit就是针对JavaScript的一个强大的单元测试框架。

   测试金字塔映射了不同测试阶段所投入的自动化测试的比例,UI层被放到了塔尖,这也说明UI层应该投入较少的自动化测试。如果系统只关注UI层的自动化测试并不是一种明智的做法,因为其很难从本质上保证产品的质量。如果妄图实现全面的UI层的自动化测试,那么需要投入大量的人力和时间,然而, 最终获得的收益可能远低于所投入的成本,因为对于一个系统来讲,越接近用户其越容易变化,为了适应这种变化就必须要投入更多的成本。

  既然UI层的自动化测试这么劳民伤财,那么我们是不是只做单元测试与接口测试就可以了呢?答案是否定的,因为不管什么样的产品,最终呈现给用户的都是UI层的功能,所以产品才需要招聘大量的测试人员进行UI层的功能测试。也正是因为测试人员在UI层投入了大量的时间与精力,所以我们才有必要通过自动化的方式帮助测试人员解放部分重复的工作。所以,应该更多的提倡“半自动化”的开展测试工作,把可以自动化测试的工作交给工具或脚本完成,这样测试人员就可以把更多的精力放在更重要的测试工作上,例如探索性测试等。

  至于在金字塔中每一层测试的投入比例则要根据实际的产品特征来划分。在《Google测试之道》一书中提到,Google对产品测试类型划分为:小测试、中测试和大测试,采用70%(小),20%(中)、10%(大)的比例,大体对应测试金字塔中的Unit、Service和 UI 层。

  在进行自动化测试中最担心的是变化,因为变化会直接导致测试用例的运行失败,所以需要对自动化脚本进行不断调整。如何控制失败、降低维护成本是对自动化测试工具及人员能力的挑战。反过来讲,一份永远都运行通过的自动化测试用例已经失去了它存在的价值。

 

三、什么样的项目适合自动化测试

  1. 任务测试明确,不会频繁变动。
  2. 每日构建后的测试验证。
  3. 比较频繁的回归测试。
  4. 软件系统界面稳定,变动少。
  5. 需要在多平台上运行的相同测试案例、组合遍历型的测试,大量的重复任务。
  6. 软件维护周期长。
  7. 项目进度压力不太大。
  8. 被测软件系统开发较为规范,能够保证系统的可测试性。
  9. 具备大量的自动化测试平台。
  10. 测试人员具备较强的编程能力。

在我们普遍的自动化测试经验中,一般满足以下三个条件就可以对项目开展自动化测试。

1. 软件需求变动不频繁

  自动化测试脚本变化的大小与频率决定了自动化测试的维护成本。如果软件需求变动过于频繁,那么测试人员就需要根据变动的需求来不断地更新自动化测试用例,从而适应新的功能。而脚本的维护本身就是一个开发代码的过程,需要扩展、修改、调试,有时还需要对架构做出调整。如果所花费的维护成本高于利用其节省的测试成本,那么自动化测试就失去了它的价值与意义。

  一种折中的做法是先对系统中相对稳定的模块与功能进行自动化测试,而变动较大的部分用用工进行测试。

2. 项目周期较长

  由于自动化测试需求的确定,自动化测试框架的设计、脚本的开发与调试均需要时间来完成,而这个过程本身就是一个软件的开发过程,如果项目的周期较短,没有足够的时间去支持这样一个过程的话,那么就不需要进行自动化测试了。

3. 自动化测试脚本可重复使用

  自动化测试脚本的重复使用要从三个方面来考量:一是所测试的项目之间是否存在很大的差异性(如C/S系统架构与B/S系统架构的差异);二是所选择的测试技术和工具是否适应这种差异;三是测试人员是否有能力设计开发出适应这种差异的自动化测试框架。

 

四、自动化测试及工具简述

   自动化测试的概念有广义与侠义之分:广义上来讲,所有借助工具来辅助进行软件测试的方法都可以称为自动化测试;狭义上来讲,主要指基于UI层的功能自动化测试。

目前市面上的自动化测试工具非常多,下面几款是比较常见的自动化测试工具。

1.UTF

  UTF有QTP和ST合并而来,有HP公司开发。它是一个企业级的自动测试工具,提供了强大易用的录制回放功能,同时兼容对象识别模式与图像识别模式两种识别方式,支持B/S与C/S两种架构的软件测试,是目前主流的自动化测试工具。

2. Robot Framework

  Robot Framework 是一款基于Python语言编写的自动化测试框架,具备良好的可扩展性,支持关键字驱动,可以同时测试多种类型的客户端或者接口,可以进行分布式测试。

3. Watir

  Watir是一个基于Web模式的自动化功能测试工具。Watir是一个Ruby语言库,使用Ruby语言进行脚本开发。

4. Selenium

  Selenium也是一个用于Web应用程序测试的工具,支持多平台、多浏览器、多语言去实现自动化测试。目前在Web自动化领域应用越来越广泛。

 

当然,除上面所列的自动化测试工具外,根据不同的应用还有很多商业的或开源的以及公司自己开发的自动化测试工具。

 

五、Selenium工具介绍

5.1 什么是Selenium?

  Selenium主要用于Web应用程序的自动化测试,但并不局限于此,它还支持所有基于Web的管理任务自动化。

Selenium的特点如下:

  • 开源、免费
  • 多浏览器支持:Firefox、Chrome、IE、Opera、Edge
  • 多平台支持:Linux Windows MAC
  • 多语言支持:Java Python Ruby C# JavaScript C++
  • 对Web页面有良好的支持
  • 简单(API简单),灵活(用开发语言驱动)
  • 支持分布式测试用例执行

selenium2自动化测试实战--基于Python语言
    





            
WebDriver API

 

5.2 Selenium IDE

  Selenium IDE是嵌入到Firefox浏览器中的一个插件,实现简单的浏览器操作的录制与回放功能。官方定位:

快速地创建bug重现脚本,在测试人员测试过程中,发现bug之后可以通过IDE将重现的步骤录制下来,以帮助开发人员更容易地重现BUG

 IDE录制的脚本可以转换成多种语言,从而帮助我们快速地开发脚本。关于这个功能在后面的章节中我们会着重介绍。

 

5.3 Selenium Grid

  Selenium Grid是一种自动化的测试辅助工具,Gird通过利用现有的计算机基础设施,能加快Web-App的功能测试。利用Grid可以很方便地实现在多台机器上和异构环境中运行测试用例

 

5.4 Selenium RC

  Selenium RC是Selenium家族的核心部分。Selenium RC支持多种不同语言编写的自动化测试脚本,通过Selenium RC的服务器作为代理服务器去访问应用,从而达到测试的目的。

  Selenium RC分为Client Libraries和Selenium Server。Client Libraries库主要用于编写测试脚本,用来控制Selenium Server的库。Selenium Server负责控制浏览器行为。总的来说,Selenium Server主要包括三部分,Launcher/Http Proxy和Core。其中,Selenium Core是被Selenium Server 嵌入到浏览器页面中的。其实Selenium Core就是一堆JavaScript函数的集合,即通过这些JavaScript函数,我们才可以实现用程序对浏览器进行操作。Launcher用于启动浏览器,把Selenium Core加载到浏览器页面当中,并把浏览器的代理设置为Selenium Server的Http Proxy。

 

5.5 Selenium 2.0

  搞清了Selenium 1.0的家族关系,再来看看Selenium 2.0。Selenium 2.0就是把 WevDriver 加入到了这个家族中,简单用公式表示为:

Selenium 2.0 = Selenium1.0 + WebDriver

  需要强调的是,在Selenium 2.0中主推的是WebDriver,可以将其看做Selenium RC 的替代品。因为Selenium为了保持向下的兼容性,所以在Selenium 2.0中并没有彻底地抛弃Selenium RC。如果是初次使用Selenium开发一个新的自动化测试项目,那么可以直接使用WebDriver。

Selenium RC与WebDriver的区别

  Selenium RC是在浏览器中运行JavaScript应用,使用浏览器内置的JavaScript翻译器来翻译和执行selenese命令(selenese是Selenium命令集合)。

  WebDriver是通过原生浏览器支持或者浏览器扩展来直接控制浏览器。WebDriver针对各个浏览器而开发,取代了嵌入到被测Web应用中的JavaScript,与浏览器紧密集成,因此支持创建更高级的测试,避免了JavaScript安全模型导致的限制。除了来自浏览器厂商的支持之外,WebDriver还利用操作系统级的调用,模拟用户输入

Selenium 与 WebDriver 合并原因?

  部分原因是WebDriver 解决了Selenium存在的缺点(例如能够绕过JavaScript沙箱,我们有出色的API)。部分原因是 Selenium 解决了WebDriver存在的问题(例如支持广泛的浏览器),部分原因是因为Selenium的主要贡献者和WebDriver的主要贡献者都觉得合并项目是为用户提供最优秀框架的最佳途径。

WebDriver API

火狐浏览器驱动:https://github.com/mozilla/geckodriver/releases

谷歌浏览器驱动:http://npm.taobao.org/mirrors/chromedriver

Selenium 及 驱动 安装

pip install Selenium
下载浏览器驱动
http://www.seleniumhq.org/download/
把下载的浏览器驱动即相应的exe文件放在PATH下即可。

from selenium import webdriver
import time

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

driver.find_element_by_id("kw").send_keys("Selenium2")
driver.find_element_by_id("su").click()
time.sleep(10)
driver.quit()

一、什么是Selenium 和WebDriver?

  Selenium是一个浏览器自动化操作框架。Selenium主要由三种工具组成。第一个工具SeleniumIDE,是Firefox的扩展插件,支持用户录制和回访测试。录制/回访模式存在局限性,对许多用户来说并不适合,因此第二个工具——Selenium WebDriver提供了各种语言环境的API来支持更多控制权和编写符合标准软件开发实践的应用程序。最后一个工具——SeleniumGrid帮助工程师使用Selenium API控制分布在一系列机器上的浏览器实例,支持并发运行更多测试。在项目内部,它们分别被称为“IDE”、“WebDriver”和“Grid”。

  这里主要介绍它的第二个工具:WebDriver。

  官网上是这么介绍它的:WebDriver is a clean, fast framework for automated testing of webapps. 但是我觉得它并不局限与进行自动化测试,完全可以用作其它用途。

  WebDriver针对各个浏览器而开发,取代了嵌入到被测Web应用中的JavaScript。与浏览器的紧密集成支持创建更高级的测试,避免了JavaScript安全模型导致的限制。除了来自浏览器厂商的支持,WebDriver还利用操作系统级的调用模拟用户输入。WebDriver支持Firefox(FirefoxDriver)、IE (InternetExplorerDriver)、Opera (OperaDriver)和Chrome (ChromeDriver)。 它还支持Android (AndroidDriver)和iPhone (IPhoneDriver)的移动应用测试。它还包括一个基于HtmlUnit的无界面实现,称为HtmlUnitDriver。WebDriver API可以通过Python、Ruby、Java和C#访问,支持开发人员使用他们偏爱的编程语言来创建测试。

from selenium import webdriver

b = webdriver.Firefox()
b.get("http://www.baidu.com")
b.find_element_by_id("kw").send_keys("火影")
b.find_element_by_id("su").click()
b.close()

在不同的编程语言中会有语法的差异,我们跑去这些差异,在不同的语言中实现百度搜索的自动化实例都完成了下面几个操作。

  1. 首先导入Selenium(WebDriver)相关模块。
  2. 调用Selenium的浏览器驱动,获取浏览器句柄(driver)并启动浏览器。
  3. 通过句柄访问百度URL。
  4. 通过句柄操作页面元素(百度输入框和按钮)。
  5. 通过句柄关闭浏览器。

  所以,webDriver支持多种编程语言,也可以看作是多种语言都支持WebDriver,唯一的不同在于不同语言实现的类和方法名的命名差异性。当然,这样做的好处不言而喻:每个人都可以根据自己熟悉的语言来使用 WebDriver 编写自动化测试脚本。

二、WebDriver API

2.1 元素定位

2.1.1 id 定位

find_element_by_id("id")

在前端,一般一个id值是唯一的只属于一个元素

2.1.2 class 定位

find_element_by_class_name("class")
  • 在前端,一般多个元素共用一个class
  • 但 find_element_by_class_name 只返回第一个匹配到class的元素
  • 坏处:当找不到元素则报错
  • 如果想返回所有匹配到class的元素,可看下面代码
lis = find_elements_by_class_name("class")
for i in lis:
    print(t.text)
  • 返回的是一个元素列表,若只匹配到一个也是列表
  • 好处:当没有找到元素时不会报错,而是返回空列表 [] 

2.1.3 name 定位

find_element_by_name("name")

和class一样。也可以使用find_elements_by_name 方法

2.1.4 tag 定位

find_element_by_tag_name("div")
# 就是标签,比如<div>/<span>等

2.1.5 link 定位

  link 定位与前面介绍的几种定位方法有所不同,它专门用来定位文本链接。百度输入框上面的几个文本链接的代码如下:

<a class="mnav" name="tj_trnews" href="http://www.baidu.com">新闻</a>
<a class="mnav" name="tj_trhao123" href="http://www.hao123.com">hao123</a>
<a class="mnav" name="tj_trmap" href="http://map.baidu.com">地图</a>
<a class="mnav" name="tj_trvideo" href="http://v.baidu.com">视频</a>
<a class="mnav" name="tj_trtieba" href="http://tieba.baidu.com">贴吧</a>

  其实可以使用name属性来定位。这里演示link定位的使用

find_element_by_link_text("新闻")
find_element_by_link_text("hao123")
find_element_by_link_text("地图")
find_element_by_link_text("视频")
find_element_by_link_text("贴吧")

  find_element_by_link_text("文本") 方法通过元素标签对之间的文本信息来定位元素。

  • find_element_by_link_text 是精确匹配,需要文本完全相同才能匹配
  • 若需要返回全部匹配到的元素,也需要用 find_elements_by_link_text  

2.1.6 partial link 定位

  partial link 定位是对 link 定位的一种补充,有些文本链接会比较长,这个时候我们可以取文本链接的一部分定位,只要这一部分信息可以唯一地标识这个链接。

<a class="mnav" name="tj_lang" href="#">一个很长很长的文本链接</a>

  可以如下定位:

find_element_by_partial_link_text("一个很长的")
find_element_by_partial_link_text("文本链接")

  find_element_by_partial_link_text()方法通过元素标签对之间的部分文本信息来定位元素。

  前面介绍的几种定位方法相对来说比较简单,理想状态下,在一个页面当中每一个元素都有一个唯一id和name属性值,我们可以通过它们的属性值来找到它们。但在实际项目中并非想象得这般美好,有时候一个元素并没有id、name属性,或者页面上多个元素的id和name属性值相同,又或者每一次刷新页面,id值都会随机变化。怎么办?可以用Xpath与CSS定位。

  • find_element_by_partial_link_text 支持模糊匹配,包含文本则匹配成功
  • 若需要返回全部匹配到的元素,也需要用  find_elements_by_partial_link_text

2.1.7 XPath 定位

  可参考:http://www.w3school.com.cn/xpath/index.asp

  XPath是一种在XML文档中定位元素的语言。因为HTML可以看做XML的一种实现,所以selenium用户可以使用这种强大的语言在web应用中定位元素。

  绝对路径定位

  XPath 有多种定位策略,最简单直观的就是写出元素的绝对路径。

  参考baidu.html前端工具所展示的代码,我们可以通过下面的方式找到百度输入框和搜索按钮。

find_element_by_xpath("/html/body/div/div[2]/div/div/div/from/span/input")
find_element_by_xpath("/html/body/div/div[2]/div/div/div/from/span[2]/input")

  find_element_by_xpath()方法使用XPath语言来定位元素。XPath主要用标签名的层级关系来定位元素的绝对路径,最外层为html语言。在body文本内,一级一级往下查找,如果一个层级下有多个相同的标签名,那么就按上下顺序确定是第几个,例如,div[2]表示当前层级下的第二个div标签。

  利用元素属性定位

  除了使用绝对路径外,XPath 也可以使用元素的属性值来定位。同样以百度输入框和搜索按钮为例:

find_element_by_xpath("//*[@id='kw']")   #注意外层 " 符号和内层  ' 符号
find_element_by_xpath("//*[@id='su']")

  //表示当前页面某个目录下,input 表示定位元素的标签名,[@]表示这个元素的 id 属性值等于 kw。下面通过name和class属性值来定位。

  浏览器开发者工具F12,复制,XPath

  层级与属性结合

  如果一个元素本身没有可以唯一标识这个元素的属性值,name我们可以找其上一级元素,如果它的上一级元素有可以唯一标识属性的值。也可以拿来使用。

find_element_by_xpath('//[@>')

  使用逻辑运算符

  如果一个属性不能唯一地区分一个元素,我们还可以使用逻辑运算符链接多个属性来查找元素。

<input type="text" id="kw" class="su" name="ie">
<input type="text" id="kw" class="aa" name="ie">
<input type="text" id="bb" class="su" name="ie">

  如上面的三行元素,假设我们现在要定位第一行元素,如果使用 id 将会与第二行元素重名,如果使用 class 将会与第三行元素重名,如果同时使用 id 和 class 就会唯一地标识这个元素,这个时候就可以通过逻辑运算符 “and” 来连接两个条件。

  当然,我们也可以用“and”连接更多的属性来唯一地标识一个元素。

find_element_by_xpath('//input[@>')

2.1.8 CSS 定位

  一般情况下的等位优先级:

  • 优先级最高:ID
  • 优先级其次:name
  • 优先级再次:CSS selector
  • 优先级再次:Xpath

在项目中我们可能用的最多的是css或者xpath,那么针对这两种,我们优先选择css,原因在哪些?

  • 原因1:css是配合html来工作,它实现的原理是匹配对象的原理,而xpath是配合xml工作的,它实现的原理是遍历的原理,所以两者在设计上,css性能更优秀
  • 原因2:语言简洁,明了,相对xpath
  • 原因3:前端开发主要是使用css,不使用xpath,所以在技术上面,我们可以获得帮助的机会非常多

定位元素注意事项:

  • 找到待定位元素的唯一属性
  • 如果该元素没有唯一属性,则先找到能被唯一定位到的父元素/子元素/相邻元素,再使用 > , " " , + 等进行辅助定位
  • 不要使用随机唯一属性定位
  • 最重要的是多跟研发沟通,尽量把关键元素加上ID或者name,并减少不合理的页面元素,例如重复ID这样的事情最好不要发生

(1) 通过 class 属性定位

find_element_by_css_selector(".s_ipt")
find_element_by_css_selector(".bgs_btn")

  find_element_by_css_selector()方法用于CSS语言定位元素,点号(.)表示通过class属性来定位元素。

(2) 通过 id 属性定位

find_element_by_css_selector("#kw")
find_element_by_css_selector("#su")

(3) 井号(#)表示通过 id 属性来定位元素

  通过标签名定位:

find_element_by_css_selector("input")

  在 CSS 语言中,用标签名定位元素不需要任何符号标识,直接使用标签名即可。但我们前面已经了解到,标签名重复的概率非常大,所以通过这种方式很难找到想要的元素。

(3.1)通过父子关系定位

find_element_by_css_selector("span>input")

  上面的写法表示有父亲元素,它的标签名为apan,查找它的所有标签名叫input的子元素。

(3.2)通过属性定位

find_element_by_css_selector("[autocomplete=off]")
find_element_by_css_selector("[name='kw']")
find_element_by_css_selector("[type='submit']")

  在 CSS 当中也可以使用元素的任意属性,只要这些属性可以唯一标识这个元素,对于属性值来说,可加引号,也可以不加,但注意和整个字符串的引号进行区分。

(3.3)组合定位

  我们当然可以把上面的定位策略组合起来使用,这就大大加强了定位元素的唯一性。

find_element_by_css_selector("span.bgs_ipt_wr>input.s_inpt")
find_element_by_css_selector("span.bgs_btn_wr>input#su")

  有一个父元素,它的标签名叫 span;它有一个class属性值叫 bgs_ipt_wr;它有一个子元素,标签名叫 input,并且这个子元素的 class 属性值叫 s_ipt。

  浏览器开发者工具F12,复制,selector

2.1.9 用 By 定位元素

  针对前面介绍的 8 种定位方法,WebDriver 还提供了另外一套写法,即统一调用 find_element()方法,通过 By 来声明定位的方法,并且传入对应定位方法的定位参数,具体如下:

find_element(By.ID,"kw")
find_element(By.NAME,"wd")
find_element(By.CLASS_NAME,"s_ipt")
find_element(By.TAG_NAME,"input")
find_element(By.LINK_TEXT,"新闻")
find_element(By.PARTIAL_LINK_TEXT,"")
find_element(By.XPATH,"//*[@class='bgs_btn']")
find_element(By.CSS_SELECTOR,"span.bgs_btn_wr>input#su")

  find_element()方法只用于定位元素。它需要两个参数,第一个参数是定位的类型,由By提供;第二个参数是定位的具体方法,在使用By之前需要将By类导入。

from selenium.webdriver.common.by import By

  通过查看 WebDriver 的底层实现代码发现它们其实是一回事儿,例如,find_element_by_id()方法的实现。

    def find_elements_by_id(self, id_):
        """
        Finds multiple elements by id.

        :Args:
         - id\_ - The id of the elements to be found.

        :Returns:
         - list of WebElement - a list with elements if any was found.  An
           empty list if not

        :Usage:
            elements = driver.find_elements_by_id('foo')
        """
        return self.find_elements(by=By.ID, value=id_)

2.1.10 定位不到元素的问题

from selenium import webdriver

b = webdriver.Chrome()
b.get("https://mail.qq.com/")
b.find_element_by_xpath('//*[@]').clear()
b.find_element_by_xpath('//*[@)
b.find_element_by_xpath('//*[@)
b.find_element_by_xpath('//*[@]').click()

b.quit()

  会报错,表示找不到元素。

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//*[@}

元素找不到有好几种可能:

1、Frame/Iframe原因定位不到元素

  这个是最常见的原因,首先要理解下frame的实质,frame中实际上是嵌入了另一个页面,而webdriver每次只能在一个页面识别,因此需要先定位到相应的frame,对那个页面里的元素进行定位。

解决方案:
  如果iframe有name或id的话,直接使用switch_to.frame("name值")或switch_to.frame("id值")。如下:
from selenium import webdriver

b = webdriver.Chrome()
b.get("https://mail.qq.com/")
b.switch_to.frame('login_frame')  #需先跳转到iframe框架
b.find_element_by_xpath('//*[@]').clear()
b.find_element_by_xpath('//*[@)
b.find_element_by_xpath('//*[@)
b.find_element_by_xpath('//*[@]').click()

b.quit()
如果iframe没有name或id的话,则可以通过下面的方式定位:
#先定位到iframe
#再将定位对象传给switch_to.frame()方法
from selenium import webdriver

b = webdriver.Chrome()
b.get("https://mail.qq.com/")
elementi= b.find_element_by_xpath('//*[@]')
b.switch_to.frame(elementi)  #需先跳转到iframe框架
b.find_element_by_xpath('//*[@]').clear()
b.find_element_by_xpath('//*[@)
b.find_element_by_xpath('//*[@)
b.find_element_by_xpath('//*[@]').click()

b.quit()
  如果完成操作后,可以通过 switch_to.parent_content() 方法跳出当前一级iframe,或者还可以通过 switch_to.default_content() 方法跳回最外层的页面。

2、Xpath描述错误原因

由于Xpath层级太复杂,容易犯错。

可以使用Firefox的firePath,复制xpath路径。该方式容易因为层级改变而需要重新编写过xpath路径,不建议使用,初学者可以先复制路径,然后尝试去修改它。

3、页面还没有加载出来,就对页面上的元素进行的操作

     这种情况一般说来,可以设置等待,等待页面显示之后再操作,这与人手工操作的原理一样:
  1. 设置等待时间;缺点是需要设置较长的等待时间,案例多了测试就很慢;
  2. 设置等待页面的某个元素出现,比如一个文本、一个输入框都可以,一旦指定的元素出现,就可以做操作。
  3. 在调试的过程中可以把页面的html代码打印出来,以便分析。
解决方案:
导入时间模块。
import time
time.sleep(3)

4、动态id定位不到元素

解决方案:
如果发现是动态id,直接用xpath定位或其他方式定位。

5、二次定位,如弹出框登录

     如百度登录弹出框登录百度账号,需先定位到百度弹出框,然后再定位到用户名密码登录。

6、不可见元素定位

     如上百度登录代码,通过名称为tj_login查找的登录元素,有些是不可见的,所以加一个循环判断,找到可见元素(is_displayed())点击登录即可。

参考:https://blog.csdn.net/apollolkj/article/details/77096547

2.2 控制浏览器

控制浏览器有哪些操作:

  • 最大化、最小化浏览器
  • 控制、获取浏览器大小
  • 获取当前标签页title、url
  • 前进、后退、刷新
  • 执行js语句
  • 打开、关闭新标签页
  • 滚动页面

2.2.1 最大化最小化浏览器

from time import sleep
from selenium import webdriver

# 加载浏览器驱动
driver = webdriver.Chrome()

# 访问网址
driver.get("https://www.baidu.com")

# 最大化浏览器
driver.maximize_window()

sleep(2)

# 最小化浏览器
driver.minimize_window()

2.2.2 控制获取浏览器大小

# 加载浏览器驱动
driver = webdriver.Chrome("../resources/chromedriver.exe")

# 访问网址
driver.get("https://www.baidu.com")

# 获取浏览器大小
size = driver.get_window_size()
print(f"浏览器大小:{size}")

sleep(2)

# 设置浏览器大小
driver.set_window_size(1200, 1000)

get_window-size() 返回的是字典

浏览器大小:{'width': 1936, 'height': 1056}

2.2.3 获取当前标签页title、url

# 加载浏览器驱动
driver = webdriver.Chrome("../resources/chromedriver.exe")

# 访问网址
driver.get("https://www.baidu.com")

# 获取当前标签页的title
print(f"标签页title:{driver.title}")

# 获取当前标签页的url
print(f"标签页url:{driver.current_url}")

# 获取当前浏览器的名称(不常用)
print(f"标签页name:{driver.name}")

# 获取当前页面完整的HTML代码(不常用)
print(f"标签页page_source:{driver.page_source}")


>>>
标签页title:百度一下,你就知道
标签页url:https://www.baidu.com/
标签页name:chrome
标签页page_source:<html><head>...</head></html>

2.2.4 控制浏览器后退、前进

  在使用浏览器浏览网页时,浏览器提供了后退和前进按钮,可以方便地在浏览过的网页之间切换,WebDriver 也提供了对应的 back() forward() 方法来模拟后退和前进按钮,下面通过例子来演示这两个方法的使用。

from selenium import webdriver

driver = webdriver.Firefox()

#访问百度首页
first_url="http://www.baidu.com"
print("now access %s" %(first_url))
driver.get(first_url)

#访问新闻页面
second_url="http://news.baidu.com"
print("now access %s" %(second_url))
driver.get(second_url)

#返回(后退)到百度首页
print("back to %s" %(first_url))
driver.back()

#前进到新闻页
print("forward to %s" %(second_url))
driver.forward()
driver.quit()

  为了看清脚本的执行过程,下面每操作一步都通过print() 来打印当前的 URL 地址,执行结果如下:

now access http://www.baidu.com
now access http://news.baidu.com
back to http://www.baidu.com
forward to http://news.baidu.com

2.2.5 模拟浏览器刷新

driver.refresh()     #刷新当前页面

2.2.6 执行 JS 语句

# 同步执行js
js='console.log(2)'
driver.execute_script(js)

# 异步执行js
driver.execute_async_script("alert(2)")

2.2.7 打开关闭新标签页

# 打开新的标签页
js = 'window.open("https://www.baidu.com/")'
driver.execute_script(js)

# 获取当前标签页句柄
print(driver.current_window_handle)

# 获取浏览器所有标签页句柄
handles = driver.window_handles
print(handles)

# 切换标签页
driver.switch_to.window(handles[-1])

# 关闭当前标签页
driver.close()
  • 打开新的标签页只能通过 js 来操作
  • 可以根据标签页的句柄来切换标签页
  • 操作标签页的好处就是,可以只打开一个浏览器但可以访问多个不同的网页;这在后续集成单元测试框架(unittest、pytest)的时候会有很大的帮助

close只关闭一个tab,quit关闭所有

quit和close的区别:https://blog.csdn.net/yangfengjueqi/article/details/84338167

2.2.8 滚动页面

# 滚动随机高度
js = "var q=document.documentElement.scrollTop=" + random.randint(100, 999)
driver.execute_script(js)

这里暂时只介绍js的滚动方法,滚动其实有几种高级方法的,后续有空补上

2.3 简单元素操作

  • 点击
  • 输入内容、清除内容
  • 返回元素尺寸、坐标
  • 获取元素标签文本
  • 获取元素属性值
  • 检查元素:是否可见、是否可点击、是否已被选择
  • 表单提交

  clear() 方法用于清除文本输入框中的内容。例如,登录框内一般默认会有“账号”、“密码”等提示信息,用于引导用户输入正确的数据;但如果直接向输入框中输入数据,则可能会与输入框中的提示信息拼接,从而造成输入信息错误。这个时候可以先使用clear()方法来清除输入框中的默认提示信息。

  send_keys() 方法模拟键盘向输入框里输入内容。如上面的例子中,通过这个方法向用户名和密码框中输入登录信息。当然,它的作用不仅于此,我们还可以用它发送键盘按键,甚至用它来模拟文件上传。

  click() 方法可以用来单击一个元素,前提是它是可以被单击的对象,它与 send_keys() 方法是Web页面操作中最常用到的两个方法。其实 click() 方法不仅可用于单击一个按钮,它还能单击任何可以单击的文字/图片链接、复选框、单选框、下拉框等。

2.3.1 点击

from selenium import webdriver

b = webdriver.Firefox()
b.get("http://www.baidu.com")
b.find_element_by_id("kw").send_keys("火影")
b.find_element_by_id("su").click()
b.close()

2.3.2 输入内容、清除内容

# 找到id = username的元素
username = driver.find_element_by_id("username")

# 输入值 张三
username.send_keys("张三")

# 清空内容
username.clear()

2.3.3 返回元素尺寸、坐标

# 找到id = username的元素
login_btn = driver.find_element_by_class_name("class_name")

# 打印 元素宽高
print(f"元素宽高:{login_btn.size}")

# 打印 元素 x , y坐标值
print(f"元素坐标值:{login_btn.location}")

>>>
元素宽高:{'height': 23, 'width': 42}
元素坐标值:{'x': 457, 'y': 8}
  • size和location都是实例属性
  • 返回的都是字典
  • 元素坐标值是通过元素的最左上角和浏览器内容区域的左上角来定位的.

2.3.4 获取元素标签文本

# 获取第一个标签为a的文本
a_text = driver.find_element_by_tag_name("a")
print(a_text.text)

# 获取第一个标签为div的文本
div_text = driver.find_element_by_tag_name("div")
print(div_text.text)
  • .text 返回的是标签里面的文本,如 <html>内容....</html> ,返回的则是中间那些内容
  • 如果标签内还有子标签,那也只会获取子标签的文本内容,不会获取标签,像上面获取div的text一样

2.3.5 获取元素属性值

# 获取元素属性值
a_attr = driver.find_element_by_class_name("mnav")
print(a_attr.get_attribute("href"))

2.3.6 检查元素:是否可见、是否可点击、是否已被选择

# 找到 不可见元素
ant_btn3 = driver.find_element_by_class_name("ant-btn3")
# 找到 可见元素
ant_btn4 = driver.find_element_by_class_name("ant-btn4")

# 查看是否可见
print("不可见元素:", ant_btn3.is_displayed())
print("可见元素:", ant_btn4.is_displayed())

# 找到 不可点击元素
ant_btn3 = driver.find_element_by_class_name("ant-btn1")
# 找到 可点击元素
ant_btn4 = driver.find_element_by_class_name("ant-btn2")

# 查看是否可点击
print("不可点击元素:", ant_btn3.is_enabled())
print("可点击元素:", ant_btn4.is_enabled())

# 找到 未被选中的元素
option1 = driver.find_elements_by_tag_name("option")[0]
# 找到 已被选中的元素
option2 = driver.find_elements_by_tag_name("option")[-1]

# 查看是否被选择
print("未被选择元素:", option1.is_selected())
print("已被选择元素:", option2.is_selected())
  • 某个元素若有 display:none 的样式则是不可见,否则就是可见
  • 某个元素若有 disabled 属性则是不可点击,否则就是可点击
  • 某个元素若有 selected 属性则是已被选择

2.3.7 表单提交

driver.get("https://www.baidu.com")

# 找到搜索框
search_text = driver.find_element_by_id('kw')

# 输入搜索内容
search_text.send_keys('小菠萝测试笔记')

# 提交表单
search_text.submit()
  • submit() 方法用于提交表单。
  • 实际场景:在搜索框输入关键字之后的“回车” 操作, 就可以通过该方法模拟

 2.4 鼠标事件

  ActionChains 类提供了鼠标操作的常用方法:

  • perform():                              执行所有ActionChains中存储的行为
  • context_click():                     右击
  • double_click():                      双击
  • drag_and_drop():                 拖动
  • move_to_element():            鼠标悬停

2.4.1 执行操作

  perform() 方法

  主要是调用其他操作方法后,都要再次调用这个方法,表示执行某个鼠标操作,后面会有例子

2.4.2 鼠标左击、右击、双击操作

from selenium import webdriver
# 引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 定位到要右击的元素
right_click = driver.find_element_by_id("kw")
# 对定位到的元素执行鼠标右键操作
ActionChains(driver).context_click(right_click).perform()

或者

from selenium.webdriver import ActionChains
from selenium import webdriver

driver = webdriver.Chrome()

# 创建实例
chains = ActionChains(driver)

# 访问网址
driver.get("https://www.baidu.com")

# 登录按钮
username = driver.find_element_by_id("username")
login_btn = driver.find_element_by_class_name("login")
password = driver.find_element_by_id("password")

# 左键点击
chains.click(username).perform()

# 右键点击
chains.context_click(username).perform()

# 双击
chains.double_click(password).perform()

 

  • from selenium.webdriver.common.action_chains import ActionChains 导入提供鼠标操作的 ActionChains 类
  • ActionChains(driver) 调用 ActionChains() 类,将浏览器驱动 driver 作为参数传入
  • context_click(right_click)    context_click() 方法用于模拟鼠标右键操作,在调用时需要指定元素定位。
  • perform()    执行所有 ActionChains 中储存的行为,可以理解成是对整个操作的提交动作。 

2.4.3 鼠标悬停

from selenium import webdriver
# 引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 定位到要悬停的元素
above = driver.find_element_by_id("su")
# 对定位到的元素执行悬停操作
ActionChains(driver).move_to_element(above).perform()

偏移

# 创建实例
chains = ActionChains(driver)
# 悬停到指定偏移量
chains.move_to_element_with_offset(login_btn, 2, 2).perform()

  move_to_element_with_offset() 是先找到元素,再根据元素位置偏移指定偏移量

2.4.4 长按

# 长按
chains.click_and_hold(login_btn).perform()

2.4.5 鼠标拖动操作

  drag_and_drop(source,target)在源元素上按住鼠标左键,然后移动到目标元素上释放。

  • source:鼠标拖动的源元素
  • target:鼠标释放的目标元素
from selenium import webdriver
# 引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 定位元素的原位置
element = driver.find_element_by_id("su")
target = driver.find_element_by_id("su")

# 执行元素的拖动操作
ActionChains(driver).drag_and_drop(element,target).perform()
ActionChains(driver).drag_and_drop_by_offset(source=username, xoffset=20, yoffset=20)

drag_and_drop 将源元素拖动到目标元素处
drag_and_drop_by_offset 将源元素拖动指定偏移量

 

click(on_element=None) ——单击鼠标左键

click_and_hold(on_element=None) ——点击鼠标左键,不松开

context_click(on_element=None) ——点击鼠标右键

double_click(on_element=None) ——双击鼠标左键

drag_and_drop(source, target) ——拖拽到某个元素然后松开

drag_and_drop_by_offset(source, xoffset, yoffset) ——拖拽到某个坐标然后松开

key_down(value, element=None) ——按下某个键盘上的键

key_up(value, element=None) ——松开某个键

move_by_offset(xoffset, yoffset) ——鼠标从当前位置移动到某个坐标

move_to_element(to_element) ——鼠标移动到某个元素

move_to_element_with_offset(to_element, xoffset, yoffset) ——移动到距某个元素(左上角坐标)多少距离的位置

perform() ——执行链中的所有动作

release(on_element=None) ——在某个元素位置松开鼠标左键

send_keys(*keys_to_send) ——发送某个键到当前焦点的元素

send_keys_to_element(element, *keys_to_send) ——发送某个键到指定元素

2.5 键盘事件

  keys() 类提供了键盘上几乎所有按键的方法。前面了解到,send_keys()方法可以用来模拟键盘输入,除此之外,我们还可以用它来输入键盘上的按键,甚至是组合键,如 Ctrl + A ,Ctrl + C等。

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "Q1mi"
# Date: 2018/7/2

from selenium import  webdriver
# 引入Keys模块
from selenium.webdriver.common.keys import Keys

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 输入框输入内容
driver.find_element_by_id("kw").send_keys("seleniumm")

# 删除多输入的一个m
driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)

# 输入空格键 + “教程”
driver.find_element_by_id("kw").send_keys(Keys.SPACE)
driver.find_element_by_id("kw").send_keys("教程")

# ctrl + a 全选输入框内容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,"a")

# ctrl + x 剪切输入框内容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,"x")

# ctrl + v 粘贴内容到输入框
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,"v")

# 通过回车键来代替单击操作
driver.find_element_by_id("su").send_keys(Keys.ENTER)

  以下为常用的键盘操作:

send_keys(Keys.BACK_SPACE)       删除键(BackSpace)
send_keys(Keys.SPACE)            空格键(Space)
send_keys(Keys.TAB)              制表键(Tab)
send_keys(Keys.ESCAPE)           回退键(Esc)
send_keys(Keys.ENTER)            回车键(Enter)
send_keys(Keys.CONTROL,"a")      全选(Ctrl+A)
send_keys(Keys.CONTROL,"c")      复制(Ctrl+C)
send_keys(Keys.CONTROL,"x")      剪切(Ctrl+X)
send_keys(Keys.CONTROL,"v")      粘贴(Ctrl+V)
send_keys(Keys.F1)               键盘F1
......                           .
send_keys(Keys.F12)              键盘F12

2.6 获取验证信息

  在编写功能测试用例时,会假定一个预期结果,在执行用例的过程中把得到的实际结果与预期结果进行比较,从而判断用户的通过或失败。自动化测试用例是由机器去执行的,通常机器并不像人一样有思维和判断能力,那么是不是模拟各种操作页面的动作没有报错就说明用例执行成功呢?并非如此,假如我们模拟百度搜索的用例,当新的迭代版本上线后,每一页的搜索结果少一条,但用例的执行不会报错,因此这个 bug 永远不会被自动化测试发现。

  那么是不是在运行自动化测试用例时需要由测试人员盯着用例的执行来辨别执行结果呢?如果是这样的话,自动化测试就失去了“自动化”的意义。在自动化用例执行完成之后,我们可以从页面上获取一些信息来“证明“用例执行是成功还是失败。

  通常用得最多的几种验证信息分别是title、URL和text。text方法在前面已经讲过,它用于获取标签对之间的文本信息。

from selenium import webdriver
import time

driver = webdriver.Chrome()
driver.get("https://mail.qq.com/")
time.sleep(2)
frame_add = driver.find_element_by_xpath('//*[@>)
driver.switch_to_frame(frame_add)  #需先跳转到iframe框架
print("Before login==========")

# 打印当前页面title
now_title = driver.title
print(now_title)


# 打印当前页面URL
now_url = driver.current_url
print(now_url)

# 执行邮箱登录
driver.find_element_by_id("u").clear()
driver.find_element_by_id("u").send_keys("578389018@qq.com")
driver.find_element_by_id("p").clear()
driver.find_element_by_id("p").send_keys("*******")
driver.find_element_by_id("login_button").click()
time.sleep(5)

print("After login==========")
# 再次打印当前页面title
now_title = driver.title
print(now_title)


# 再次打印当前页面URL
now_url = driver.current_url
print(now_url)

# 获取登录的用户名
user = driver.find_element_by_id("useraddr").text
print(user)

driver.quit()

>>>
C:\Users\Administrator\AppData\Local\Programs\Python\Python37\python.exe C:/Users/Administrator/Desktop/py/test.py
Before login==========
登录QQ邮箱
https://mail.qq.com/
After login==========
QQ邮箱
https://mail.qq.com/cgi-bin/frame_html?sid=IDp2I6d3Vi8WORtA&r=b6242c8e186a9f7c4b80aab437e760f8
dongye95@foxmail.com

Process finished with exit code 0
  • title:用于获得当前页面的标题。
  • current_url:用户获得当前页面的URL

  通过打印结果,我们发现登录前后的title和URL明显不同。我们可以把登录之后的这些信息存放起来,作为登录是否成功的验证信息。当然,这里URL每次登录都会有所变化,是不能拿来做验证信息的。title可以拿来做验证信息,但它并不能明确地标识是哪个用户登录成功了,因此通过text获取用户文本(dongye95@foxmail.com)是很好的验证信息。

2.7 设置元素等待

2.7.1 为什么需要设置元素等待

  • 因为,目前大多数Web应用程序都是使用Ajax和Javascript开发的;每次加载一个网页,就会加载各种HTML标签、JS文件 
  • 但是,加载肯定有加载顺序,大型网站很难说一秒内就把所有东西加载出来,不仅如此,加载速度也受网络波动影响
  • 因此,当我们要在网页中做元素定位的时候,有可能我们打开了网页但元素未加载出来,这个时候就定位不到元素,就会报错 
  • 所以,我们需要设置元素等待,意思就是:等待指定元素已被加载出来之后,我们才去定位该元素,就不会出现定位失败的现象了

2.7.2 强制等待sleep 休眠方法

  有时候我们希望脚本在执行到某一位置时做固定时间的休眠,尤其是在脚本调试过程中。这时可以使用sleep()方法,需要说明的是,sleep()方法由Python的time模块提供。

from selenium import webdriver
from time import sleep

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

sleep(2)
driver.find_element_by_id("kw").send_keys("webdriver")
driver.find_element_by_id("su").click()
sleep(3)

driver.quit()

  当执行到sleep()方法时会固定休眠一定的时长,然后再继续执行。sleep()方法默认参数以秒为单位,如果设置时长小于1秒,则可以用小数表示,如sleep(0.5)表示休眠0.5秒。

2.7.3 隐式等待

什么是隐式等待:如果某些元素不是立即可用的,隐式等待是告诉WebDriver去等待一定的时间后去查找元素默认等待时间是0秒,隐式等待对整个WebDriver的周期都起作用,所以只要设置一次即可。

如何体现隐式等待:如果在规定时间内,整个网页都加载完成,则执行下一步。否则会抛出异常。

隐式等待弊端:需要页面加载完成,才能执行下一步。【增加了不必要的加载时间】

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from time import ctime

driver  = webdriver.Firefox()

# 设置隐式等待10秒
driver.implicitly_wait(10)
driver.get("http://www.baidu.com")

try:
    print(ctime())
    driver.find_element_by_id("kw22").send_keys('selenium')
except NoSuchElementException as e:
    print("查找元素异常 %s" %(e))
else:
    pass
finally:
    print(ctime())
    driver.quit()

>>>
Wed Jul  4 12:51:30 2018
查找元素异常 Message: Unable to locate element: []

Wed Jul  4 12:51:40 2018

Process finished with exit code 0

  implicitly_wait() 默认参数的单位为秒,本例中设置等待时长为10秒。首先这10秒并非一个固定的等待时间,它并不影响脚本的执行速度。其次,它并不针对页面上的某一元素进行等待。当脚本执行到某个元素定位时,如果元素可以定位,则继续执行;如果元素定位不到,则它将以轮询的方式不断地判断元素是否被定位到。假设在第6秒定位到了元素则继续执行,若直到超出设置时间(10秒)还没有定位到元素,则抛出异常。

  在上面的例子中,显然百度输入框的定位 id=kw22 是有误的,通过打印的两个时间可以看出,当执行度一百度输入框的操作时,超过了10秒的等待。

2.7.4 显式等待

什么是显式等待:针对元素可不可见,作用域指定元素

显式等待优势:仅对元素生效,针对元素设置,无需等待页面加载完成,节省加载时间

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 as EC

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

element = WebDriverWait(driver, 5, 0.5).until(
    EC.presence_of_element_located((By.ID,"kw"))
    )

element.send_keys('selenium')
driver.quit()

2.7.4.1 WebDriverWait 源码解读

 WebDriverWait 类是由 WebDriver 提供的的等待方法。在设置时间内,默认每隔一段时间检测一次当前页面元素是否存在,如果超过设置时间检测不到则抛出异常。具体格式如下

class WebDriverWait(object):
    def __init__(self, driver, timeout, poll_frequency=POLL_FREQUENCY, ignored_exceptions=None):
        """Constructor, takes a WebDriver instance and timeout in seconds.

           :Args:
            - driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote)
            - timeout - Number of seconds before timing out
            - poll_frequency - sleep interval between calls
              By default, it is 0.5 second.
            - ignored_exceptions - iterable structure of exception classes ignored during calls.
              By default, it contains NoSuchElementException only.
  • driver                                浏览器驱动
  • timeout                             最长超时时间,默认以秒为单位
  • poll_frequency                 检测的间隔(步长)时间,默认为0.5s    
  • ignored_exceptions         需要忽略的异常。
    •   如果在调用 until() 或 until_not() 的过程中抛出这个元组中的异常,则不中断代码,继续等待;
    •   如果抛出的是这个元组外的异常,则终端代码
    •        或略的异常默认只有 NoSuchElementException

WebDriverWait(driver实例, 超时时长, 调用频率, 忽略的异常).until(要调用的 方法, 超时时返回的信息) 

2.7.4.2 WebDriverWait 实例的两个方法

until(self, method, message='') 

作用:每隔一段时间(上面的poll_frequency)调用method,直到返回值不为False或不为

method:需要执行的method

message:抛出异常时的文案,会返回 TimeoutException ,表示超时

注意这个才是常用的,如:定位元素直到不返回空

 

 

until_not(self, method, message='') 

作用:调用method,直到返回值False或

method:需要执行的method

message:抛出异常时的文案,会返回  TimeoutException ,表示超时

 

两个方法的 method参数注意点

如果直接传入WebElement(页面元素)

WebDriverWait(driver, 10).until(driver.find_element_by_id('kw'))

则会抛出异常

TypeError: 'xxx' object is not callable

method 参数需要传入的对象必须包含   __call()__  方法 ,什么意思?让对象可以直接被调用 

 

官方提供的两个小例子

element = WebDriverWait(driver, 10).until(lambda x: x.find_element_by_id("someId")) 
is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).until_not(lambda x: x.find_element_by_id("someId").is_displayed())

可以看到,通过匿名函数也是可以的,可以说比后面介绍的  expected_conditions   模块要方便多了

 

那么有哪些是包含  __call()__  的对象呢?

  •  expected_conditions 模块(接下来重点讲的)
  • WebElement的 is_displayed() 、 is_enabled() 、 is_selected() 

2.7.5 expeted_conditions 源码解读

 

  在本例中,通过 as 关键字将 expected_conditions 重命名为 EC,并调用 presence_of_element_located() 方法判断元素是否存在。

  expected_conditions 类所提供的预期条件判断的方法如下表所示。

selenium2自动化测试实战--基于Python语言
    





            
WebDriver API

这里介绍两个在设置元素等待里面最常用的判断条件类

其一:presence_of_element_located

作用:检查当前DOM树种是否存在该元素(和是否可见没有关系),只要有一个元素加载出来则通过

class presence_of_element_located(object):
    """ An expectation for checking that an element is present on the DOM
    of a page. This does not necessarily mean that the element is visible.
    locator - used to find the element
    returns the WebElement once it is located
    """
    def __init__(self, locator):
        self.locator = locator

    def __call__(self, driver):
        return _find_element(driver, self.locator)

传入一个元组,格式如下 (By.ID, "元素ID" 

  • 第一个参数:定位元素的方式,和那八种元素定位方式一样,只是这里需要引入 By 模块,然后再调用类属性 
  • 第二个参数:和之前调用元素定位方法一样传参即可 
  • 所以正确写法是: presence_of_element_located((By.ID, "kw")) 

一起看看 By 模块的源码:

class By(object):
    """
    Set of supported locator strategies.
    """

    ID = "id"
    XPATH = "xpath"
    LINK_TEXT = "link text"
    PARTIAL_LINK_TEXT = "partial link text"
    NAME = "name"
    TAG_NAME = "tag name"
    CLASS_NAME = "class name"
    CSS_SELECTOR = "css selector"

其二:presence_of_all_elements_located

class presence_of_all_elements_located(object):

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

    def __call__(self, driver):
        return _find_elements(driver, self.locator)
  • 因为调用的是 _find_elements ,会返回多个元素
  • 如果用这个条件类,必须等所有匹配到的元素都加载出来才通过

2.8 针对 Select 下拉框的操作和源码解读

 

<html lang="en">

相关文章: