一、概要
前面介紹了Puppeteer+jest+TypeScript做UI自動化,但是這知識基礎的,我們實現自動化要考慮的很多,比如PO模式,比如配置文件,比如斷言等等。下面就來一一實現我是怎么用puppeteer 做UI自動化的
二、斷言
(一)需要依賴的安裝包
依賴包 | 命令 |
---|---|
Jest | npm install jest --save-dev |
@types/jest | npm install @types/jest --save-dev |
expect-puppeteer | npm install expect-puppeteer --save-dev |
@types/expect-puppeteer | npm install @types/expect-puppeteer --save-dev |
我們寫過Jest的知道Jest自己就帶有斷言,但是Jest的斷言有時候可能不全滿足我們,我們來看看expect-puppeteer的api
鏈接:expect-puppeteer(api)
(二) 簡單介紹下api
- 看api的介紹,expect-puppeteer封裝了一些可供我們使用的方法,斷言比如toMatch(驗證頁面是否能匹配到值),toMathcElement((驗證頁面是否能匹配到元素),這兩個還是比較好用的,尤其toMathcElement,具體實戰再說
- 其他api,看跟puppeteer api有點不一樣,puppeteer一樣有click方法那么expect-puppeteer好處是什么呢?這個時候我們就要看源碼了這里建議即使有現成的庫,我們也要多看看源碼
- 查看expect-puppeteer - index.d.ts 發現寫了一個ExpectPuppeteer接口,這里里面就有我們所有的api,我們順便點看任意一個js文件看看,目錄(expect-puppeteer - lib - options.js),我們就看看這個文件,看不懂沒關系,但是大概我們能猜出,就是控制運行時間的嘛,實際工作中一個case可能會寫大量的waitfor()是不是很麻煩,所以,我建議,吧options.js的timeout設置長一點,這樣方便更准確的尋找頁面元素
- api不多做介紹,照着api文檔就會了,比較簡單
options.js -> 修改timeout為2s
let defaultOptionsValue = {
timeout: 2000
};
三、PO 模式
問:什么是PO模式?
答:概念自行百度,我就不粘貼了,我想稍微寫過一點UI自動化的,應該都會多多少少了解一點,通俗的說,我們把元素,方法,測試case 分開寫,這樣方便我們去管理,邏輯也不叫清晰,具體下面拿實例來說明
四、實例 (以同程網站為實例)
今天我們來寫,從首頁進入ly.com,點擊機票 - 國內機票 - 驗證機票默認彈框
(一)我的腳本目錄
-----__tests__
-------ui
--------DomesticTictet
-------- cases
-------- basic.test.ts
-------- element
--------Index
-------- action
-------- Navi.action.help.ts
-------- element
-------- Navigation.help.ts
-----env
------ ly.yaml
-----utils
------ config.js
- 測試用例都在__tests__文件夾中,DomesticTictet,Index 不同模塊的文件夾,分別有cases(測試用例存放的文件夾)element(管理頁面元素)action(方法)
- env,管理yaml文件的文件夾,所有的yaml文件放在這里
- utils 自己寫的工具類,config.js讀取yaml文件
(二)element類管理
Navigation.help.ts
import { Page } from 'puppeteer';
import expectPuppeteer = require('expect-puppeteer');
export const Nav_Ticket = '#menuNav li:nth-child(3) b';
export const DomesticTicket = '.submenu-nav .flight-submenu1';
home.help.ts
// 標題名
export const titleContent = '.s-title.dflex span';
// 出發城市
export const start_city_input = '.s-box:nth-child(1) input[value]';
// 到達城市
export const arrive_city_input = '.s-box:nth-child(3) input[value]';
// 出發時間
export const start_data_input = 'input[placeholder="出發日期"]';
// 到達時間
export const return_data_input = 'input[placeholder="返回日期"]';
// 搜索按鈕
export const domestic_tictet_search = '.s-button';
// 搜索不到航班信息提示
export const flight_no_data_tip = '.flight-no-data span'
// 存在航班的元素
export const flight_get_data = '.top-flight-info span b'
(三)action方法編寫
Navi.action.help.ts
import { Page } from 'puppeteer';
import expectPuppeteer = require('expect-puppeteer');
const navi_element = require('../element/Navigation.help');
export class Navi_Action {
/**
* 點擊國內機票
*/
public async hover_home_ticket(page:Page) {
await page.waitForSelector(navi_element.Nav_Ticket);
await page.hover(navi_element.Nav_Ticket);
await page.waitFor(3000);
await expectPuppeteer(page).toClick(navi_element.DomesticTicket);
await page.waitFor(3000);
}
}
(四)yaml配置文件編寫
ly.yaml
url:
web: https://www.ly.com/
flighthome: https://www.ly.com/flights/home
# puppeteer lanuch配置
puppeteer:
proxy:
viewport:
width: 1920
height: 1080
(五)測試用例編寫
basic.test.ts
import { Page } from 'puppeteer';
import { Navi_Action } from '../../Index/action/Navi.action.help';
const config = require('../../../../utils/config');
const Home_Element = require('../element/home.help');
const time = require('silly-datetime');
const ly = config.readConfig('ly');
describe('domestic ticket page content verification', () => {
let page : Page;
beforeEach( async () => {
page = await browser.newPage();
await page.setViewport(ly.puppeteer.viewport);
await page.goto(ly.url.web,{waitUntil:'networkidle2'});
let navi_action = new Navi_Action();
await navi_action.hover_home_ticket(page);
},30000)
afterEach ( async () => {
await page.close();
})
test('TEST_001:驗證跳轉鏈接' , async() => {
const url = await page.url();
await expect(url).toBe(ly.url.flighthome);
},30000);
test('TEST_002:驗證標題名' , async() => {
const titleElement = Home_Element.titleContent;
const content = await page.evaluate( (titleElement) => {
return document.querySelector(titleElement).innerHTML;
},titleElement);
await expect(content).toEqual('國內機票');
},30000);
test('TEST_003:驗證出發默認城市' , async() => {
const content = await page.$eval(Home_Element.start_city_input,el => el.getAttribute('value'));
await expect(content).toEqual('上海');
},30000);
test('TEST_004:驗證到達默認城市' , async() => {
const content = await page.$eval(Home_Element.arrive_city_input,el => el.getAttribute('value'));
await expect(content).toEqual('北京');
},30000);
test('TEST_004:驗證時間為當天時間' , async() => {
const current_time = time.format(new Date(),'YYYY-MM-DD');
const start_time_element = Home_Element.start_data_input;
const start_time_content = await page.evaluate( (start_time_element) => {
return document.querySelector(start_time_element).value;
},start_time_element);
await expect(start_time_content).toEqual(current_time);
},30000);
test('TEST_005:驗證到達默認值' , async() => {
const return_input = await page.$eval(Home_Element.return_data_input,el => el.getAttribute('placeholder'));
await expect(return_input).toEqual('返回日期');
},30000);
})
結果