自动化测试(10) | Selenium Java 封装

WebDriver 封装

欢迎阅读WebDriver封装讲义。本篇讲义将会重点介绍Selenium WebDriver API的封装的概念和方法,以及使用封装进行自动化测试的设计。

WebDriver API 封装

封装的概念

从之前的讲义和学习中,我们知道,WebDriver API的调用以及自动化测试,我们也初步接触了线性测试、以及模块化自动化测试和数据驱动测试,那么回顾之前的内容,我们不只是可以利用WebDriver提供的一系列的定位符以便使用元素定位方法,我们这里开始尝试封装后调用。首先,我们从封装的概念开始。

封装是一个面向对象编程的概念,是面向对象编程的核心属性,通过将代码内部实现进行密封和包装,从而简化编程。

所谓“对象”,形象地说,我们可以把它理解为一块积木。设计积木的人需要设计积木的外观与形状,还有内部的材质。堆积木的人对于内部的材质并不关心,他们只需要根据不同的外观与形状来决定堆放的位置。因此,对于开发者而言,要设计面向对象的程序,同时会是两个迥然不同的身份:设计者与使用者。

先谈谈使用者。使用者的身份,就是利用已经提供给你的所有对象,根据需求,设计出自己需要实现的程序。就如堆积木的过程。这恰恰是面向对象编程的优势所在,那就是“对象的重用”。已经设计好的对象,可以被不同的使用者调用,这些功能既然已经实现,对于使用者而言,当然就免去了自己去设计的过程。正如堆积木那样,既然有了现成设计好的积木,使用者所要做的工作就是把这些积木最后组合起来,堆成不同的形状。WebDriver所提供的类库,就是这样的积木。那么我们以下的操作将会基于上述的定位符进行定位操作。

前面说到对象好比是一个积木,设计者需要定义好这个积木的外观和形状,也要考虑积木内部的制作,例如选用的材质,以及是空心还是实心。如果将这个积木剖开来看,实际上该对象应分为内、外两层。由于使用者只关心外部的实现,因此设计者就需要考虑,哪些实现应暴露在外,哪些实现应隐藏于内。这就体现了对象的封装的思想。

简而言之,封装就是把原始和原生的方法进行再包装。将原始的代码用心的代码包装起来,通过对新代码的调用,来使用原始的代码的过程。

封装的好处

对Selenium进行封装的好处主要有如下三个方面:

  • 使用成本低
    1. 不需要要求所有的测试工程师会熟练使用Selenium,而只需要会使用封装以后的代码
    2. 不需要对所有的测试工程师进行完整培训。也避免工作交接的成本。
    3. 测试人员使用统一的代码库
  • 维护成本低
    1. 通过封装,在代码发生大范围变化和迁移的时候,不需要维护所有代码,只需要变更封装的部分即可
    2. 维护代码不需要有大量的工程师,只需要有核心的工程师进行封装的维护即可
  • 代码安全性
    1. 对作为第三方的Selenium进行封装,是代码安全的基础。
    2. 对于任何的代码的安全隐患,必须由封装来解决,使得风险可控。
    3. 使用者并不知道封装内部的代码结构。

封装的目的

封装,最终为了实现自动化测试框架。在自动化测试领域,有一个经典的问题:“既然可以编写或者通过record & playback可以做一个脚本,那么为什么还需要自动化测试框架呢?”简而言之,就是为什么需要如此这般麻烦的设计和编写自动化测试框架,不是原本已经可以做自动化测试了么?

这个回答可以很“官方”,从维护性、重用性、安全性和成本等角度可以回答。

在这点,就好比是建造房子。在没有设计框架的基础上,我们或许可以建造一个楼房,最多也就三两层吧;但是对于一份经过完整设计的图纸,人们可以建造出高楼大厦。

封装,就是把基础的石头等建材,通过我们的个性化的方法,将地基可用而安全的搭建好。是自动化测试框架的基石。

自动化测试模型

自动化测试模型.png
  1. 封装 Selenium 为 BoxDriver
  2. 在 测试用例中,实例化 BoxDriver,产生 bd 对象
  3. 使用 bd 对象,构造 业务模块的实例化对象,产生 common
  4. 使用 common 在测试用例中,构建测试步骤
  5. 使用数据驱动的外部数据,通过读取,进行测试
  6. 执行整个用例

