Puppeteer使用Page Object设计模式和自动等待

此篇博客主要介绍如何利用Puppeteer编写一个UI层测试案例以及如何利用现有的第三方包完成自动等待,为了完成此次课程目标,拆分了3个task。

  • 利用Puppeteer编写一个简单的UI层用例
  • 使用Puppeteer框架时如何实现Page Object
  • 实现自动等待,减少脚本编写成本

接下来我们就开始第一个task的学习

利用Puppeteer编写一个简单的UI层用例

以下代码是使用puppeteer完成页面打开和点击“sign in”操作。编写case时除需要引入puppeteer外,还套用了jest框架,jest是一个javascript的测试框架,引入该测试框架,即可添加每个案例的描述信息等。大家现在无需单独学习jest,我们使用到jest的某些特性时会进行讲解。clone代码后执行 "npm run basic" 即可运行下面的案例,代码实现细节如下所示。

const puppeteer = require('puppeteer');
// 引入pupeeteer对象

describe("use puppeteer to get  browser and page object demo ", ()=> {   
//引入了jest的框架,describe和it是jest框架中的关键字,可在此添加案例描述信息,一个js文件中可包含一个describe,一个describe下可包含多个it,所以describe更像一个测试集

    it("should open a page with puppeteer", async() => {   
        //it是jest中关键字,it里面包含真正的UI层测试执行步骤

        const browser = await puppeteer.launch();
        //要实现对页面元素的操作,首选需要获取browser对象,browser对象上对外提供了很多API,后面课程中会进行讲解

        const page = await browser.newPage();
        //通过browser对象获取page对象

        await page.goto("https://angular.realworld.io/");
       //所有的页面元素操作,都是基于page对象上进行的

        await page.waitForSelector('app-layout-header li a[href="/login"]');
       //waiForSelector是等待语句,等待页面元素出现后再进行下一步操作

        await page.click('app-layout-header li a[href="/login"]');
       //点击页面的“sign in“元素 

        await page.close();
        await browser.close();
      //完成测试后释放page和browser对象  
    });
});

上面是一个最简单的测试场景,实际场景中,当我们开始一个UI层测试时,可能需要指定执行的浏览器,指定页面大小等。在puppeteer中浏览器版本、页面大小、执行速度、是否用无头模式等都是通过在puppeteer.launch()中添加option参数实现。当我们执行npm install puppeteer安装pueeteer时,同时还会安装Chronium,如果不指定puppeteer.launch()中的option参数,默认所有案例都运行在Chronium上,且是无头模式运行。

接下来通过下面的案例看看常用的option参数指定方式。同样,执行命令 "npm run launch-option" 即可运行下面的案例。需要特别注意一点,案例中指定的chrome安装目录是 “/Applications/Google Chrome.app/Contents/MacOS/Google Chrome”,这里请修改成你自己电脑的浏览器安装目录,否则运行时可能无法启动浏览器导致案例运行失败,代码实现细节如下所示。

