Node.js開發多端自動化步驟詳解(Windows,Mobile,Web)

在上一期內容中,我們介紹了Node.js 結合 Cucumber開發多端的自動化,併發布了演示視訊: 如何做跨平臺業務流程自動化(Windows,Moible,Web)
這一期我們就主要介紹一下開發的詳細步驟。
主要目標
通過本例學習,可以掌握如何同時測試Windows,Mobile,Web應用。
環境配置
- 作業系統:Windows 10
- 開發語言:node.js
- 編輯工具:CukeTest
- 被測應用:Windows Outlook郵件
- 桌面端:Windows 10 Mail
Mobile端:安卓應用商店搜尋"Outlook" Web端: ofollow,noindex">outlook.live.com/mail/
其它必要庫
自動化Mobile端使用到Appium,請在本機安裝好Appium 以及Android相關配置環境(具體請在網上查詢相關資料)
自動化Web端需要使用到selenium-server,以及瀏覽器驅動,可以在npm.taobao.org/ 下載。
操作步驟
1. 建立專案
開啟CukeTest,新建專案,【專案模板】選擇 "Basic",【專案名】輸入"OutlookTesting",【專案路徑】輸入自己的本地目錄。點選【建立】

2. 安裝專案依賴庫
CukeTest本身自帶的有操作Windows庫的控制元件,所以我們只需要引用即可。針對Web端和Mobile端,我們使用開源的webdriver.io庫作為實現庫。具體api可以參考官方文件 webdriver.io/guide.html
【專案名】右鍵--【在命令列視窗開啟】

在開啟的命令列視窗輸入
npm install webdriverio @types/webdriverio --save 複製程式碼
安裝成功之後會有如下提示
npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN [email protected] No repository field. + @types/[email protected] + [email protected] added 155 packages from 192 contributors and audited 345 packages in 50.09s found 0 vulnerabilities 複製程式碼
3. 建立驅動
分別為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 複製程式碼
4. 編寫測試場景
為了讓我們的測試更有趣,這裡虛擬了一個這樣的劇本:
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郵件並上傳相關文件 複製程式碼
對應的視覺化介面

5. 完善自動化指令碼
在features/step_definitons 目錄裡分別建立mobileAction.js, webAction.js, windowsAction.js 分別儲存3端的自動化指令碼。
場景1中,實現對Windows桌面應用的自動化。
- 開啟windowsAction.js, 點選場景1中每個操作步驟後的灰色按鈕,在windwosAction.js中生成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'; }); 複製程式碼
- 開啟tmodel檔案,利用CukeTest自帶的模型管理器選取Windows操作控制元件,具體選取方式可以參考視訊教程 ke.qq.com/course/3473…

- Windows物件選取完成後實現windowsAction.js中定義的操作步驟。
引入Windows物件模型
const { model } = require('../support/win_driver'); const { Util } = require('leanpro.common'); 複製程式碼
- 模型管理器中根據選取的物件控制元件複製對應的實現程式碼。最終程式碼 features/step_definitons/windowsAction.js
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("[email protected]"); 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應用的自動化。
- 開啟features/step_definitions/moblieAction.js, 點選feature的視覺化介面中場景2中每個步驟後的灰色按鈕,在mobileAction.js中生成手機端操作程式碼樣例。
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應用的自動化。
- 開啟features/step_definitions/webAction.js, 點選feature的視覺化介面中場景3中每個步驟後的灰色按鈕,在mobileAction.js中生成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'; }); 複製程式碼
- 新增對web_driver 的引用:
const { driver } = require('../support/web_driver'); 複製程式碼
- 根據webdriverio相關api 完成對應的操作。最終程式碼 features/step_definitions/webAction.js
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','[email protected]'); 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
- 開啟features/support/hooks.js 檔案,設定執行時:
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 [Appium] Welcome to Appium v1.9.1 [Appium] Appium REST http interface listener started on 0.0.0.0:4723 複製程式碼
- 啟動selenium-server
$ 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配合開源的工具可以完成各類應用的自動化。既快速又免費,有興趣的可以嘗試一下噢。