Cypress之处理页面弹框以及多tab页间切换

上次博客讲解了如何模拟键盘输入和鼠标操作以及拖动页面元素,此次课程将介绍如何处理页面弹框,保证测试案例继续运行。另外,还会介绍如果测试案例跨多个tab页,如何实现多tab页间切换测试。最后介绍cypress框架提供的插件和自定义命令功能,为了完成此次课程目标,拆分了2个task。

  • 处理页面弹框
  • 多tab页间切换
  • 使用插件或自定义命令功能

接下来就从第一个task开始吧。

处理页面弹框

实际cypress在浏览器中运行时发出了很多事件,其中就包括监听alert或者confirm弹框,并允许控制弹框行为。默认情况下,会对弹框进行确认处理,也提供了入口让用户自定义如何处理弹框。例如下面web页面,当点击populate按钮时,会弹出一个confirm弹框。

Cypress之处理页面弹框以及多tab页间切换_第1张图片

编写自动化脚本,Test Runner中选择“handleAlertDialog_spec.js”运行下面的脚本。运行过程中并没有看见confirm弹框,因为cypress默认进行了确认处理。Test Runner上查看脚本运行日志,可以在console中看到对alert弹框做了确认处理。

   it("should cypress confirm alert dialog successfully", () => {
        cy.visit("https://devexpress.github.io/testcafe/example/");
        cy.get('#populate').click();
    });
  //可以看到,上面脚本只是打开被测页面完成点击按钮操作,没有任何脚本处理alert弹框。

 查看Test Runner左侧的运行日志,可以看到对alert框做了确认处理,下图是处理日志记录。

除默认确认操作外,还可以根据alert弹框信息,用户自定义弹框处理结果。下面脚本对弹框做拒绝处理,等同于点击confirm弹框的cancel按钮。

  it("should change alert dialog behavior successfully", () => {
        cy.on('window:confirm', (str) => {  
            //cy.on('window:confirm')监听页面弹出的alert、confirm弹框

            if (expect(str).to.eq('Reset information before proceeding?')) {
                return false
                //return fasle表示对弹框做拒绝处理,等于点击弹框上的cancel按钮

            } else {
                return true
               //return true或者不返回任何值表示对弹框做确认处理,等于点击弹框上的confirm按钮 
            }
        });
        cy.visit("https://devexpress.github.io/testcafe/example/");
        cy.get('#populate').click();
    });

 查看上面脚本运行日志,可以看到console中对弹框做了拒绝处理。日志记录如下图所示。

Cypress之处理页面弹框以及多tab页间切换_第2张图片

Cypress框架在浏览器中运行时,除监听confirm、aler弹框外,还监听应用运行的异常信息,这个在上次课程中也提到过。例如下面的脚本,默认情况下,cypress监听到被测应用存在未捕获的异常信息就会终止测试脚本运行,并显示错误信息。如果运行过程中,不期望被测应用的异常阻止测试脚本运行,那么可以return false。

it("should handle dialog successfully",()=> {
        Cypress.on('uncaught:exception', (err, runnable) => {
            //监听被测应用未捕获的异常信息

            return false
           //如果发现异常信息,不阻断测试脚本运行,默认情况下会阻断测试脚本运行,并显示错误信息 
        });
        cy.visit("https://chercher.tech/practice/popups");
        //打开被测应用

        cy.get('input[name="alert"]').click();
       //打开一个alert弹框,cypress默认进行了确认处理。

        cy.get('input[name="confirmation"]').click();
      //打开一个confirm弹框,cypress默认进行了确认处理  
    })

 上面只介绍了cypress运行过程中发出的三个事件:window:confirm,window:alert,uncaught:exception。实际,Cypress运行过程中还发出了其他类型的事件,Cypress官网可以查询所有发出的事件信息。“ Catalog of Events | Cypress Documentation ”。上面讲解了如何处理弹框,接下来看看如果测试案例涉及多个tab页,如何进行处理。

多tab页间切换

这里的多tab页切换指当前页面上点击某个链接或者button后,浏览器上打开了一个新的tab页面,测试脚本需要切换到新的页面定位和操作页面元素。实际,Cyress框架并不支持多tab页间切换操作,因为cypress是运行在浏览器内部非node环境下,那么如果测试场景中涉及多tab页间切换,如何处理呢?Cypress官网给了一些workaround的解决办法,下面列举了官网建议的workround方法。

  • 第一:如果测试场景只是想验证点击某个链接或者button后,会新开一个tab页,那么可以通过校验button或者链接包含属性"target"且属性值是"_blank"。
  • 第二:点击链接或者button时,删除"target=_blank"属性,这样该页面就会在当前页面被打开,那么就可以继续操作打开的新页面上的元素。
  • 第三:如果能直接知道新打开的tab页链接地址,那么在当前窗口上通过调用cy.visit("new tab page url")直接访问新tab页面链接地址。
  • 第四:调用cy.request("new tab page url").its(body)或者新tab页的html body内容,然后校验body内容中是否存在某些页面元素。

