在上一期内容中,咱们介绍了Node.js 结合 Cucumber开发多端的自动化,并发布了演示视频: 如何作跨平台业务流程自动化(Windows,Moible,Web)html
这一期咱们就主要介绍一下开发的详细步骤。java
经过本例学习,能够掌握如何同时测试Windows,Mobile,Web应用。node
Mobile端:安卓应用商店搜索"Outlook" Web端:outlook.live.com/mail/android
自动化Mobile端使用到Appium,请在本机安装好Appium 以及Android相关配置环境(具体请在网上查找相关资料)web
自动化Web端须要使用到selenium-server,以及浏览器驱动,能够在 npm.taobao.org/ 下载。chrome
打开CukeTest,新建项目,【项目模板】选择 "Basic",【项目名】输入"OutlookTesting",【项目路径】输入本身的本地目录。点击【建立】npm
CukeTest自己自带的有操做Windows库的控件,因此咱们只须要引用便可。针对Web端和Mobile端,咱们使用开源的webdriver.io库做为实现库。具体api能够参考官方文档 webdriver.io/guide.htmljson
【项目名】右键--【在命令行窗口打开】windows
在打开的命令行窗口输入api
npm install webdriverio @types/webdriverio --save
复制代码
安装成功以后会有以下提示
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN outlooktesting@1.0.0 No repository field.
+ @types/webdriverio@4.13.0
+ webdriverio@4.14.0
added 155 packages from 192 contributors and audited 345 packages in 50.09s
found 0 vulnerabilities
复制代码
分别为Windows,Mobile,Web 建立不一样驱动。
features/support/web_driver.js
var webdriverio = require('webdriverio');// 设置浏览器信息
var options = {
desiredCapabilities: {
browserName: 'chrome'
}};
//建立driver
function createDriver(){
return webdriverio.remote(options)
}
exports.driver = createDriver();
复制代码
features/support/mobile_driver.js
const webdriverio = require('webdriverio');
//设置被测应用参数
let options = {
desiredCapabilities: {
platformName: "Android",
deviceName: "9c83590", //设备序列串号
// platformVersion: "5.1", //系统平台版本
appPackage: "com.microsoft.office.outlook", //package 名字
appActivity: ".MainActivity", //启动activity 名字
resetKeyboard: true,
noReset: true,
unicodeKeyboard: true
},
host: "127.0.0.1",
port: 4723
}
//根据参数配置建立WebDriverIO实例;
function createDriver() {
const client = webdriverio.remote(options);
return client;
}
exports.app_driver = createDriver();
复制代码
建立Windows桌面应用模型文件features\support\Mail.tmodel
Windows驱动文件 features\support\win_driver.js
const { TestModel, Auto } = require("leanpro.win");
let path = require('path')
let tmodelfile = path.join(__dirname,'Mail.tmodel')
var model = TestModel.loadModel(tmodelfile);
exports.model = model;
复制代码
此时的目录结构为
│ package-lock.json
│ package.json
│
└─features
│ feature1.feature
│
├─step_definitions
│ definitions1.js
│
└─support
Mail.tmodel
mobile_driver.js
web_driver.js
win_driver.js
复制代码
为了让咱们的测试更有趣,这里虚拟了一个这样的剧本:
Jason做为公司的老板,开会须要用到一些培训文档,Carol做为公司职员,已经将培训文档作好并存储在公司电脑上。 Jason使用Windows桌面端 Mail向Carol发送邮件索要文档。 Carol在公司外边使用mobile收到邮件后利用mobile端回复Jason稍后会回到公司发送。 Carol回到公司后使用web端将文档做为附件发送给Jason。
利用这样的用户场景:能够将Windows,Mobile,Web 这三端的自动化链接起来。因而咱们在feature文件中能够以下编辑:
features/feature1.feature
# language: zh-CN
功能: 同时自动化desktop,mobile,web
Outlook 应用分为移动端,Windows端和Web端。
Jason在Windows端给Carol发送邮件,Carol手机端收到邮件后回复Jason。
以后Carol在Web端给Jason发送一封带有附件的邮件。
@desktop
场景: Jason使用PC端Mail给Carol发送邮件索要文档
假如打开Outlook桌面客户端
当点击新建邮件
而且在收件人,主题,收件内容中输入对应的信息
同时点击发送邮件
@mobile
场景: Carol手机端回复Jason稍后发送
假如打开手机端Outlook
当打开收件箱窗口
同时打开未读邮件
同时答复框内回复对应内容并发送
@web
场景: Carol使用web端回复Jason
假如用户Carol登陆Outlook web页面
当打开收件箱并打开最新一次的邮件
那么回复Jason邮件并上传相关文档
复制代码
对应的可视化界面
在features/step_definitons 目录里分别建立mobileAction.js, webAction.js, windowsAction.js 分别存储3端的自动化脚本。
场景1中,实现对Windows桌面应用的自动化。
var { Given, When, Then } = require('cucumber')
Given(/^打开Outlook桌面客户端$/, async function () {
return 'pending';
});
When(/^点击新建邮件$/, async function () {
return 'pending';
});
When(/^在收件人,主题,收件内容中输入对应的信息$/, async function () {
return 'pending';
});
When(/^点击发送邮件$/, async function () {
return 'pending';
});
复制代码
引入Windows对象模型
const { model } = require('../support/win_driver');
const { Util } = require('leanpro.common');
复制代码
const { Given, When, Then } = require('cucumber');
const { model } = require('../support/win_driver');
const { Util } = require('leanpro.common');
Given(/^打开Outlook桌面客户端$/, async function () {
await model.getButton("开始").click(0, 0, 1);
await Util.delay(2000);
await model.getListItem("Mail").click(0, 0, 1);
});
When(/^点击新建邮件$/, async function () {
await Util.delay(2000)
await model.getButton("New mail").click(0, 0, 1);
})
;When(/^在收件人,主题,收件内容中输入对应的信息$/, async function () {
await model.getEdit("To:").clearAll();
await model.getEdit("To:").set("carolseaver1@outlook.com");
await model.getEdit("Subject").clearAll();
await model.getEdit("Subject").set("培训大纲");
let content = "hi,Carol:{ENTER}请把本次培训内容发送给我好吗?{ENTER}Jason Seaver"
await model.getDocument("消息").clearAll();
await model.getDocument("消息").set(content);});
When(/^点击发送邮件$/, async function () {
await model.getButton("Send").click(0, 0, 1);
await Util.delay(1000)
});
复制代码
场景2中,实现对Mobile应用的自动化。
var { Given, When, Then } = require('cucumber')
Given(/^打开手机端Outlook$/, async function () {
return 'pending';
});
When(/^打开收件箱窗口$/, async function () {
return 'pending';
})
;When(/^打开未读邮件$/, async function () {
return 'pending';
});
When(/^答复框内回复对应内容并发送$/, async function () {
return 'pending';
});
复制代码
const { Util} = require('leanpro.common')
const { app_driver } = require('../support/mobile_driver')
复制代码
使用webdriver.io 相关api 实现自动化操做代码。最终代码 features/step_defintions/mobileAction.js
const { Given, When, Then } = require('cucumber')
const { Util} = require('leanpro.common')
const { app_driver } = require('../support/mobile_driver')
Given(/^打开手机端Outlook$/, async function () {
// 手机客户端接收邮件须要5-10秒的延迟
await Util.delay(5000)
return true
});
When(/^打开收件箱窗口$/, async function () {
await app_driver.click('~打开导航抽屉');
await app_driver.click('android=new UiSelector().resourceId("com.microsoft.office.outlook:id/drawer_item_title").index(1).text("收件箱")')});
Then(/^打开未读邮件$/, async function () {
await app_driver.waitForExist('android=new UiSelector().resourceId("com.microsoft.office.outlook:id/message_snippet_frontview").index(0).className("android.widget.LinearLayout")',20*1000);
await app_driver.click('android=new UiSelector().resourceId("com.microsoft.office.outlook:id/message_snippet_frontview").index(0).className("android.widget.LinearLayout")')});Then(/^答复框内回复对应内容并发送$/, async function () {
let loctor = 'new UiSelector().text("答复").index(1)'
await app_driver.click('android='+loctor)
await app_driver.clearElement('~邮件正文。')
let content = `
Hi,Jason:
培训内容文档在我公司PC上保存,如今我在外边,稍后我到公司回复您。
Carol Seaver
`
await app_driver.setValue('~邮件正文。',content);
await app_driver.click('~发送');
});
复制代码
场景3中,实现对web应用的自动化。
var { Given, When, Then } = require('cucumber')
Given(/^用户Carol登陆Outlook web页面$/, async function () {
return 'pending';
});
When(/^打开收件箱并打开最新一次的邮件$/, async function () {
return 'pending';
});
Then(/^回复Jason邮件并上传相关文档$/, async function () {
return 'pending';
});
复制代码
const { driver } = require('../support/web_driver');
复制代码
var { Given, When, Then } = require('cucumber')
const { Auto } = require('leanpro.win')
const { Util } = require('leanpro.common')
const path = require('path')
const assert = require('assert');
const { driver } = require('../support/web_driver');
Given(/^用户Carol登陆Outlook web页面$/, async function () {
await driver.url("https://outlook.live.com/mail/inbox#");
await driver.click('div.headerHero>a:last-child');
await driver.setValue('#i0116','carolseaver1@outlook.com');
await driver.click('#idSIButton9');
await driver.setValue('#i0118','密码');
await driver.waitForEnabled('#idSIButton9',20*1000)
await driver.click('#idSIButton9');
});
When(/^打开收件箱并打开最新一次的邮件$/, async function () {
await driver.waitForEnabled('div > div[role="option"] > div[draggable="true"] > div[tabindex="-1"]',15*1000);
await driver.click('div > div[role="option"] > div[draggable="true"] > div[tabindex="-1"]' );
});
Then(/^回复Jason邮件并上传相关文档$/,{timeout:180*1000}, async function () {
// await Util.delay(1000);
let content = `
hi,Jason:
附件为本次培训内容大纲。请查收,谢谢。
Carol Seaver
`
let bool = await driver.waitForEnabled('div[tabindex="-1"] > div > button[type="button"]> div >span',45*1000)
console.log("ele",bool);
if(bool==true){
console.log("get Text ===")
let text = await driver.getText('div[tabindex="-1"] > div > button[type="button"]> div >span')
console.log("text == ",text)
if(text.includes('加载')){
console.log('click loading')
await driver.click('div[tabindex="-1"] > div > button[type="button"]> div >span');
await Util.delay(1000)
await driver.click('div[tabindex="-1"] > div > button[type="button"]> div >span')
}else{
await driver.click('div[tabindex="-1"] > div > button[type="button"]> div >span')
}
}
// await driver.keys()
// let input_area = await driver.element('div[dir="ltr"]');
await driver.waitForExist('div[dir="ltr"]',20*1000);
await driver.click('div[dir="ltr"]')
// await driver.clearElement('div[dir="ltr"]')
await driver.setValue('div[dir="ltr"]',content);
await driver.waitForEnabled('button[name="附加"]',20*1000);
await driver.click('button[name="附加"]');
await Util.delay(500)
await driver.waitForVisible('button[name="浏览此计算机"]',20*1000);
await driver.click('button[name="浏览此计算机"]');
await Util.delay(1500);
let uplaodFilePane =await Auto.getPane({
"className": "Chrome_WidgetWin_1",
"name": "邮件 - Carol Seaver - Outlook - Google Chrome"
}).getWindow({
"className": "#32770",
"title": "打开"
}).getComboBox({
"automationId": "1148",
"name": "文件名(N):"
}).getEdit({
"automationId": "1148",
"name": "文件名(N):"
})
let filepath = path.join(__dirname, '..', '..', 'files','培训大纲.md')
await uplaodFilePane.clearAll();
await uplaodFilePane.set(filepath);
await Auto.getPane({
"className": "Chrome_WidgetWin_1",
"name": "邮件 - Carol Seaver - Outlook - Google Chrome"
}).getWindow({
"className": "#32770",
"title": "打开"
}).getButton({
"automationId": "1",
"name": "打开(O)"
}).click(0, 0, 1);
await driver.waitForEnabled('div.ms-Button-flexContainer> i[data-icon-name="Send"]',20*1000);
await driver.click('div.ms-Button-flexContainer> i[data-icon-name="Send"]')
await Util.delay(500);
});
复制代码
设置hooks
const { BeforeAll, After, Before, AfterAll, setDefaultTimeout } = require('cucumber');
const cuketest = require('cuketest');
const { Util } = require('leanpro.win');
const { app_driver } = require('./mobile_driver');
const { model } = require('./win_driver');
const { driver } = require('./web_driver');
//set default step timeout
setDefaultTimeout(120 * 1000);
BeforeAll(async function () {
await cuketest.minimize();
await driver.init();
await driver.windowHandleMaximize();
await driver.timeouts(30 * 1000);
await app_driver.init();
await app_driver.timeouts(10000);
})
After({ tags: "@desktop" }, async function () {
let screenshot = await model.getWindow("Window").takeScreenshot();
this.attach(screenshot, 'image/png');
await cuketest.delay(1000);
await model.getWindow("Window").close();
});
After({ tags: "@mobile" }, async function () {
let screenshot = await app_driver.saveScreenshot();
this.attach(screenshot, 'image/png');
await app_driver.end();});
After({ tags: "@web" }, async function () {
let screenshot = await driver.saveScreenshot();
this.attach(screenshot, 'image/png');
//clean up cookies
await driver.deleteCookie();
await driver.end();
});
AfterAll(async function () {
await cuketest.restore();})
复制代码
命令行中输入 appium
$ appium
[Appium] Welcome to Appium v1.9.1
[Appium] Appium REST http interface listener started on 0.0.0.0:4723
复制代码
$ java -jar -Dwebdriver.chrome.driver=./chromedriver.exe selenium-server-standalone-3.141.0.jar14:53:29.932 INFO [GridLauncherV3.parse] - Selenium server version: 3.141.0, revision: 2ecb7d9a14:53:30.135 INFO [GridLauncherV3.lambda$buildLaunchers$3] - Launching a standalone Selenium Server on port 44442018-11-08 14:53:30.260:INFO::main: Logging initialized @1318ms to org.seleniumhq.jetty9.util.log.StdErrLog14:53:30.728 INFO [WebDriverServlet.<init>] - Initialising WebDriverServlet14:53:31.564 INFO [SeleniumServer.boot] - Selenium Server is up and running on port 4444
复制代码
点击feature文件上每一个场景后的运行按钮能够运行单个场景,点击运行项目,能够运行整个项目。运行项目后会生成对应的图文报表。
依托强大的Node.js生态和大量的开源库。使用CukeTest配合开源的工具能够完成各种应用的自动化。既快速又免费,有兴趣的能够尝试一下噢。