const puppeteer = require('puppeteer');
describe("use puppeteer to get  browser and page object demo ", ()=> {
    it("use more launch properties demo", async () => {
        const browser = await puppeteer.launch({slowMo: '500', headless: false,
            executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
          //puppeteer.launch({key:value,key:value})指定option参数,可以指定多个;

         // slowMo指定脚本运行速度,单位毫秒;降低执行速度,有助于调试时查看具体哪个步骤失败了

        // headless设置为false,运行的时候可在浏览器中看到具体的页面元素操作过程

       // executablePath启动特定安装目录下浏览器,这里地址是作者自己Mac电脑上Chrome的安装目录,这里的值需要替换成你自己的Chrome安装目录

       //puppeteer目前已支持firefox,这里还可指定firefox安装目录启动firefox运行案例

    });
});

上面两个案例都是在测试案例脚本里面直接获取browser和page对象,然后在page对象上对页面元素进行操作。实际项目中,UI层的自动化案例一定会用Page Object设计模式,Page Object指以页为单位,把这个页相关的所有操作都放到某个脚本文件中,这样在测试场景层,只需调用页对象包好的方法即可,这种按页划分的思路可以很好的对脚本进行分类管理。需要注意一点,这里的页不仅仅指一个web应用的页面,大部分情况下页对象是一个web应用的某个页面,但也可以把一些通用操作放到一个页对象中,例如应用中所有的确定框的操作,属于很多脚本都会共用的部分,那么可以定义一个dialog页对象。同理,如果某个web页面包含太多内容,例如登陆淘宝后的首页面,为了防止js文件中内容过多,那么会把该web页上的元素定位和操作按功能聚合度拆分到多个页对象中。

按照Page Object的思想,每个js文件里面包含各种对该页的元素操作、元素值获取、校验等方法。Puppeteer里面所有的元素操作都是基于page对象,为了使用Page Object设计模式,不能在每个js文件中通过“ const page = await browser.newPage() ”创建一个新page对象,必须所有js文件都共用一个page对象;因为一个完整的测试案例中所有操作都是有关联的,只有前面的步骤正确执行了,后续的步骤才能顺利完成,所以,同一个测试案例必须共用一个page对象。那么如何做到所有的js文件共享同一个page对象呢?这里最简单的方式是直接引用已经封装好的第三方包“jest-puppeteer”,该包提供了共用的全局browser和page对象,接下来就一起学习如何利用该包实现Page Object。

使用Puppeteer框架时使用Page Object设计模式

执行“npm install jest-puppteer --save”安装第三方包,接着增加三个配置文件,jest.config.js、jest.setup.js、jest-puppeteer.config.js,各个配置文件主要配置信息如下所示。

jest.config.js配置文件

module.exports= {
    preset: 'jest-puppeteer',  
    // 如果要使用jest-puppeteer,需要在jest.config.js中增加该配置

    setupFilesAfterEnv: ['expect-puppeteer','./jest.setup.js']   
    //这里第一个配置‘expect-puppeteer’是下面我们将要讲解的自动等待需要引入的包
};

jest.setup.js文件中是关于jest框架的一些参数设置,目前使用到的是设置案例运行超时时间。Jest中默认的超时时间比较小,需要重新设置,否则用例未执行完成就报超时错误了。

    {
    jest.setTimeout(120000)  
    //设置案例的运行超时时间为2分钟,如果你的案例运行时间很长,请调大此值
    }

jest-puppeteer.config.js配置文件

module.exports ={
    launch : {
        headless: false,
        slowMo: 150,
        args:['--window-size=1920,1080'],
        executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
    },
    //这里把获取browser对象时指定的option值放到配置文件中指定,每个option的含义前面已讲解过

    browserContext : "default",
    exitOnPageError: false
};

完成上面配置后,即可编写Page Object模式下的UI层测试案例了。同样,执行“npm run page-object-demo”即可运行下面的案例。以下是封装好的测试案例层脚本。

page-object-demo.test.js文件内容

const menuPage = require('./page/menuPage');  
//引入menuPage文件,这样即可调用该文件中封装的方法

const loginPage = require('./page/loginPage');
//引入loginPage文件,这样即可调用该文件中封装的方法

describe("page object demo with jest-puppeteer",  () => {
    it("should login in successfully", async () => {
        await menuPage.openApp();
        await menuPage.gotoSign();
        await loginPage.login('e2etest@163.com','12345678');  
        // 上面三行代码实现调用两个页对象中封装好的方法,这样测试案例层的代码更简洁,更易阅读
    });
});

menuPage.js文件内容,即其中一个文件对象

async function openApp() {
    await page.goto("https://angular.realworld.io/");
}

async function gotoSign() {
    await page.waitForSelector('app-layout-header li a[href="/login"]');
    await page.click('app-layout-header li a[href="/login"]');

}
module.exports ={
    openApp : openApp,
    gotoSign : gotoSign
};
//必须导出封装的方法,这样在其他js文件中才能调用这些方法

可以看到通过引入“jest-puppeteer”即可在每个js文件中直接引用全局的page对象,无需自己写代码实现,给大家带来了很大的便捷。实际jest-puppeteer是借用了jest框架的一些能力实现共享全局的一个browser和page对象,如果大家对这部分实现原理感兴趣可查看我的另外一个puppeteer事例代码 “https://github.com/tlqiao/puppeteer-e2e.git ”,这个事例代码来源于 “https://github.com/xfumihiro/jest-puppeteer-example.git ”。jest-puppeteer实现原理不影响后续UI层脚本的编写,大家可以按兴趣去选择是否详细了解。

有过编写UI层自动化脚本经验的同学可能都知道UI层测试有个比较大的痛点是如何保证脚本的稳定性,为了提高脚本稳定性,其中比较重要的措施之一是做好等待处理,例如点击一个页面元素时先写等待脚本,保证这个元素在页面已显示后再进行点击操作,那使用puppeteer框架时,如何处理等待问题呢?接下来就让我们一起来学习如何进行自动等待处理。

实现自动等待,减少脚本编写成本

Puppeteer框架自身也提供了一些等待处理方法,例如waitForSelecotor(),waitFor(),waitFunction()等,调用这些方法实现等待都需要在编写脚本的时候手动实现,那如何实现自动等待呢?即脚本编写时无需调用waitForSelector()等方法,要实现自动等待需要引入第三方包“expect-puppeteer”。

下面案例是打开一个页面,完成一个加法运行,在执行过程中,当点击运算按钮时,会等待1-2秒才会出现运算结果。这里如果不进行等待处理,那么案例肯定会因为无法找到匹配的结果而运行失败。引入“expect-puppeteer”,脚本中未调用任何wait方法,运行案例,执行成功,说明自动等待生效。同样,执行“npm run auto-wait”即可运行该案例,案例代码如下所示。

describe('auto-wait with jest-expect ', () => {
    it('should add numbers correctly with auto wait', async () => {
        await page.goto('http://juliemr.github.io/protractor-demo/');

        await expect(page).toFill('input[ng-model="first"]','5');
        // 使用expect-puppeteer包,所有的脚本都是expect(page)开头,这里是在输入框中输入数字5

        await expect(page).toSelect('select[ng-model="operator"]','SUBTRACTION');
        //选择下拉列表中的减法这个值

        await expect(page).toFill('input[ng-model="second"]','3');
        //输入框中输入数字3

        await expect(page).toClick('#gobutton');
        //点击go这个button

        await expect(page).toMatchElement('h2',{text:'2'});
        //获取h2这个页面元素的innerText,并校验值是否等于2

        await expect(page).toMatchElement('tbody tr:nth-child(1) td:nth-child(3)',{text:'2'});
       //获取table列表中值并校验是否等于2 
    })
});

可以看到引入“expect-puppeteer”后,轻松实现了自动等待,关于”expect-puppeteer”更多的细节后面还会单独讲解。处理好页面元素的等待只是提高脚本稳定性的措施之一,关于如何提高UI层脚本稳定性是一个比较大的内容,后续还会有章节单独讲解。至此,本次课程就结束了,此次课程介绍了使用puppeteer框架时如何使用Page Object设计模式以及实现自动等待。下篇博客将介绍如何利用puppeteer定位和操作页面元素以及校验页面内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

taoli-qiao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值