下面是对于上面四种workaround方式的示例代码,同样,Test Runner中选择“checkAttribute_spec.js”即可运行下面的脚本。

describe("multiple tab demo", () => {
    Cypress.on('uncaught:exception', (err, runnable) => {
        return false
    });
    //添加cypress.on(....)是因为找的这个demo页面自身会报一些错误,忽略被测页面的异常信息。

    it("should check href and target attribute successfully", () => {
        cy.visit("https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_a_target");
        cy.getIframe('iframe[name="iframeResult"]').find('p a')
        //找的被测页面包含了iframe,故这里调用了前面封装的getIframe()自定义命令定位操作iframe下的页面元素

            .should('have.attr', 'target')
            .and('include', '_blank')
        //校验超链接的属性值,除了校验target属性,还可以校验href属性值是否和期望的一致    
    })
});

下面是第二种workaround方式,Test Runner上选择“updateAttribute_spec.js”即可运行下面的脚本。

describe("multiple tab demo", () => {
    Cypress.on('uncaught:exception', (err, runnable) => {
        return false
    });
    it("should remove target attribute successfully",()=> {
        cy.visit("https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_a_target");
        cy.getIframe('iframe[name="iframeResult"]').find('p a[href="https://www.w3schools.com"]')
            .invoke('removeAttr', 'target').click();
            //点击click前删除target属性,此时点击页面链接,就会在当前窗口打开新的tab页

        cy.getIframe('iframe[name="iframeResult"]').find('a').contains('Try it Yourself').first().click();
        //点击新tab页上的元素,以此验证可以定位操作新tab页上面的页面元素

    })
});

剩余的两种workaround方式演示脚本如下所示,对应的脚本文件名称是“vistNewTabUrl_spec.js” 和 “checkBody_spec.js”。

describe("multiple tab demo", () => {
    Cypress.on('uncaught:exception', (err, runnable) => {
        return false
    });
    it("should visit new tab url successfully",()=> {
        cy.visit("https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_a_target");
        cy.getIframe('iframe[name="iframeResult"]').find('p a[href="https://www.w3schools.com"]')
            .should('have.attr', 'target')
            .and('include', '_blank');
        cy.visit('https://www.w3schools.com');
        //通过调用cy.visit(new tab page url),那么新的tab页就会在目前页面上打开。

        cy.getIframe('iframe[name="iframeResult"]').find('a').contains('Try it Yourself').first().click();

    })
});

describe("multiple tab demo",()=> {
    it("should check body successfully",()=> {
        cy.visit("https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_a_target");
        cy.getIframe('iframe[name="iframeResult"]').find('p a[href="https://www.w3schools.com"]')
            .then($el => {
                const href = $el.prop("href");
                cy.request(href)
                    .its('body')
                    .should('include' ,'a[href="/html/tryit.asp?filename=tryhtml_default"]')
                //获取新tab的html body,校验body中是否存在期望查找的页面元素,此种方法有一定局限性,因为此种方法无法实现点击新tab页上的页面元素
            })
    })
});

总结而言,如果测试场景只是想校验点击某个链接后应该在新的tab页上显示内容,那么可以通过方法一完成;如果测试场景是点击某个链接后,还需操作新tab页上的页面元素,那么可以通过方法二和方法三完成。如果测试场景需要新旧两个tab页上来回切换操作,那么上面的四种方式可能就无法满足测试场景需要。这也是选择cypress框架的局限性之一,如果你的被测应用有较多的多tab页切换场景,那么就要考虑是否选择此框架了。

Cypress框架除在多tab页间切换场景支持有限外,也不支持跨域页面请求。例如下面的脚本运行会出错,因为点击主页面链接后,打开的新tab页和主页不属于相同的域。同样,Test Runner中选择“vistDiffDomainPage_spec.js”即可运行下面的脚本。

