前面讲解了使用cypress框架如何定位、操作页面元素以及校验测试结果,此次课程将介绍如何实现文件上传、操作iframe下面的页面原因以及操作shadow dom下的页面元素。为了完成此次课程目标,拆分了3个task。
- 实现文件上传和下载
- 定位操作iframe下的页面元素
- 定位操作shadow dom下的页面元素
接下来我们就从第一个taks开始吧。
实现文件上传和下载
Cypress框架自身没有提供直接的方法完成文件的上传,如果要实现文件上传操作,可以引入第三方包“cypress-file-upload”,该包封装了cypress的自定义命令,故安装此包后,还需在command.js文件中引入该包。command.js中引入该包后,通过调用cy.get('elementSelector').attachFile(filePath)即可完成文件上传。需要注意一点,这里传入的filePath是相对于cypress/fixture的相对路径,具体可看下面的演示脚本。Test Runner中选择“uploadFile_spec.js”即可运行下面的脚本。
describe("upload file demo", () => {
Cypress.on('uncaught:exception', (err, runnable) => {
return false
});
//Cypress.on(....)作用是如果出现uncaught:exception的error时,继续运行测试案例不失败。添加此脚本是因为测试案例使用的被测页面有一些错误。
//实际项目不应该添加此语句忽略这样的异常信息,因为console界面的错误也属于issue
it("should upload file successfully", () => {
cy.visit("https://chercher.tech/practice/popups");
//打开被测页面,如下图所示
cy.get('input[type="file"]').attachFile('testData/dev/user.json');
//上传文件,首先通过cy.get(selector)定位上传文件按钮,然后调用attachFile(filePath)上传目标文件
cy.wait(3000);
//添加等待语句,主要是为了调试时查看是否真的上传了文件
cy.visit("https://chercher.tech/practice/popups");
cy.get('input[type="file"]')
.attachFile('testData/dev/user.json')
.attachFile('profile.json', {force: true, encoding: 'utf-8'});
//还支持一次上传多个文件,且attachFile()方法还支持一些可选参数输入
cy.wait(3000)
});
});
下图是被测页面,可以看到打开该页面时,console中有错误信息,这是因为被测页面脚本问题,故上面的测试脚本中需要添加Cypress.on(.....)语句忽略掉这些错误。默认情况下,cypress会监听这些异常信息,如果发现异常信息,测试案例会失败。运行上面的案例,应该能在页面上看到被上传的文件。
可以看到通过引入“cypress-file-upload”能非常方便的实现文件上传,除文件上传外还有文件下载场景,因为文件下载基本都是点击某个按钮或者链接完成文件下载,和普通的点击操作无区别,故这里不再做额外的介绍。接下来我们看看如何定位和操作iframe下的页面元素。
定位操作iframe下的页面元素
Cypress框架没有提供直接操作iframe下元素的的方法,如果被测应用中包含iframe,如何才能操作iframe下的元素呢?下面的内容将给大家演示如何定位和操作iframe下的页面元素。
如下图所示,打开的web页面中包含iframe。
下面的脚本演示了获取iframe下的" </h1/> " 元素的值以及点击iframe中的submit按钮。实现的思路是获取iframe下document对象,然后获取document对象的body,在body基础上查找需要的页面元素并进行点击操作。同样,Test Runner中选择“iframe_spec.js”即可运行下面的脚本。运行完成后Test Runner中可以看到点击submit后的页面,说明整个脚本运行与期望一致。
describe("control iframe", () => {
it("should access element in iframe successfully", ()=> {
cy.visit("https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_type_file");
getIframeBody('iframe[name="iframeResult"]').find('h1').should('contain','Show File-select Fields');
getIframeBody('iframe[name="iframeResult"]').find('input[type="submit"]').first().click();
cy.wait(3000);
})
});
const getIframeDocument = (iframeSelector) => {
//参数传入需要查找的iframe selector,利于方法的复用
return cy
.get(iframeSelector)
.its('0.contentDocument').should('exist')
//通过0.conentDocument属性获取document对象。这里添加should校验,保证iframe加载完成后才继续运行后面的脚本,保证脚本稳定性
};
const getIframeBody = (iframeSelector) => {
return getIframeDocument(iframeSelector)
.its('body').should('not.be.empty')
//获取document的body对象,并校验body内容不为空,保证iframe加载完成后才继续运行后面的脚本,保证脚本稳定性
.then(cy.wrap)
//cy.wrap是cypress提供的一个命令,调用cy.warp,后面才能继续添加链式命令,例如.find(selector).click()等
};
上面的脚本中把getIframeDocument()和getIframeBody()与测试用例脚本放在一起,但这两个方法属于共用方法,涉及iframe的地方都需要调用这两个方法,故这里可以优化一下。下面脚本把getIframeBody设置为cypress的自定义用户命令,这样在任何需要获取iframeBody的地方调用命令即可。support/command.js文件中添加getIframeBody的脚本。
Cypress.Commands.add('getIframe',(iframeSelector)=> { //定义的command名称是getIframe
return cy
.get(iframeSelector)
.its('0.contentDocument').should('exist')
.its('body').should('not.be.empty')
.then(cy.wrap)
})
修改测试案例脚本,通用使用cy.getIframe()命令操作iframe里面的页面元素。同样,Test Runner上选择“iframeWithCommand_spec.js”即可运行下面的脚本。
it('should control element in iframe with command',()=> {
cy.visit("https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_type_file");
cy.getIframe('iframe[name="iframeResult"]').find('h1').should('contain','Show File-select Fields');
//调用cy.getIframe(iframeSelector).find(elementSelector)定位iframe下的页面元素
cy.getIframe('iframe[name="iframeResult"]').find('input[type="submit"]').first().click();
cy.wait(3000);
//这里添加等待是方便调试时查看点击iframe下button后的效果,确保确实点击了submit button
});
执行结果如下图所示,Test Runner中可以看到点击了submit后打开了新的页面。
关于cypress中封装自定义命令的内容后面章节还会单独进行详细讲解。接下来我们看看如果页面元素包含在shadow dom中,如何定位操作shadow dom下的页面元素。
定位操作shadow dom下的页面元素
Cypress框架自身并没有直接提供定位操作shadow dom下页面元素的方法,但可以通过引入第三方包“cypress-shadow-dom”实现定位shadow dom下的页面元素。该包添加了cypress自定义命令,故安装此包后,还需在command.js文件中引入该包。下图是一个包含shdow dom的web页面。
下面是定位操作shadow dom下页面元素的demo脚本,同样,Test Runner中选择“shadowDom_spec.js”即可运行下面的脚本。
describe("shadow dom demo", () => {
it("should control dom in shadow successfully", () => {
cy.visit("https://radogado.github.io/shadow-dom-demo/");
cy.shadowGet('#app')
//获取shadow dom对象
.shadowFind('p')
//在shadow dom对象基础上通过elementSelector定位页面元素
.its('length')
.should('eq', 1);
//获取定位的页面元素个数并校验个数是否等于1
cy.shadowGet('#app')
.shadowFind('p')
.shadowContains('Dynamically generated content');
//定位shadow dom下的页面元素p,并校验p的innerText等于“Dynamically generated content”
cy.shadowGet('#app')
.shadowFind('p')
.shadowClick()
//定位shadow dom下的页面元素p,并点击p元素
})
});
可以看到通过引入“cypress-shadow-dom”可以方便的定位和操作shadow dom下的页面元素。除上面脚本用的方法外,该包还提供了其他一些api,这里汇总整理了“cypress-shadow-dom”目前提供的所有api。
cy.shadowGet('selector')
//这里传入的selector和普通web页面上定位元素的selector一致,需要注意一点是,selector定位到shadow-root的位置
cy.shadowGet('selector').find('selector')
cy.shadowGet('selector').find('selector').shadowFirst()
cy.shadowGet('selector').find('selector').shadowLast()
//定位到shadow dom后,在此基础上继续查找shadow dom下的页面元素,如果获取到了多个页面元素,那么可以通过shadowFirst()或者shadowLast()定位期望的页面元素
cy.shadowGet('selector').find('selector').shadowClick()
//点击定位的页面元素
cy.shadowGet('selector').find('selector').shadowType()
//输入内容到shadow dom下的输入框中
cy.shadowGet('selector').find('selector').shadowContain(contentText)
//校验定位的元素的innerText是否包含contentText
cy.shadowGet('selector').find('selector').trigger(eventName)
//这里的event指mousedown,mouseup,mouseleave等等
上面列举了“cypress-shadow-dom”包对外提供的api,这些api除接受必填参数外,还接受可选参数输入。例如:shadowClick({force:true})接受可选参数force,即是否强制进行点击操作。如果想了解每个api可以接受哪些可选参数可查看github上介绍