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

在上一期內容中,咱們介紹了Node.js 結合 Cucumber開發多端的自動化,併發布了演示視頻: 如何作跨平臺業務流程自動化(Windows,Moible,Web)html

這一期咱們就主要介紹一下開發的詳細步驟。java

主要目標

經過本例學習,能夠掌握如何同時測試Windows,Mobile,Web應用。node

環境配置

  • 操做系統:Windows 10
  • 開發語言:node.js
  • 編輯工具:CukeTest
  • 被測應用:Windows Outlook郵件
  • 桌面端:Windows 10 Mail

Mobile端:安卓應用商店搜索"Outlook" Web端:outlook.live.com/mail/android

其它必要庫

自動化Mobile端使用到Appium,請在本機安裝好Appium 以及Android相關配置環境(具體請在網上查找相關資料)web

自動化Web端須要使用到selenium-server,以及瀏覽器驅動,能夠在 npm.taobao.org/ 下載。chrome

操做步驟

1. 建立項目

打開CukeTest,新建項目,【項目模板】選擇 "Basic",【項目名】輸入"OutlookTesting",【項目路徑】輸入本身的本地目錄。點擊【建立】npm

2. 安裝項目依賴庫

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
複製代碼

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桌面應用的自動化。

  1. 打開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';
});

複製代碼
  1. 打開tmodel文件,利用CukeTest自帶的模型管理器選取Windows操做控件,具體選取方式能夠參考視頻教程 ke.qq.com/course/3473…

  1. Windows對象選取完成後實現windowsAction.js中定義的操做步驟。

引入Windows對象模型

const { model } = require('../support/win_driver');

const { Util } = require('leanpro.common');
複製代碼
  1. 模型管理器中根據選取的對象控件複製對應的實現代碼。最終代碼 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("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應用的自動化。

  1. 打開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';
});

複製代碼
  1. 引入手機端驅動
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應用的自動化。

  1. 打開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';
});
複製代碼
  1. 添加對web_driver 的引用:
const { driver } = require('../support/web_driver');
複製代碼
  1. 根據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','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

  1. 打開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();})
複製代碼

運行

  1. 啓動appium

命令行中輸入 appium

$ appium
[Appium] Welcome to Appium v1.9.1
[Appium] Appium REST http interface listener started on 0.0.0.0:4723 
複製代碼
  1. 啓動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配合開源的工具能夠完成各種應用的自動化。既快速又免費,有興趣的能夠嘗試一下噢。

相關文章
相關標籤/搜索