describe("visit different domain page",()=> {
    Cypress.on('uncaught:exception', (err, runnable) => {
        return false
    });
    it("can't visit different domain page",()=> {
      cy.visit("https://freshdesignweb.com/jquery-html5-file-upload/");
        cy.get('a[href="https://codepen.io/bi11johnston/pen/bsGDf"]')
            .invoke('removeAttr', 'target').click();
      cy.get('a[href="https://codepen.io/bi11johnston/pen/bsGDf"]').click();
      //当点击该链接后,会在当前页面打开新域名的页面,因为cypress不支持跨域请求,故测试案例会失败
    })

     it("can't visit diff domain url page", () => {
        cy.visit("http://www.lorenzostanco.com/lab/demos/CrossDomainFragment/Demo.html");
        cy.get('p a').first().click();
    })
    //第二个脚本也是个跨域请求的例子,当运行此脚本时,cypress也会报错。
});

运行结果如下图所示,可以看到案例运行失败,并提示cypress不支持跨域请求。

Cypress之处理页面弹框以及多tab页间切换_第3张图片

总结而言,cypress对于多tab页间切换操作场景支持有限,另外,不支持测试场景中访问跨域的页面。后面介绍的puppeteer和testcafe框架在这两个点上支持就比较好,故在选择框架时需根据被测项目情况选择合适的框架。

使用插件或自定义命令功能

执行“npm install cypress”命令时,除完成cypress安装外,该命令还会初始化一些目录和文件。对于插件而言,生成plugin目录,并在该目录下默认生成index.js文件;对于自定义命令而言,生成support目录,并在该目录下默认生成index.js和command.js文件。接下来就看看如何使用cypress提供的插件功能。

插件功能分为两类,一类是使用已经写好的插件,一类是在index.js文件中编写脚本。对于第一类,使用现有的插件,使用方法很简单,执行安装命令安装插件后,调用插件提供的功能即可。例如前面已经使用过的“cypress-file-upload”或者“cypress-shadow-dom”。可查看这里“ Plugins | Cypress Documentation ”获取cypress提供的所有插件信息。这里重点介绍如何在index.js编写自定义脚本。

index.js文件中默认必须按如下格式导出方法,这里的config信息是cypress.json中配置的信息,即通过插件脚本可以轻松获取cypress.json中所有的配置信息。on()方法中第一个参数是eventName,cypress支持4中类型的event,每个task的作用可查看此处资料 “ Writing a Plugin | Cypress Documentation ”。

module.exports = (on, config) => {
    on('eventName',(arg1,arg2)=> {..... }))
}

 下面脚本演示了通过调用task event完成数据库链接操作。当index.js中定义了数据库链接脚本后,当需要连接数据库进行查询操作时,调用cy.task('taskName')即可。

const mysql = require('mysql');
module.exports = (on, config) => {
        const con = mysql.createConnection({
            host: 'localhost',
            user: 'root',
            password: 'root123456',
            database: 'test'
        });

        on('task', {queryDBWithParameter(sql) {
                return new Promise((resolve, reject) => {
                    con.query(sql,function (err, result) {
                        if (err) {
                            reject(err)
                        } else {
                            return resolve(result)
                        }
                    })
                })
            }
        });
    };

上面就是关于插件的介绍,接着看看自定义命令功能,在前面的课程中实际已使用过自定义命令功能。只要是一些通用的处理,实际都可以封装成自定义命令,这样可以在脚本任意位置轻松调用自定义命令完成相关逻辑处理。例如调用任意接口前都需要先获取登陆生成的token,那么可以把登陆生成token封装成自定义命令,脚本如下所示。

Cypress.Commands.add("login", (email, password) => {      
    //定义command的名称是“login”,后面调用时输入Cypress.log(email,password)即可

   return cy.request({         //调用cy.request()方法完成接口调用
        url: "https://conduit.productionready.io/api/users/login",
        method: "POST",
        body: {
            "user": {
                "email": email,
                "password": password
            }
        }
    }).then(response => {
        return response.body.user.token
    })
});

调用自定义的login命令获取token后调用创建blog接口,代码详情如下所示。可以看到该文件并没有引入command.js文件,但却可以调用command.js中自定义的命令,因为Cypress在启动时会在所有脚本前加载command中的内容,无需手动引入。Test Runner上选择“createArticleWithCommand_spec.js”运行下面的案例,可以看到接口返回200,说明通过自定义命令正确获取到了token信息。

function createArticleThree() {
    cy.login('[email protected]','12345678').then(token => {     
        //cy.login()方式调用

        cy.request({
            method: 'POST',
            url: 'https://conduit.productionready.io/api/articles/',
            headers: {
                'authorization': 'Token '+token
            },
            body: {
                "article": {
                    "tagList": [],
                    "title": "testTitle",
                    "description": "test",
                    "body": "test"
                }
            }
        }).then(response => {
            expect(response.status).to.eq(200);
        })
    })
}

你可能感兴趣的:(Web,UI自动化测试,ui,测试工具,javascript)