浅谈cypress的PO模式
一、环境部署
1、安装Node.js
[https://nodejs.org/zh-cn/](https://nodejs.org/zh-cn/)
2、安装cypress
1) 新建项目目录,例如 /Users/dabing/cypress_public
2) 命令行进入该目录,并执行下面 命令
npm init --生成package.json文件
npm config set registry https://registry.npm.taobao.org --修改镜像库地址
npm install cypress --save -dev --安装cypress
3、打开cypress
方法一:
进入 /Users/dabing/cypress_public/node_modules/.bin目录
cypress open
方法二:
npx cypress open
方法三:
1)修改package.json,新增scripts代码段,例如:
"script"{
"cypress":"cypresss open"
}
2)执行 npm run cypress
执行完后会生成cypress文件夹
写用例是在这个目录下
二、UI与Api的第一个例子
直接上代码,备注也注明
// context相当于测试套件
context('登录',()=> {
// ui访问示例
it ('输入正确的用户名和密码,可以登录成功', () => {
// 访问的url,相当于selenium里的get
cy.visit("http://pc-pp.xingfuyikatong.com/login#none")
cy.get('#normalLoginTab').click()
cy.get('#username').type('10111111111')
cy.get('#pwd').type('lc123123')
cy.get('#formlogin > .item-fore5 > .login-btn > .btn-img').click()
// should用于断言使用
cy.get('span > a').should('contain','退出')
})
// 接口访问示例
it ('输入错误的用户名和密码,登录失败', () => {
cy.request({
url:'https://api-pp.xingfuyikatong.com/login',
// method是请求方法,默认情况是GET,还可以是POST、PUT、DELETE等
method:'post',
// 是否将body的值转换为url encoded并设置x-www=form-urlencoded标头
form:true,
// body是请求体
body:{
'loginName':'10111111111',
'password':'error',
'passportIdKey':'',
'platform':'H5'
}
}).then(response=>{
// 断言
expect(response.status).to.be.equal(200)
expect(response.body.msg).to.be.equal('密码不正确!')
})
})
})
三、PO设计模式
最早以前自动化方面接触的工具可能就是QTP了,该工具就是将所有元素都封装成对象,到至今很多演变的工具或者设计思想也都沿用于此,也是为之不多的能做c/s框架的UI自动化。随之更多轻便的工具衍生,cypress的轻便及高效的开发能力,确实得到了不少人的青睐。再次也只是针对以前用selenium时的PO设计思想套用到cpyress中。
以下是设计的目录结构
PO设计模式主要两大概念,封装业务对象、封装页面常见的业务流
1、封装对象识别属性–json–elementConfig.json
{
"loginPage":{
"username": "#username",
"password": "#pwd",
"submit": "#formlogin > .item-fore5 > .login-btn > .btn-img",
"accountError": "#login_error",
"exit": "span > a"
}
}
2、封装cypress可以识别的对象–loginpage.js–>class LoginPage
3、封装常见业务方法–loginpage.js–>class LoginPage
import locator from './elementConfig.json'
export default class LoginPage {
constructor() {
this.url = 'http://pc-pp.xingfuyikatong.com/login'
}
// 封装页面对象
get getByUsername(){
return cy.get(locator.loginPage.username)
}
get getByPassword(){
return cy.get(locator.loginPage.password)
}
get getBySubmit(){
return cy.get(locator.loginPage.submit)
}
get getByAccountError(){
return cy.get(locator.loginPage.accountError)
}
get getByExit(){
return cy.get(locator.loginPage.exit)
}
visit(){
cy.visit(this.url)
}
// 封装常见业务流
login(name,pwd){
cy.get('#normalLoginTab').click()
cy.wait(1)
if(name!==""){
this.getByUsername.type(name)
}
if(pwd!==""){
this.getByPassword.type(pwd)
}
this.getBySubmit.click()
}
}
4、编写测试用例:testLogin.js–调用loginpage.js–>class LoginPage
import LoginPage from "./page/loginPage";
import 'cypress-xpath'
context('登录',()=> {
it ('输入正确的用户名和密码,可以登录成功', () => {
let login = new LoginPage()
login.visit()
login.login('10111111111','lc123123')
login.getByExit.should('contain','退出')
})
it ('输入错误的用户名和密码,登录失败', () => {
let login = new LoginPage()
login.visit()
login.login('10111111111','error')
login.getByAccountError.should('contain','您输入的用户名或密码错误!')
})
})
四、常用定位元素的命令
describe('选择器及元素定位',()=>{
beforeEach(()=>{
cy.visit('https://example.cypress.io/commands/querying')
})
// -------------------------选择器--------------------------
it('方式1:id 选择器',()=>{
cy.get('#query-btn').should('contain','Button') //BDD
})
it('方式2:标签 选择器',()=>{
cy.get('button').should('contain','Button') //BDD
})
it('方式3:属性 选择器',()=>{
cy.get('[id="query-btn"]').should('contain','Button') //BDD
})
it('方式4:标签+属性 选择器',()=>{
cy.get('button[id="query-btn"]').should('contain','Button') //BDD
})
it('方式5:id+属性 选择器',()=>{
cy.get('#inputName[placeholder="Name"]').type('huice')
})
it('方式6:class 选择器',()=>{
cy.get('.query-btn').should('contain','Button')
})
it('方式7:级联混合 选择器',()=>{
cy.get('#querying .well>button').should('contain','Button')
})
it('方式8::nth-child(n)',()=>{
cy.get('.query-ul').get(':nth-child(2)').should('contain','Two')
})
// ------------------------元素获取-----------------------
// 方法一:get,略
it('contains 一',()=>{
cy.get('.query-list').contains('bananas').should('have.class', 'third')
})
it('contains 二',()=>{
cy.get('#querying').contains('ul', 'oranges').should('have.class', 'query-list')
})
it('find 二',()=>{
cy.get('#querying').find('.first').should('contain','first')
})
it('with',()=>{
cy.get('.query-form').within(() => {
cy.get('input:first').should('have.attr', 'placeholder', 'Email')
cy.get('input').first().should('have.attr', 'placeholder', 'Email')
cy.get('input').eq(0).should('have.attr', 'placeholder', 'Email')
cy.get('input:last').should('have.attr', 'placeholder', 'Password')
})
})
})
// 其它辅助方法:
// .children():获取dom元素的子元素
// .parents():获取dom元素的所有父元素
// .parent():获取向上级的第一层父元素
// .siblings():获取所有同级元素(兄弟元素)
// .first():匹配找到的第一个元素
// .last():匹配找到的最后一个元素
// .next():匹配紧跟着的下一个同级元素
// .nextAll:匹配该对象之后的所有同级元素
// .nextUntil():匹配该对象之后的所有同级元素,直到遇到Until中定义的元素为止
// .prev:与next()相反
// .prevAll:与nextAll相反
// .prevUntil():与nextUntil相反
// .each():遍历所有子元素
// it('each遍历所有元素',()=>{
// cy.get('.query-ul').each(($li) => {
// cy.log($li.text())
// })
// })
// // .eq(index):根据索引获取指定元素
// it.only('eq获取指定元素',()=>{
// cy.get('.query-ul').get('li').eq(1).should('contain','Two')
// })
// -----------------------元素常见操作--------------------------
// .click()
// .dblclick()
// .rightclick()
// .type()
// .clear()
// .check()
// .uncheck()
// .select('huice') .select(['huice1','huice2'])
// .trigger()
// .visit()
// .reload()/.reload(true) --强制刷新
// .viewpoint(1024,768) --设置窗口大小
// 后退:.go('back')/.go(-1) 前进:.go('forward')/.go(1)
// 判断元素是否可见:.should('be.visible')/.should('not.be.visible')
// 判断元素是否存在:.should('exist')/.should('not exist')
// 操作被覆盖的元素,例如:.click({force:true})