使用封装进行自动化测试

封装技术

  1. 面向对象的编程
  • 类:模板,设计
  • 构造方法
  • 成员变量
  • 成员方法
  • 类的实例化产生对象
  • 实例化过程: new 关键字
  • 实例化的方法: 类名(参数)--> 构造方法
  • 实例化过程相当于 类执行了构造方法,产生了 this
  • 实例化出来的对象,可以访问成员方法
  1. 封装:做一个模板,这个模板有webdriver所有的功能
  • 封装 webdriver,自动化 WebUI 测试
  • 封装 Appium,自动化 APP UI 测试
  • 封装 自定义的 Web接口 Driver
  1. 其他公司的开源框架

封装示例

测试脚本:以下的脚本

  • 找到一个指定输入框(selector),并且输入指定的字符(text)

    type(selector, text)

    不用在业务逻辑中,使用多次的 findElement(By.id(...))

    public void type(String selector, String text) {
      WebElement we = this.locateElement(selector);
      we.clear();
      we.sendKeys(text);
    }
    

  • 找到一个可以点击的元素(selector),并且点击(click)

    click(selector)

    public void click(String selector) {
      this.locateElement(selector).click();
    }
    

  • 找到一个指定的frame,并且切换进去

    switchToFrame(selector)

    public void switchToFrame(String selector) {
      WebElement we = this.locateElement(selector);
      this.baseDriver.switchTo().frame(we);
    }
    
  • 找到一个指定的select,并且通过index进行选择

    selectByIndex(selector, index)

    public void selectByIndex(String selector, int index) {
      WebElement we = this.locateElement(selector);
      Select s = new Select(we);
      s.selectByIndex(index);
    }
    

以上的代码是封装了locateElement()的几种方法,在具体使用封装过的代码的时候,只需要简单的调用即可。接下来的重点,是介绍 locateElement(selector)的封装方式。

  • 查找元素:findElement(By...)
  • 支持各种的查找:8种方式都需要支持,必须通过 selector 显示出分类
    • selector中需要包含一个特殊符号
    • 实例化 封装好的类的时候,需要约定好是什么特殊符号
      1. 强制性用硬编码 hard code来实例化,例如 , 或者 ? 或者 其他非常用字符 =>
      2. 或者,构造方法中,传递 this.byChar
  • 要把查找到元素的返回给调用的地方:必须要有返回值,类型是 WebElement
private WebElement locateElement(String selector) {
  WebElement we;
  // 如果定位符中 有 分隔符,那么就从分隔符处分成两段
  // 第一段是By
  // 第二段是真正的定位符
  // 如果没有分隔符,就默认用 id 定位
  if (!selector.contains(this.byChar)) {
    // 用 id 定位
    we = this.baseDriver.findElement(By.id(selector));
  } else {
    // 用 分隔符 分成两个部分
    String by = selector.split(this.byChar)[0];
    String value = selector.split(this.byChar)[1];
    we = findElementByChar(by, value);
  }
  return we;
}
  • 接下来的重点,是实现 findElementByChar(by, value)
private WebElement findElementByChar(String by, String value) {
  WebElement we = null;
  switch (by.toLowerCase()) {
    case "id":
    case "i":
      we = this.baseDriver.findElement(By.id(value));
      break;
    case "css_selector":
    case "css":
    case "cssselector":
    case "s":
      we = this.baseDriver.findElement(By.cssSelector(value));
      break;
    case "xpath":
    case "x":
      we = this.baseDriver.findElement(By.xpath(value));
      break;
    case "link_text":
    case "link":
    case "text":
    case "linktext":
    case "l":
      we = this.baseDriver.findElement(By.linkText(value));
      break;
      //TODO: other by type
  }
  return we;
}

使用上面的封装类,就需要指定特定的 selector

类型 示例(分隔符以逗号,为例) 描述
id "account" 或者 "i,account" 或者 "id,account" 分隔符左右两侧不可以空格
xpath "x,//*[@id="s-menu-dashboard"]/button/i"
css selector "s,#s-menu-dashboard > button > i"
link text "l,退出"
partial link text "p,退"
name "n,name1"
tag name "t,input"
class name "c,dock-bottom

