UI Recorder 是一款零成本UI自动化录制工具,类似于Selenium IDE.
UI Recorder 要比Selenium IDE更加强大!
开源地址 官网地址
以windows为例
uirecorder要求nodejs版本号 >= v7.x,官网下载 node-v8.11.3-x64.msi 按向导完成nodejs安装,并完成环境变量设置,比如安装目录是:D:\Program Files\nodejs
检查:打开cmd
Microsoft Windows [版本 6.1.7601]
版权所有 (c) 2009 Microsoft Corporation。保留所有权利。
C:\Users\yueling>node -v
v8.11.3
C:\Users\yueling>npm -v
5.6.0
C:\Users\yueling>
要先配置npm的全局模块的存放路径以及cache的路径,例如希望将以上两个文件夹放在NodeJS的主目录下,便在NodeJs下建立”node_global”及”node_cache”两个文件夹,输入以下命令改变npm配置
C:\Users\yueling>npm config set prefix "D:\Program Files\nodejs\node_global"
C:\Users\yueling>npm config set cache "D:\Program Files\nodejs\node_cache"
在系统环境变量添加系统变量NODE_PATH,输入路径D:\Program Files\nodejs\node_global\node_modules,此后所安装的模块都会安装到改路径下
C:\Users\yueling>npm install -g cnpm --registry=https://registry.npm.taobao.org
将D:\Program Files\nodejs\node_global添加到环境变量
检查:打开cmd
C:\Users\yueling>cnpm -v
cnpm@6.0.0 (D:\Program Files\nodejs\node_global\node_modules\cnpm\lib\parse_argv.js)
npm@6.2.0 (D:\Program Files\nodejs\node_global\node_modules\cnpm\node_modules\npm\lib\npm.js)
node@8.11.3 (D:\Program Files\nodejs\node.exe)
npminstall@3.9.2 (D:\Program Files\nodejs\node_global\node_modules\cnpm\node_modules\npminstall\lib\index.js)
prefix=D:\Program Files\nodejs\node_global
win32 x64 6.1.7601
registry=https://registry.npm.taobao.org
C:\Users\yueling>
下载最新的chrome版本,需要注意——版本要和驱动相对应。
C:\Users\yueling>npm install uirecorder mocha -g
C:\Users\yueling>npm install selenium-standalone -g
下面这一步在初始化的时候也会安装,可以跳过这一步,使用项目中的selenium-standalone
C:\Users\yueling>selenium-standalone install --drivers.firefox.baseURL=http://npm.taobao.org/mirrors/geckodriver --baseURL=http://npm.taobao.org/mirrors/selenium --drivers.chrome.baseURL=http://npm.taobao.org/mirrors/chromedriver --drivers.ie.baseURL=http://npm.taobao.org/mirrors/selenium
打开cmd
C:\Users\yueling>cd /d d:\autotest
d:\autotest>mkdir uitest
d:\autotest>cd uitest
d:\autotest\uitest>uirecorder init
d:\autotest\uitest>uirecorder init
__ ______ ____ __
/ / / / _/ / __ \___ _________ _________/ /__ _____
/ / / // / / /_/ / _ \/ ___/ __ \/ ___/ __ / _ \/ ___/
/ /_/ // / / _, _/ __/ /__/ /_/ / / / /_/ / __/ /
\____/___/ /_/ |_|\___/\___/\____/_/ \__,_/\___/_/ v2.5.42
Official Site: http://uirecorder.com
------------------------------------------------------------------
? Path扩展属性配置,除id,name,class之外 data-id,data-name,type,data-type,role,data-role,data-value
? 属性值黑名单正则
? class值黑名单正则
? 断言前隐藏
? WebDriver域名或IP 127.0.0.1
? WebDriver端口号 4444
? 需要同时测试的浏览器列表 chrome, ie 11
config.json 文件保存成功
package.json 文件创建成功
README.md 文件创建成功
screenshots 文件夹创建成功
commons 文件夹创建成功
uploadfiles 文件夹创建成功
.editorconfig 文件创建成功
.gitignore 文件创建成功
install.sh 文件创建成功
run.bat 文件创建成功
run.sh 文件创建成功
hosts 文件创建成功
.vscode/launch.json 文件创建成功
Start install project dependencies...
--------------------------------------------
√ Installed 7 packages
√ Linked 197 latest versions
√ Run 0 scripts
deprecate mochawesome-uirecorder@1.5.22 › node-uuid@^1.4.1 Use uuid module instead
Recently updated (since 2018-07-17): 2 packages (detail see file d:\autotest\uitest\node_modules\.recently_updates.txt)
√ All packages installed (228 packages installed from npm registry, used 2m, speed 15.34kB/s, json 204(1.53MB), tarball 0B)
Start install webdriver dependencies...
--------------------------------------------
> uirecorderTest@1.0.0 installdriver d:\autotest\uitest
> selenium-standalone install --drivers.firefox.baseURL=http://npm.taobao.org/mirrors/geckodriver --baseURL=http://npm.taobao.org/mirrors/selenium --drivers.chrome.baseURL=http://npm.taobao.org/mirrors/chromedriver --drivers.ie.baseURL=http://npm.taobao.org/mirrors/selenium
----------
selenium-standalone installation starting
----------
---
selenium install:
from: http://npm.taobao.org/mirrors/selenium/3.12/selenium-server-standalone-3.12.0.jar
to: d:\autotest\uitest\node_modules\_selenium-standalone@6.15.1@selenium-standalone\.selenium\selenium-server\3.12.0-server.jar
---
chrome install:
from: http://npm.taobao.org/mirrors/chromedriver/2.40/chromedriver_win32.zip
to: d:\autotest\uitest\node_modules\_selenium-standalone@6.15.1@selenium-standalone\.selenium\chromedriver\2.40-x64-chromedriver
---
ie install:
from: http://npm.taobao.org/mirrors/selenium/3.12/IEDriverServer_x64_3.12.0.zip
to: d:\autotest\uitest\node_modules\_selenium-standalone@6.15.1@selenium-standalone\.selenium\iedriver\3.12.0-x64-IEDriverServer.exe
---
firefox install:
from: http://npm.taobao.org/mirrors/geckodriver/v0.20.1/geckodriver-v0.20.1-win64.zip
to: d:\autotest\uitest\node_modules\_selenium-standalone@6.15.1@selenium-standalone\.selenium\geckodriver\0.20.1-x64-geckodriver
---
edge install:
from: https://download.microsoft.com/download/F/8/A/F8AF50AB-3C3A-4BC4-8773-DC27B32988DD/MicrosoftWebDriver.exe
to: d:\autotest\uitest\node_modules\_selenium-standalone@6.15.1@selenium-standalone\.selenium\edgedriver\17134-MicrosoftEdgeDriver.exe
-----
selenium-standalone installation finished
-----
在cmd控制台输入uirecorder
d:\autotest\uitest>uirecorder
__ ______ ____ __
/ / / / _/ / __ \___ _________ _________/ /__ _____
/ / / // / / /_/ / _ \/ ___/ __ \/ ___/ __ / _ \/ ___/
/ /_/ // / / _, _/ __/ /__/ /_/ / / / /_/ / __/ /
\____/___/ /_/ |_|\___/\___/\____/_/ \__,_/\___/_/ v2.5.42
Official Site: http://uirecorder.com
------------------------------------------------------------------
? 测试脚本文件名: test.spec.js
? 打开同步校验浏览器? No
? 浏览器大小 (格式:1024 x 768): maximize
录制服务器监听在端口: 17725
[101228:102628:0724/104435.165:ERROR:install_util.cc(597)] Unable to read registry value HKLM\SOFTWARE\Policies\Google\Chrome\MachineLevelUserCloudPolicyEnrollmentToken for writing result=2
DevTools listening on ws://127.0.0.1:17729/devtools/browser/2060c6df-6ef6-46bd-9c88-b37d542fb64f
录制浏览器已开启
url: https://www.baidu.com
waitBody:
[101228:102580:0724/104456.783:ERROR:shader_disk_cache.cc(237)] Failed to create shader cache entry: -2
click: wd ( #kw, 210, 7, 0 )
sendKeys: helloworld{ENTER}
expect: text, #content_left div[id="1"] > h3.c-gap-bottom-small > a, equal, helloworld_百度百科
sleep: 1000
click: helloworld_百度百科 ( #content_left div[id="1"] > h3.c-gap-bottom-small > a, 113, 7, 0 )
switchWindow: 1
waitBody:
waitBody:
click: span.cmn-baike-logo > em.cmn-icons_logo-du, 32, 32, 0
waitBody:
expect: val, #lemmaNum, notEqual, 1
mouseMove: 分类 ( //a[text()="分类"], 21, 3 )
click: 生活 ( dl.cat > dd:nth-child(2) > div:nth-child(6) > a, 10, 8, 0 )
switchWindow: 2
waitBody:
expect: text, //h2[text()="生活频道"], equal, 生活频道
------------------------------------------------------------------
共录制18个步骤 (未经过校验)
录制脚本已保存: test.spec.js
录制服务器已关闭
录制浏览器已关闭
d:\autotest\uitest>dir
回放并生成测试报告
d:\autotest\uitest>mocha test.spec.js --reporter mochawesome-uirecorder
[mochawesome] Generating report files...
test.spec : chrome
√ url: https://www.baidu.com (3919ms)
√ waitBody: (596ms)
√ click: wd ( #kw, 210, 7, 0 ) (730ms)
√ sendKeys: helloworld{ENTER}
√ expect: text, #content_left div[id="1"] > h3.c-gap-bottom-small > a, equal, helloworld_百度百科
√ sleep: 1000 (1002ms)
√ click: helloworld_百度百科 ( #content_left div[id="1"] > h3.c-gap-bottom-small > a, 113, 7, 0 ) (815ms)
√ switchWindow: 1 (3250ms)
√ waitBody: (601ms)
√ waitBody: (585ms)
√ click: span.cmn-baike-logo > em.cmn-icons_logo-du, 32, 32, 0 (3886ms)
√ waitBody: (576ms)
√ expect: val, #lemmaNum, notEqual, 1
√ mouseMove: 分类 ( //a[text()="分类"], 21, 3 ) (734ms)
√ click: 生活 ( dl.cat > dd:nth-child(2) > div:nth-child(6) > a, 10, 8, 0 ) (756ms)
√ switchWindow: 2 (3084ms)
√ waitBody: (573ms)
√ expect: text, //h2[text()="生活频道"], equal, 生活频道
18 passing (42s)
[mochawesome] Report saved to reports\index.html
[mochawesome] Report saved to reports\index.xml
d:\autotest\uitest>cd /d d:\autotest
脚本test.spec.js
const fs = require('fs');
const path = require('path');
const chai = require("chai");
const should = chai.should();
const JWebDriver = require('jwebdriver');
chai.use(JWebDriver.chaiSupportChainPromise);
const resemble = require('resemblejs-node');
resemble.outputSettings({
errorType: 'flatDifferenceIntensity'
});
const rootPath = getRootPath();
module.exports = function(){
let driver, testVars;
before(function(){
let self = this;
driver = self.driver;
testVars = self.testVars;
});
it('url: https://www.baidu.com', async function(){
await driver.url(_(`https://www.baidu.com`));
});
it('waitBody: ', async function(){
await driver.sleep(500).wait('body', 30000).html().then(function(code){
isPageError(code).should.be.false;
});
});
it('click: wd ( #kw, 210, 7, 0 )', async function(){
await driver.sleep(300).wait('#kw', 30000)
.sleep(300).mouseMove(210, 7).click(0);
});
it('sendKeys: helloworld{ENTER}', async function(){
await driver.sendKeys('helloworld{ENTER}');
});
it('expect: text, #content_left div[id="1"] > h3.c-gap-bottom-small > a, equal, helloworld_百度百科', async function(){
await driver.sleep(300).wait('#content_left div[id="1"] > h3.c-gap-bottom-small > a', 30000)
.text()
.should.not.be.a('error')
.should.equal(_(`helloworld_百度百科`));
});
it('sleep: 1000', async function(){
await driver.sleep(1000);
});
it('click: helloworld_百度百科 ( #content_left div[id="1"] > h3.c-gap-bottom-small > a, 113, 7, 0 )', async function(){
await driver.sleep(300).wait('#content_left div[id="1"] > h3.c-gap-bottom-small > a', 30000)
.sleep(300).mouseMove(113, 7).click(0);
});
it('switchWindow: 1', async function(){
await driver.sleep(500).switchWindow(1);
});
it('waitBody: ', async function(){
await driver.sleep(500).wait('body', 30000).html().then(function(code){
isPageError(code).should.be.false;
});
});
it('waitBody: ', async function(){
await driver.sleep(500).wait('body', 30000).html().then(function(code){
isPageError(code).should.be.false;
});
});
it('click: span.cmn-baike-logo > em.cmn-icons_logo-du, 32, 32, 0', async function(){
await driver.sleep(300).wait('span.cmn-baike-logo > em.cmn-icons_logo-du', 30000)
.sleep(300).mouseMove(32, 32).click(0);
});
it('waitBody: ', async function(){
await driver.sleep(500).wait('body', 30000).html().then(function(code){
isPageError(code).should.be.false;
});
});
it('expect: val, #lemmaNum, notEqual, 1', async function(){
await driver.sleep(300).wait('#lemmaNum', 30000)
.val()
.should.not.be.a('error')
.should.not.equal(_(`1`));
});
it('mouseMove: 分类 ( //a[text()="分类"], 21, 3 )', async function(){
await driver.sleep(300).wait('//a[text()="分类"]', 30000)
.sleep(300).mouseMove(21, 3);
});
it('click: 生活 ( dl.cat > dd:nth-child(2) > div:nth-child(6) > a, 10, 8, 0 )', async function(){
await driver.sleep(300).wait('dl.cat > dd:nth-child(2) > div:nth-child(6) > a', 30000)
.sleep(300).mouseMove(10, 8).click(0);
});
it('switchWindow: 2', async function(){
await driver.sleep(500).switchWindow(2);
});
it('waitBody: ', async function(){
await driver.sleep(500).wait('body', 30000).html().then(function(code){
isPageError(code).should.be.false;
});
});
it('expect: text, //h2[text()="生活频道"], equal, 生活频道', async function(){
await driver.sleep(300).wait('//h2[text()="生活频道"]', 30000)
.text()
.should.not.be.a('error')
.should.equal(_(`生活频道`));
});
function _(str){
if(typeof str === 'string'){
return str.replace(/\{\{(.+?)\}\}/g, function(all, key){
return testVars[key] || '';
});
}
else{
return str;
}
}
};
if(module.parent && /mocha\.js/.test(module.parent.id)){
runThisSpec();
}
function runThisSpec(){
// read config
let webdriver = process.env['webdriver'] || '';
let proxy = process.env['wdproxy'] || '';
let config = require(rootPath + '/config.json');
let webdriverConfig = Object.assign({},config.webdriver);
let host = webdriverConfig.host;
let port = webdriverConfig.port || 4444;
let match = webdriver.match(/([^\:]+)(?:\:(\d+))?/);
if(match){
host = match[1] || host;
port = match[2] || port;
}
let testVars = config.vars;
let browsers = webdriverConfig.browsers;
browsers = browsers.replace(/^\s+|\s+$/g, '');
delete webdriverConfig.host;
delete webdriverConfig.port;
delete webdriverConfig.browsers;
// read hosts
let hostsPath = rootPath + '/hosts';
let hosts = '';
if(fs.existsSync(hostsPath)){
hosts = fs.readFileSync(hostsPath).toString();
}
let specName = path.relative(rootPath, __filename).replace(/\\/g,'/').replace(/\.js$/,'');
browsers.split(/\s*,\s*/).forEach(function(browserName){
let caseName = specName + ' : ' + browserName;
let browserInfo = browserName.split(' ');
browserName = browserInfo[0];
let browserVersion = browserInfo[1];
describe(caseName, function(){
this.timeout(600000);
this.slow(1000);
let driver;
before(function(){
let self = this;
let driver = new JWebDriver({
'host': host,
'port': port
});
let sessionConfig = Object.assign({}, webdriverConfig, {
'browserName': browserName,
'version': browserVersion,
'ie.ensureCleanSession': true,
'chromeOptions': {
'args': ['--enable-automation']
}
});
if(proxy){
sessionConfig.proxy = {
'proxyType': 'manual',
'httpProxy': proxy,
'sslProxy': proxy
}
}
else if(hosts){
sessionConfig.hosts = hosts;
}
self.driver = driver.session(sessionConfig).maximize().config({
pageloadTimeout: 30000, // page onload timeout
scriptTimeout: 5000, // sync script timeout
asyncScriptTimeout: 10000 // async script timeout
});
self.testVars = testVars;
let casePath = path.dirname(caseName);
self.screenshotPath = rootPath + '/screenshots/' + casePath;
self.diffbasePath = rootPath + '/diffbase/' + casePath;
self.caseName = caseName.replace(/.*\//g, '').replace(/\s*[:\.\:\-\s]\s*/g, '_');
mkdirs(self.screenshotPath);
mkdirs(self.diffbasePath);
self.stepId = 0;
return self.driver;
});
module.exports();
beforeEach(function(){
let self = this;
self.stepId ++;
if(self.skipAll){
self.skip();
}
});
afterEach(async function(){
let self = this;
let currentTest = self.currentTest;
let title = currentTest.title;
if(currentTest.state === 'failed' && /^(url|waitBody|switchWindow|switchFrame):/.test(title)){
self.skipAll = true;
}
if(!/^(closeWindow):/.test(title)){
let filepath = self.screenshotPath + '/' + self.caseName + '_' + self.stepId;
let driver = self.driver;
try{
// catch error when get alert msg
await driver.getScreenshot(filepath + '.png');
let url = await driver.url();
let html = await driver.source();
html = '<!--url: '+url+' -->\n' + html;
fs.writeFileSync(filepath + '.html', html);
let cookies = await driver.cookies();
fs.writeFileSync(filepath + '.cookie', JSON.stringify(cookies));
}
catch(e){}
}
});
after(function(){
return this.driver.close();
});
});
});
}
function getRootPath(){
let rootPath = path.resolve(__dirname);
while(rootPath){
if(fs.existsSync(rootPath + '/config.json')){
break;
}
rootPath = rootPath.substring(0, rootPath.lastIndexOf(path.sep));
}
return rootPath;
}
function mkdirs(dirname){
if(fs.existsSync(dirname)){
return true;
}else{
if(mkdirs(path.dirname(dirname))){
fs.mkdirSync(dirname);
return true;
}
}
}
function callSpec(name){
try{
require(rootPath + '/' + name)();
}
catch(e){
console.log(e)
process.exit(1);
}
}
function isPageError(code){
return code == '' || / jscontent="errorCode" jstcache="\d+"|diagnoseConnectionAndRefresh|dnserror_unavailable_header|id="reportCertificateErrorRetry"|400 Bad Request|403 Forbidden|404 Not Found|500 Internal Server Error|502 Bad Gateway|503 Service Temporarily Unavailable|504 Gateway Time-out/i.test(code);
}
function catchError(error){
}