调用示例

void logIn(String account, String password) throws InterruptedException {
  BoxDriver driver = this.baseDriver;
  driver.type("account", account);
  driver.type("password", password);
  driver.click("submit");
  // 点击登录按钮后,需要等待浏览器刷新
  Thread.sleep(2000);
}

自动化的测试代码示例

@Test
public void test01Login() {
  common.openPage();
  common.logIn(member.getAccount(), member.getPassword());
  expectedUrl = this.baseUrl + "sys/index.html";
  Assert.assertEquals(driver.getCurrentUrl(), expectedUrl, "新用户登录失败");
  common.logOut(lang);
}

封装需要注意的部分

  • 方法有无返回值、和参数

    • 如果有返回值:确保代码所有的路径都会有返回值

      public WebElement getElement(){
        if (xxx) {
          return yyy;
        }
      }
      // 上述代码会报错,如果xxx条件不符合,就没有返回值的路径了。
      
    • 如果有参数,确保非值类型的参数是非空的 不是 null

      public void type(Driver driver){
        driver.xxx()
      }
      // 如果driver是null,那么xxx会直接报错。 NullPointerException错误异常
      
    • 如果有较多的分支语句,需要注意 break的使用

      public WebElement getElement(String selector){
        switch(selector.toLowerCase()){
          case "id":
          case "i":
            driver.findElement(By.id(xxx));
            break;
          case "x":
            xxx
        }
      }
      // 注意需要填写break
      

      在python中,不支持switch语句,python使用的是 if ... elif

      def get_element(selector):
          if selector == "id" or selecor == "i":
              driver.find_element_by_id(xxx)
          elif selector == "x":
              xxx
      

  • 封装中,WebDriver对象的传递

    • WebDriver应该是在封装的类中,唯一存在。

      class AutomateDriver(){
        public click(String selector){
          WebDriver driver = new FirefoxDriver();
          driver....
        }
        
        public type(String selector, String text){
          WebDriver driver = new FirefoxDriver();
          driver....
        }
      }
      // 上述代码中,会打开多个火狐浏览器
      // 正确的做法:声明一个全局的类的成员变量,进行赋值使用。
      

关于Firefox Profile的使用

在做自动化测试的时候,经常会发现Firefox 被初始化,提示收藏夹等,遮住元素窗口。因为Firefox每次都以默认的Profile 加载全新的Firefox,例如如下图片:

Snap1.jpg

在这样的情况下,我们需要专门准备一份独立的Firefox Profile进行使用。步骤如下

  1. 在命令行中进入Firefox的安装目录,然后输入 firefox -ProfileManager -no-remote

    Snap2.jpg
  2. 然后在弹出的窗口中,新建一个Profile,记录地址。

    e5792a15-e7ed-39fa-bf46-5505c54923a5.png
  3. 根据刚刚的地址,打开Profile所在的文件夹:C:\Users\Linty\AppData\Roaming\Mozilla\Firefox\Profiles

    Snap3.jpg
  4. 复制 einy9uds.selenium文件夹到 base目录中。

    Snap4.jpg
  5. 修改代码 automate_driver.py

    def __init__(self, by_char=",", firefox_profile=None):
        """
        构造方法:实例化 BoxDriver 时候使用
        :param by_char: 分隔符
        :param firefox_profile:
        可选择的参数,如果不传递,就是None
        如果传递一个 profile,就会按照预先的设定启动火狐
        去掉遮挡元素的提示框等
        """
        if firefox_profile is not None:
            firefox_profile = FirefoxProfile(firefox_profile)
            driver = webdriver.Firefox(firefox_profile=firefox_profile)
            try:
                self.base_driver = driver
                self.by_char = by_char
                except Exception:
                    raise NameError("Firefox Not Found!")
    
  6. 修改代码 xxx_test_cases.py

    profile = "base\\einy9uds.selenium"
    self.base_driver = AutomateDriver(firefox_profile=profile)
    
  7. 完毕。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容